Update API [skip ci]

This commit is contained in:
Ilya Laktyushin 2022-12-13 17:42:32 +04:00
parent 21121e1924
commit 47ffcd2e4d
41 changed files with 867 additions and 302 deletions

View File

@ -8450,6 +8450,7 @@ Sorry for the inconvenience.";
"UserInfo.SuggestPhoto" = "Suggest Photo for %@"; "UserInfo.SuggestPhoto" = "Suggest Photo for %@";
"UserInfo.SetCustomPhoto" = "Set Photo for %@"; "UserInfo.SetCustomPhoto" = "Set Photo for %@";
"UserInfo.ResetCustomPhoto" = "Reset to Original Photo"; "UserInfo.ResetCustomPhoto" = "Reset to Original Photo";
"UserInfo.CustomPhotoInfo" = "You can replace %@s photo with another photo that only you will see.";
"UserInfo.SuggestPhotoTitle" = "Do you want to suggest a profile picture for %@?"; "UserInfo.SuggestPhotoTitle" = "Do you want to suggest a profile picture for %@?";
"UserInfo.SetCustomPhotoTitle" = "Do you want to set a custom profile picture for %@?"; "UserInfo.SetCustomPhotoTitle" = "Do you want to set a custom profile picture for %@?";
@ -8467,6 +8468,9 @@ Sorry for the inconvenience.";
"UserInfo.CustomPhoto" = "photo set by you"; "UserInfo.CustomPhoto" = "photo set by you";
"UserInfo.CustomVideo" = "video set by you"; "UserInfo.CustomVideo" = "video set by you";
"UserInfo.PublicPhoto" = "public photo";
"UserInfo.PublicVideo" = "public video";
"UserInfo.ResetToOriginalAlertText" = "Are you sure you want to reset to %@ original photo?"; "UserInfo.ResetToOriginalAlertText" = "Are you sure you want to reset to %@ original photo?";
"UserInfo.ResetToOriginalAlertReset" = "Reset"; "UserInfo.ResetToOriginalAlertReset" = "Reset";
@ -8491,3 +8495,12 @@ Sorry for the inconvenience.";
"PhotoEditor.SetAsMyPhoto" = "Set as My Photo"; "PhotoEditor.SetAsMyPhoto" = "Set as My Photo";
"PhotoEditor.SetAsMyVideo" = "Set as My Video"; "PhotoEditor.SetAsMyVideo" = "Set as My Video";
"Notification.BotWriteAllowed" = "You allowed this bot to message you when you added it in the attachment menu.";
"Privacy.ProfilePhoto.SetPublicPhoto" = "Set Public Photo";
"Privacy.ProfilePhoto.UpdatePublicPhoto" = "Update Public Photo";
"Privacy.ProfilePhoto.RemovePublicPhoto" = "Remove Public Photo";
"Privacy.ProfilePhoto.PublicPhotoInfo" = "You can upload a public photo for those who are restricted from viewing your real profile photo.";
"WebApp.AddToAttachmentAllowMessages" = "Allow **%@** to send me messages";

View File

@ -116,7 +116,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati
switch action.action { switch action.action {
case let .photoUpdated(image), let .suggestedProfilePhoto(image): case let .photoUpdated(image), let .suggestedProfilePhoto(image):
if let peer = messageMainPeer(EngineMessage(message)), let image = image { if let peer = messageMainPeer(EngineMessage(message)), let image = image {
let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action")]) let promise: Promise<[AvatarGalleryEntry]> = Promise([AvatarGalleryEntry.image(image.imageId, image.reference, image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: .media(media: .message(message: MessageReference(message), media: media), resource: $0.resource)) }), peer._asPeer(), message.timestamp, nil, message.id, image.immediateThumbnailData, "action", false)])
let sourceCorners: AvatarGalleryController.SourceCorners let sourceCorners: AvatarGalleryController.SourceCorners
if case .photoUpdated = action.action { if case .photoUpdated = action.action {

View File

@ -198,7 +198,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio
} else { } else {
string = presentationData.strings.MemberRequests_UserAddedToGroup(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string string = presentationData.strings.MemberRequests_UserAddedToGroup(peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string
} }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string, action: nil), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .invitedToVoiceChat(context: context, peer: peer, text: string, action: nil, duration: 3), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}) })
} }

View File

@ -190,13 +190,13 @@ final class MediaPickerGridItemNode: GridItemNode {
func animateFadeIn(animateCheckNode: Bool, animateSpoilerNode: Bool) { func animateFadeIn(animateCheckNode: Bool, animateSpoilerNode: Bool) {
if animateCheckNode { if animateCheckNode {
self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.checkNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
self.gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.gradientNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
if animateSpoilerNode { if animateSpoilerNode {
self.spoilerNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.spoilerNode?.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
} }

View File

@ -285,18 +285,18 @@ private class MediaPickerSelectedItemNode: ASDisplayNode {
self.isHidden = self.interaction?.hiddenMediaId == asset.uniqueIdentifier self.isHidden = self.interaction?.hiddenMediaId == asset.uniqueIdentifier
if !self.isHidden && wasHidden { if !self.isHidden && wasHidden {
if let checkNode = self.checkNode, checkNode.alpha > 0.0 { if let checkNode = self.checkNode, checkNode.alpha > 0.0 {
checkNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) checkNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
if let durationTextNode = self.durationTextNode, durationTextNode.alpha > 0.0 { if let durationTextNode = self.durationTextNode, durationTextNode.alpha > 0.0 {
durationTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) durationTextNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
if let durationBackgroundNode = self.durationBackgroundNode, durationBackgroundNode.alpha > 0.0 { if let durationBackgroundNode = self.durationBackgroundNode, durationBackgroundNode.alpha > 0.0 {
durationBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) durationBackgroundNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
if let spoilerNode = self.spoilerNode, spoilerNode.alpha > 0.0 { if let spoilerNode = self.spoilerNode, spoilerNode.alpha > 0.0 {
spoilerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) spoilerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
} }
} }
} }

View File

@ -37,10 +37,16 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id
|> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in |> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in
if let peer = peerViewMainPeer(peerView) { if let peer = peerViewMainPeer(peerView) {
var secondEntry: TelegramMediaImage? var secondEntry: TelegramMediaImage?
if firstEntry.representations.first?.representation.isPersonal == true, let cachedData = peerView.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo { var lastEntry: TelegramMediaImage?
secondEntry = photo if let cachedData = peerView.cachedData as? CachedUserData {
if firstEntry.representations.first?.representation.isPersonal == true, case let .known(photo) = cachedData.photo {
secondEntry = photo
}
if case let .known(photo) = cachedData.fallbackPhoto {
lastEntry = photo
}
} }
return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry, secondEntry: secondEntry) return fetchedAvatarGalleryEntries(engine: context.engine, account: context.account, peer: peer, firstEntry: firstEntry, secondEntry: secondEntry, lastEntry: lastEntry)
|> map(Optional.init) |> map(Optional.init)
} else { } else {
return .single(nil) return .single(nil)
@ -74,7 +80,7 @@ public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: Peer
public enum AvatarGalleryEntry: Equatable { public enum AvatarGalleryEntry: Equatable {
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?) case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, GalleryItemIndexData?, Data?, String?)
case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?) case image(MediaId, TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Peer?, Int32?, GalleryItemIndexData?, MessageId?, Data?, String?, Bool)
public init(representation: TelegramMediaImageRepresentation, peer: Peer) { public init(representation: TelegramMediaImageRepresentation, peer: Peer) {
self = .topImage([ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.standalone(resource: representation.resource))], [], peer, nil, nil, nil) self = .topImage([ImageRepresentationWithReference(representation: representation, reference: MediaResourceReference.standalone(resource: representation.resource))], [], peer, nil, nil, nil)
@ -87,7 +93,7 @@ public enum AvatarGalleryEntry: Equatable {
return .resource(last.representation.resource.id.stringRepresentation) return .resource(last.representation.resource.id.stringRepresentation)
} }
return .topImage return .topImage
case let .image(id, _, representations, _, _, _, _, _, _, _): case let .image(id, _, representations, _, _, _, _, _, _, _, _):
if let last = representations.last { if let last = representations.last {
return .resource(last.representation.resource.id.stringRepresentation) return .resource(last.representation.resource.id.stringRepresentation)
} }
@ -99,7 +105,7 @@ public enum AvatarGalleryEntry: Equatable {
switch self { switch self {
case let .topImage(_, _, peer, _, _, _): case let .topImage(_, _, peer, _, _, _):
return peer return peer
case let .image(_, _, _, _, peer, _, _, _, _, _): case let .image(_, _, _, _, peer, _, _, _, _, _, _):
return peer return peer
} }
} }
@ -108,7 +114,7 @@ public enum AvatarGalleryEntry: Equatable {
switch self { switch self {
case let .topImage(representations, _, _, _, _, _): case let .topImage(representations, _, _, _, _, _):
return representations return representations
case let .image(_, _, representations, _, _, _, _, _, _, _): case let .image(_, _, representations, _, _, _, _, _, _, _, _):
return representations return representations
} }
} }
@ -117,7 +123,7 @@ public enum AvatarGalleryEntry: Equatable {
switch self { switch self {
case let .topImage(_, _, _, _, immediateThumbnailData, _): case let .topImage(_, _, _, _, immediateThumbnailData, _):
return immediateThumbnailData return immediateThumbnailData
case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _): case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _, _):
return immediateThumbnailData return immediateThumbnailData
} }
} }
@ -126,7 +132,7 @@ public enum AvatarGalleryEntry: Equatable {
switch self { switch self {
case let .topImage(_, videoRepresentations, _, _, _, _): case let .topImage(_, videoRepresentations, _, _, _, _):
return videoRepresentations return videoRepresentations
case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _): case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _, _):
return videoRepresentations return videoRepresentations
} }
} }
@ -135,7 +141,7 @@ public enum AvatarGalleryEntry: Equatable {
switch self { switch self {
case let .topImage(_, _, _, indexData, _, _): case let .topImage(_, _, _, indexData, _, _):
return indexData return indexData
case let .image(_, _, _, _, _, _, indexData, _, _, _): case let .image(_, _, _, _, _, _, indexData, _, _, _, _):
return indexData return indexData
} }
} }
@ -148,8 +154,8 @@ public enum AvatarGalleryEntry: Equatable {
} else { } else {
return false return false
} }
case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId, lhsImmediateThumbnailData, lhsCategory): case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId, lhsImmediateThumbnailData, lhsCategory, lhsIsFallback):
if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory { if case let .image(rhsId, rhsImageReference, rhsRepresentations, rhsVideoRepresentations, rhsPeer, rhsDate, rhsIndexData, rhsMessageId, rhsImmediateThumbnailData, rhsCategory, rhsIsFallback) = rhs, lhsId == rhsId, lhsImageReference == rhsImageReference, lhsRepresentations == rhsRepresentations, lhsVideoRepresentations == rhsVideoRepresentations, arePeersEqual(lhsPeer, rhsPeer), lhsDate == rhsDate, lhsIndexData == rhsIndexData, lhsMessageId == rhsMessageId, lhsImmediateThumbnailData == rhsImmediateThumbnailData, lhsCategory == rhsCategory, lhsIsFallback == rhsIsFallback {
return true return true
} else { } else {
return false return false
@ -176,8 +182,8 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE
let indexData = GalleryItemIndexData(position: index, totalCount: count) let indexData = GalleryItemIndexData(position: index, totalCount: count)
if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry { if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry {
updatedEntries.append(.topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category)) updatedEntries.append(.topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category))
} else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category) = entry { } else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category, isFallback) = entry {
updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category)) updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category, isFallback))
} }
index += 1 index += 1
} }
@ -203,7 +209,7 @@ public func initialAvatarGalleryEntries(account: Account, engine: TelegramEngine
if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first { if photo.immediateThumbnailData == nil, let firstEntry = initialEntries.first, let firstRepresentation = firstEntry.representations.first {
representations.insert(firstRepresentation, at: 0) representations.insert(firstRepresentation, at: 0)
} }
return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil)] return [.image(photo.imageId, photo.reference, representations, photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, nil, nil, nil, photo.immediateThumbnailData, nil, false)]
} else { } else {
if case .known = peerPhoto { if case .known = peerPhoto {
return [] return []
@ -235,7 +241,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) {
var initialMediaIds = Set<MediaId>() var initialMediaIds = Set<MediaId>()
for entry in initialEntries { for entry in initialEntries {
if case let .image(mediaId, _, _, _, _, _, _, _, _, _) = entry { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _) = entry {
initialMediaIds.insert(mediaId) initialMediaIds.insert(mediaId)
} }
} }
@ -247,24 +253,24 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
photosCount += 1 photosCount += 1
for entry in initialEntries { for entry in initialEntries {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount))
if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _) = entry { if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _, _) = entry {
result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil)) result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil, false))
index += 1 index += 1
} }
} }
} }
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount))
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
index += 1 index += 1
} }
} else { } else {
for photo in photos { for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first { if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
} else { } else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
} }
index += 1 index += 1
} }
@ -276,7 +282,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
} }
} }
public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> { public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account, peer: Peer, firstEntry: AvatarGalleryEntry, secondEntry: TelegramMediaImage?, lastEntry: TelegramMediaImage?) -> Signal<(Bool, [AvatarGalleryEntry]), NoError> {
let initialEntries = [firstEntry] let initialEntries = [firstEntry]
return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries)) return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries))
|> then( |> then(
@ -292,7 +298,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) {
var initialMediaIds = Set<MediaId>() var initialMediaIds = Set<MediaId>()
for entry in initialEntries { for entry in initialEntries {
if case let .image(mediaId, _, _, _, _, _, _, _, _, _) = entry { if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _) = entry {
initialMediaIds.insert(mediaId) initialMediaIds.insert(mediaId)
} }
} }
@ -306,8 +312,8 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
photosCount += 1 photosCount += 1
for entry in initialEntries { for entry in initialEntries {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount))
if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _) = entry { if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _, _) = entry {
result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil)) result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil, false))
index += 1 index += 1
} }
} }
@ -317,7 +323,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
} }
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount))
result.append(.image(photo.image.imageId, photo.image.reference, representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
index += 1 index += 1
} }
} else { } else {
@ -325,12 +331,15 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account
if let secondEntry { if let secondEntry {
photos.insert(TelegramPeerPhoto(image: secondEntry, reference: secondEntry.reference, date: 0, index: 1, totalCount: 0, messageId: nil), at: 1) photos.insert(TelegramPeerPhoto(image: secondEntry, reference: secondEntry.reference, date: 0, index: 1, totalCount: 0, messageId: nil), at: 1)
} }
if let lastEntry {
photos.append(TelegramPeerPhoto(image: lastEntry, reference: lastEntry.reference, date: 0, index: photos.count, totalCount: 0, messageId: nil))
}
for photo in photos { for photo in photos {
let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count))
if result.isEmpty, let first = initialEntries.first { if result.isEmpty, let first = initialEntries.first {
result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, first.representations, photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, false))
} else { } else {
result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil)) result.append(.image(photo.image.imageId, photo.image.reference, photo.image.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), photo.image.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), peer, photo.date, indexData, photo.messageId, photo.image.immediateThumbnailData, nil, photo.image.id == lastEntry?.id))
} }
index += 1 index += 1
} }
@ -441,8 +450,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
let isFirstTime = strongSelf.entries.isEmpty let isFirstTime = strongSelf.entries.isEmpty
var entries = entries var entries = entries
if !isFirstTime, let updated = entries.first, case let .image(mediaId, imageReference, _, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) = updated, !videoRepresentations.isEmpty, let previous = strongSelf.entries.first, case let .topImage(representations, _, _, _, _, _) = previous { if !isFirstTime, let updated = entries.first, case let .image(mediaId, imageReference, _, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption, _) = updated, !videoRepresentations.isEmpty, let previous = strongSelf.entries.first, case let .topImage(representations, _, _, _, _, _) = previous {
let firstEntry = AvatarGalleryEntry.image(mediaId, imageReference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) let firstEntry = AvatarGalleryEntry.image(mediaId, imageReference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption, false)
entries.remove(at: 0) entries.remove(at: 0)
entries.insert(firstEntry, at: 0) entries.insert(firstEntry, at: 0)
} }
@ -758,13 +767,13 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
} else { } else {
} }
case let .image(_, reference, _, _, _, _, _, _, _, _): case let .image(_, reference, _, _, _, _, _, _, _, _, _):
if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) {
if let reference = reference { if let reference = reference {
let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference)
|> deliverOnMainQueue).start(next: { [weak self] photo in |> deliverOnMainQueue).start(next: { [weak self] photo in
if let strongSelf = self, let photo = photo, let firstEntry = strongSelf.entries.first, case let .image(_, _, _, _, _, index, indexData, messageId, _, caption) = firstEntry { if let strongSelf = self, let photo = photo, let firstEntry = strongSelf.entries.first, case let .image(_, _, _, _, _, index, indexData, messageId, _, caption, _) = firstEntry {
let updatedEntry = AvatarGalleryEntry.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), strongSelf.peer, index, indexData, messageId, photo.immediateThumbnailData, caption) let updatedEntry = AvatarGalleryEntry.image(photo.imageId, photo.reference, photo.representations.map({ ImageRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatar(peer: peerReference, resource: $0.resource)) }), photo.videoRepresentations.map({ VideoRepresentationWithReference(representation: $0, reference: MediaResourceReference.avatarList(peer: peerReference, resource: $0.resource)) }), strongSelf.peer, index, indexData, messageId, photo.immediateThumbnailData, caption, false)
for (lhs, rhs) in zip(firstEntry.representations, updatedEntry.representations) { for (lhs, rhs) in zip(firstEntry.representations, updatedEntry.representations) {
if lhs.representation.dimensions == rhs.representation.dimensions { if lhs.representation.dimensions == rhs.representation.dimensions {
@ -882,7 +891,7 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr
} }
} }
} }
case let .image(_, reference, _, _, _, _, _, messageId, _, _): case let .image(_, reference, _, _, _, _, _, messageId, _, _, _):
if self.peer.id == self.context.account.peerId { if self.peer.id == self.context.account.peerId {
if let reference = reference { if let reference = reference {
let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start()

View File

@ -107,7 +107,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode {
var buttonText: String? var buttonText: String?
var canShare = true var canShare = true
switch entry { switch entry {
case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _): case let .image(_, _, _, videoRepresentations, peer, date, _, _, _, _, _):
nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? "" nameText = peer.flatMap(EnginePeer.init)?.displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder) ?? ""
if let date = date { if let date = date {
dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string

View File

@ -106,7 +106,7 @@ class PeerAvatarImageGalleryItem: GalleryItem {
switch self.entry { switch self.entry {
case let .topImage(representations, _, _, _, _, _): case let .topImage(representations, _, _, _, _, _):
content = representations content = representations
case let .image(_, _, representations, _, _, _, _, _, _, _): case let .image(_, _, representations, _, _, _, _, _, _, _, _):
content = representations content = representations
} }
@ -268,7 +268,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
var id: Int64 var id: Int64
var category: String? var category: String?
if case let .image(mediaId, _, _, _, _, _, _, _, _, categoryValue) = entry { if case let .image(mediaId, _, _, _, _, _, _, _, _, categoryValue, _) = entry {
id = mediaId.id id = mediaId.id
category = categoryValue category = categoryValue
} else { } else {
@ -608,7 +608,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode {
switch entry { switch entry {
case let .topImage(topRepresentations, _, _, _, _, _): case let .topImage(topRepresentations, _, _, _, _, _):
representations = topRepresentations representations = topRepresentations
case let .image(_, _, imageRepresentations, _, _, _, _, _, _, _): case let .image(_, _, imageRepresentations, _, _, _, _, _, _, _, _):
representations = imageRepresentations representations = imageRepresentations
} }

View File

@ -15,6 +15,7 @@ import GalleryUI
import UniversalMediaPlayer import UniversalMediaPlayer
import RadialStatusNode import RadialStatusNode
import TelegramUIPreferences import TelegramUIPreferences
import AvatarNode
private class PeerInfoAvatarListLoadingStripNode: ASImageNode { private class PeerInfoAvatarListLoadingStripNode: ASImageNode {
private var currentInHierarchy = false private var currentInHierarchy = false
@ -90,7 +91,7 @@ private struct CustomListItemResourceId {
public enum PeerInfoAvatarListItem: Equatable { public enum PeerInfoAvatarListItem: Equatable {
case custom(ASDisplayNode) case custom(ASDisplayNode)
case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?)
case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?, Bool)
var id: MediaResourceId { var id: MediaResourceId {
switch self { switch self {
@ -99,7 +100,7 @@ public enum PeerInfoAvatarListItem: Equatable {
case let .topImage(representations, _, _): case let .topImage(representations, _, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return representation.resource.id return representation.resource.id
case let .image(_, representations, _, _): case let .image(_, representations, _, _, _):
let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation
return representation.resource.id return representation.resource.id
} }
@ -114,7 +115,7 @@ public enum PeerInfoAvatarListItem: Equatable {
} else { } else {
return false return false
} }
} else if case let .image(_, rhsRepresentations, _, _) = self { } else if case let .image(_, rhsRepresentations, _, _, _) = self {
if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }),
let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) {
return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation) return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation)
@ -124,7 +125,7 @@ public enum PeerInfoAvatarListItem: Equatable {
} else { } else {
return false return false
} }
} else if case let .image(_, lhsRepresentations, _, _) = self { } else if case let .image(_, lhsRepresentations, _, _, _) = self {
if case let .topImage(rhsRepresentations, _, _) = self { if case let .topImage(rhsRepresentations, _, _) = self {
if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }),
let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) {
@ -132,7 +133,7 @@ public enum PeerInfoAvatarListItem: Equatable {
} else { } else {
return false return false
} }
} else if case let .image(_, rhsRepresentations, _, _) = self { } else if case let .image(_, rhsRepresentations, _, _, _) = self {
if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }),
let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) {
return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation) return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation)
@ -153,7 +154,7 @@ public enum PeerInfoAvatarListItem: Equatable {
return [] return []
case let .topImage(representations, _, _): case let .topImage(representations, _, _):
return representations return representations
case let .image(_, representations, _, _): case let .image(_, representations, _, _, _):
return representations return representations
} }
} }
@ -165,20 +166,29 @@ public enum PeerInfoAvatarListItem: Equatable {
return [] return []
case let .topImage(_, videoRepresentations, _): case let .topImage(_, videoRepresentations, _):
return videoRepresentations return videoRepresentations
case let .image(_, _, videoRepresentations, _): case let .image(_, _, videoRepresentations, _, _):
return videoRepresentations return videoRepresentations
} }
} }
var isFallback: Bool {
switch self {
case .custom, .topImage:
return false
case let .image(_, _, _, _, isFallback):
return isFallback
}
}
public init?(entry: AvatarGalleryEntry) { public init?(entry: AvatarGalleryEntry) {
switch entry { switch entry {
case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _):
self = .topImage(representations, videoRepresentations, immediateThumbnailData) self = .topImage(representations, videoRepresentations, immediateThumbnailData)
case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _, isFallback):
if representations.isEmpty { if representations.isEmpty {
return nil return nil
} }
self = .image(reference, representations, videoRepresentations, immediateThumbnailData) self = .image(reference, representations, videoRepresentations, immediateThumbnailData, isFallback)
} }
} }
} }
@ -441,7 +451,7 @@ public final class PeerInfoAvatarListItemNode: ASDisplayNode {
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId id = id &+ resource.photoId
} }
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _):
representations = imageRepresentations representations = imageRepresentations
videoRepresentations = videoRepresentationsValue videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail immediateThumbnailData = immediateThumbnail
@ -523,6 +533,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
public let stripContainerNode: ASDisplayNode public let stripContainerNode: ASDisplayNode
public let highlightContainerNode: ASDisplayNode public let highlightContainerNode: ASDisplayNode
private let setByYouNode: ImmediateTextNode private let setByYouNode: ImmediateTextNode
private let setByYouImageNode: ImageNode
private var setByYouTapRecognizer: UITapGestureRecognizer?
public private(set) var galleryEntries: [AvatarGalleryEntry] = [] public private(set) var galleryEntries: [AvatarGalleryEntry] = []
private var items: [PeerInfoAvatarListItem] = [] private var items: [PeerInfoAvatarListItem] = []
private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:] private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:]
@ -694,6 +707,10 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.setByYouNode.alpha = 0.0 self.setByYouNode.alpha = 0.0
self.setByYouNode.isUserInteractionEnabled = false self.setByYouNode.isUserInteractionEnabled = false
self.setByYouImageNode = ImageNode()
self.setByYouImageNode.alpha = 0.0
self.setByYouImageNode.isUserInteractionEnabled = false
self.controlsContainerNode = ASDisplayNode() self.controlsContainerNode = ASDisplayNode()
self.controlsContainerNode.isUserInteractionEnabled = false self.controlsContainerNode.isUserInteractionEnabled = false
@ -764,6 +781,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.controlsClippingNode.addSubnode(self.controlsContainerNode) self.controlsClippingNode.addSubnode(self.controlsContainerNode)
self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode) self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode)
self.stripContainerNode.addSubnode(self.setByYouNode) self.stripContainerNode.addSubnode(self.setByYouNode)
self.stripContainerNode.addSubnode(self.setByYouImageNode)
self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -830,6 +848,19 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
self.positionDisposable.dispose() self.positionDisposable.dispose()
} }
public override func didLoad() {
super.didLoad()
let setByYouTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.setByYouTapped))
self.setByYouNode.isUserInteractionEnabled = true
self.setByYouNode.view.addGestureRecognizer(setByYouTapRecognizer)
self.setByYouTapRecognizer = setByYouTapRecognizer
}
@objc private func setByYouTapped() {
self.selectLastItem()
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return super.hitTest(point, with: event) return super.hitTest(point, with: event)
} }
@ -845,6 +876,17 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
} }
public func selectLastItem() {
let previousIndex = self.currentIndex
self.currentIndex = self.items.count - 1
if self.currentIndex != previousIndex {
self.currentIndexUpdated?()
}
if let size = self.validLayout {
self.updateItems(size: size, transition: .immediate, stripTransition: .animated(duration: 0.3, curve: .spring))
}
}
public func updateEntryIsHidden(entry: AvatarGalleryEntry?) { public func updateEntryIsHidden(entry: AvatarGalleryEntry?) {
if let entry = entry, let index = self.galleryEntries.firstIndex(of: entry) { if let entry = entry, let index = self.galleryEntries.firstIndex(of: entry) {
self.currentItemNode?.isHidden = index == self.currentIndex self.currentItemNode?.isHidden = index == self.currentIndex
@ -956,7 +998,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
func setMainItem(_ item: PeerInfoAvatarListItem) { func setMainItem(_ item: PeerInfoAvatarListItem) {
guard case let .image(imageReference, _, _, _) = item else { guard case let .image(imageReference, _, _, _, _) = item else {
return return
} }
var items: [PeerInfoAvatarListItem] = [] var items: [PeerInfoAvatarListItem] = []
@ -966,16 +1008,16 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _):
entries.append(entry) entries.append(entry)
items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData))
case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _, isFallback):
if representations.isEmpty { if representations.isEmpty {
continue continue
} }
if imageReference == reference { if imageReference == reference {
entries.insert(entry, at: 0) entries.insert(entry, at: 0)
items.insert(.image(reference, representations, videoRepresentations, immediateThumbnailData), at: 0) items.insert(.image(reference, representations, videoRepresentations, immediateThumbnailData, isFallback), at: 0)
} else { } else {
entries.append(entry) entries.append(entry)
items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData)) items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData, isFallback))
} }
} }
} }
@ -994,7 +1036,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
public func deleteItem(_ item: PeerInfoAvatarListItem) -> Bool { public func deleteItem(_ item: PeerInfoAvatarListItem) -> Bool {
guard case let .image(imageReference, _, _, _) = item else { guard case let .image(imageReference, _, _, _, _) = item else {
return false return false
} }
@ -1009,13 +1051,13 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _):
entries.append(entry) entries.append(entry)
items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData))
case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _, isFallback):
if representations.isEmpty { if representations.isEmpty {
continue continue
} }
if imageReference != reference { if imageReference != reference {
entries.append(entry) entries.append(entry)
items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData)) items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData, isFallback))
} else { } else {
deletedIndex = index deletedIndex = index
} }
@ -1081,8 +1123,8 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
} }
var synchronous = false var synchronous = false
if !strongSelf.galleryEntries.isEmpty, let updated = entries.first, case let .image(mediaId, reference, _, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) = updated, !videoRepresentations.isEmpty, let previous = strongSelf.galleryEntries.first, case let .topImage(representations, _, _, _, _, _) = previous { if !strongSelf.galleryEntries.isEmpty, let updated = entries.first, case let .image(mediaId, reference, _, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption, _) = updated, !videoRepresentations.isEmpty, let previous = strongSelf.galleryEntries.first, case let .topImage(representations, _, _, _, _, _) = previous {
let firstEntry = AvatarGalleryEntry.image(mediaId, reference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) let firstEntry = AvatarGalleryEntry.image(mediaId, reference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption, false)
entries.remove(at: 0) entries.remove(at: 0)
entries.insert(firstEntry, at: 0) entries.insert(firstEntry, at: 0)
synchronous = true synchronous = true
@ -1264,13 +1306,39 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode {
if !self.items.isEmpty, self.currentIndex >= 0 && self.currentIndex < self.items.count { if !self.items.isEmpty, self.currentIndex >= 0 && self.currentIndex < self.items.count {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
let currentItem = self.items[self.currentIndex] let currentItem = self.items[self.currentIndex]
var photoTitle: String?
var hasLink = false
var fallbackImageSignal: Signal<UIImage?, NoError>?
if let representation = currentItem.representations.first?.representation, representation.isPersonal { if let representation = currentItem.representations.first?.representation, representation.isPersonal {
photoTitle = representation.hasVideo ? presentationData.strings.UserInfo_CustomVideo : presentationData.strings.UserInfo_CustomPhoto
} else if currentItem.isFallback, let representation = currentItem.representations.first?.representation {
photoTitle = representation.hasVideo ? presentationData.strings.UserInfo_PublicVideo : presentationData.strings.UserInfo_PublicPhoto
} else if self.currentIndex == 0, let lastItem = self.items.last, lastItem.isFallback, let representation = lastItem.representations.first?.representation {
photoTitle = representation.hasVideo ? presentationData.strings.UserInfo_PublicVideo : presentationData.strings.UserInfo_PublicPhoto
hasLink = true
if let peer = self.peer {
fallbackImageSignal = peerAvatarCompleteImage(account: self.context.account, peer: EnginePeer(peer), forceProvidedRepresentation: true, representation: representation, size: CGSize(width: 28.0, height: 28.0))
}
}
if let photoTitle = photoTitle {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.7) transition.updateAlpha(node: self.setByYouNode, alpha: 0.7)
self.setByYouNode.attributedText = NSAttributedString(string: representation.hasVideo ? presentationData.strings.UserInfo_CustomVideo : presentationData.strings.UserInfo_CustomPhoto, font: Font.regular(12.0), textColor: UIColor.white) self.setByYouNode.attributedText = NSAttributedString(string: photoTitle, font: Font.regular(12.0), textColor: UIColor.white)
let setByYouSize = self.setByYouNode.updateLayout(size) let setByYouSize = self.setByYouNode.updateLayout(size)
self.setByYouNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - setByYouSize.width) / 2.0), y: 17.0), size: setByYouSize) self.setByYouNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - setByYouSize.width) / 2.0), y: 17.0), size: setByYouSize)
self.setByYouNode.isUserInteractionEnabled = hasLink
} else { } else {
transition.updateAlpha(node: self.setByYouNode, alpha: 0.0) transition.updateAlpha(node: self.setByYouNode, alpha: 0.0)
self.setByYouNode.isUserInteractionEnabled = false
}
if let fallbackImageSignal = fallbackImageSignal {
self.setByYouImageNode.setSignal(fallbackImageSignal)
transition.updateAlpha(node: self.setByYouImageNode, alpha: 1.0)
self.setByYouImageNode.frame = CGRect(origin: CGPoint(x: self.setByYouNode.frame.minX - 32.0, y: 11.0), size: CGSize(width: 28.0, height: 28.0))
} else {
transition.updateAlpha(node: self.setByYouImageNode, alpha: 0.0)
} }
} }

View File

@ -82,6 +82,7 @@ public final class QrCodeScanScreen: ViewController {
public enum Subject { public enum Subject {
case authTransfer(activeSessionsContext: ActiveSessionsContext) case authTransfer(activeSessionsContext: ActiveSessionsContext)
case peer case peer
case custom(info: String)
} }
private let context: AccountContext private let context: AccountContext
@ -97,9 +98,12 @@ public final class QrCodeScanScreen: ViewController {
} }
public var showMyCode: () -> Void = {} public var showMyCode: () -> Void = {}
public var completion: (String?) -> Void = { _ in }
private var codeResolved = false private var codeResolved = false
private var validLayout: ContainerViewLayout?
public init(context: AccountContext, subject: QrCodeScanScreen.Subject) { public init(context: AccountContext, subject: QrCodeScanScreen.Subject) {
self.context = context self.context = context
self.subject = subject self.subject = subject
@ -126,7 +130,9 @@ public final class QrCodeScanScreen: ViewController {
(strongSelf.displayNode as! QrCodeScanScreenNode).updateInForeground(inForeground) (strongSelf.displayNode as! QrCodeScanScreenNode).updateInForeground(inForeground)
}) })
if case .peer = subject { if case .custom = subject {
self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Cancel, style: .plain, target: self, action: #selector(self.cancelPressed))
} else if case .peer = subject {
self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Contacts_QrCode_MyCode, style: .plain, target: self, action: #selector(self.myCodePressed)) self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Contacts_QrCode_MyCode, style: .plain, target: self, action: #selector(self.myCodePressed))
} else { } else {
#if DEBUG #if DEBUG
@ -145,6 +151,16 @@ public final class QrCodeScanScreen: ViewController {
self.approveDisposable.dispose() self.approveDisposable.dispose()
} }
@objc private func cancelPressed() {
guard let layout = self.validLayout else {
return
}
self.completion(nil)
self.controllerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: layout.size.height), duration: 0.2, removeOnCompletion: false, additive: true, completion: { _ in
self.dismiss()
})
}
@objc private func myCodePressed() { @objc private func myCodePressed() {
self.showMyCode() self.showMyCode()
} }
@ -153,6 +169,16 @@ public final class QrCodeScanScreen: ViewController {
self.dismissWithSession(session: nil) self.dismissWithSession(session: nil)
} }
private var animatedIn = false
public override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if case .custom = self.subject, !self.animatedIn, let layout = self.validLayout {
self.animatedIn = true
self.controllerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: layout.size.height), to: CGPoint(), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true)
}
}
private func dismissWithSession(session: RecentAccountSession?) { private func dismissWithSession(session: RecentAccountSession?) {
guard case let .authTransfer(activeSessionsContext) = self.subject else { guard case let .authTransfer(activeSessionsContext) = self.subject else {
return return
@ -184,6 +210,14 @@ public final class QrCodeScanScreen: ViewController {
} }
} }
private func completeWithCode(_ code: String) {
guard case .custom = self.subject else {
return
}
self.completion(code)
self.dismiss()
}
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = QrCodeScanScreenNode(context: self.context, presentationData: self.presentationData, controller: self, subject: self.subject) self.displayNode = QrCodeScanScreenNode(context: self.context, presentationData: self.presentationData, controller: self, subject: self.subject)
@ -235,6 +269,8 @@ public final class QrCodeScanScreen: ViewController {
} }
}) })
} }
case .custom:
strongSelf.completeWithCode(code)
} }
}) })
@ -246,6 +282,8 @@ public final class QrCodeScanScreen: ViewController {
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
super.containerLayoutUpdated(layout, transition: transition) super.containerLayoutUpdated(layout, transition: transition)
self.validLayout = layout
(self.displayNode as! QrCodeScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition) (self.displayNode as! QrCodeScanScreenNode).containerLayoutUpdated(layout: layout, navigationHeight: self.navigationLayout(layout: layout).navigationFrame.maxY, transition: transition)
} }
} }
@ -344,6 +382,9 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
case .peer: case .peer:
title = "" title = ""
text = "" text = ""
case let .custom(info):
title = presentationData.strings.AuthSessions_AddDevice_ScanTitle
text = info
} }
self.titleNode = ImmediateTextNode() self.titleNode = ImmediateTextNode()
@ -445,6 +486,8 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie
filteredCodes = codes.filter { $0.message.hasPrefix("tg://") } filteredCodes = codes.filter { $0.message.hasPrefix("tg://") }
case .peer: case .peer:
filteredCodes = codes.filter { $0.message.hasPrefix("https://t.me/") || $0.message.hasPrefix("t.me/") } filteredCodes = codes.filter { $0.message.hasPrefix("https://t.me/") || $0.message.hasPrefix("t.me/") }
case .custom:
filteredCodes = codes
} }
if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) { if let code = filteredCodes.first, CGRect(x: 0.3, y: 0.3, width: 0.4, height: 0.4).contains(code.boundingBox.center) {
if strongSelf.codeWithError != code.message { if strongSelf.codeWithError != code.message {

View File

@ -614,7 +614,22 @@ class PrivacyAndSecurityControllerImpl: ItemListController, ASAuthorizationContr
} }
} }
public func privacyAndSecurityController(context: AccountContext, initialSettings: AccountPrivacySettings? = nil, updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil, updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil, updatedHasTwoStepAuth: ((Bool) -> Void)? = nil, focusOnItemTag: PrivacyAndSecurityEntryTag? = nil, activeSessionsContext: ActiveSessionsContext? = nil, webSessionsContext: WebSessionsContext? = nil, blockedPeersContext: BlockedPeersContext? = nil, hasTwoStepAuth: Bool? = nil, loginEmailPattern: Signal<String?, NoError>? = nil, updatedTwoStepAuthData: (() -> Void)? = nil) -> ViewController { public func privacyAndSecurityController(
context: AccountContext,
initialSettings: AccountPrivacySettings? = nil,
updatedSettings: ((AccountPrivacySettings?) -> Void)? = nil,
updatedBlockedPeers: ((BlockedPeersContext?) -> Void)? = nil,
updatedHasTwoStepAuth: ((Bool) -> Void)? = nil,
focusOnItemTag: PrivacyAndSecurityEntryTag? = nil,
activeSessionsContext: ActiveSessionsContext? = nil,
webSessionsContext: WebSessionsContext? = nil,
blockedPeersContext: BlockedPeersContext? = nil,
hasTwoStepAuth: Bool? = nil,
loginEmailPattern: Signal<String?, NoError>? = nil,
updatedTwoStepAuthData: (() -> Void)? = nil,
requestPublicPhotoSetup: (() -> Void)? = nil,
requestPublicPhotoRemove: (() -> Void)? = nil
) -> ViewController {
let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true) let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true)
let stateValue = Atomic(value: PrivacyAndSecurityControllerState()) let stateValue = Atomic(value: PrivacyAndSecurityControllerState())
let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in
@ -804,7 +819,11 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting
|> deliverOnMainQueue |> deliverOnMainQueue
currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in
if let info = info { if let info = info {
pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .profilePhoto, current: info.profilePhoto, updated: { updated, _, _ in pushControllerImpl?(selectivePrivacySettingsController(context: context, kind: .profilePhoto, current: info.profilePhoto, requestPublicPhotoSetup: {
requestPublicPhotoSetup?()
}, requestPublicPhotoRemove: {
requestPublicPhotoRemove?()
}, updated: { updated, _, _ in
if let currentInfoDisposable = currentInfoDisposable { if let currentInfoDisposable = currentInfoDisposable {
let applySetting: Signal<Void, NoError> = privacySettingsPromise.get() let applySetting: Signal<Void, NoError> = privacySettingsPromise.get()
|> filter { $0 != nil } |> filter { $0 != nil }

View File

@ -10,6 +10,7 @@ import ItemListUI
import PresentationDataUtils import PresentationDataUtils
import AccountContext import AccountContext
import UndoUI import UndoUI
import ItemListPeerActionItem
enum SelectivePrivacySettingsKind { enum SelectivePrivacySettingsKind {
case presence case presence
@ -54,7 +55,10 @@ private final class SelectivePrivacySettingsControllerArguments {
let updatePhoneDiscovery: ((Bool) -> Void)? let updatePhoneDiscovery: ((Bool) -> Void)?
let copyPhoneLink: ((String) -> Void)? let copyPhoneLink: ((String) -> Void)?
init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?, updatePhoneDiscovery: ((Bool) -> Void)?, copyPhoneLink: ((String) -> Void)?) { let setPublicPhoto: (() -> Void)?
let removePublicPhoto: (() -> Void)?
init(context: AccountContext, updateType: @escaping (SelectivePrivacySettingType) -> Void, openSelective: @escaping (SelectivePrivacySettingsPeerTarget, Bool) -> Void, updateCallP2PMode: ((SelectivePrivacySettingType) -> Void)?, updateCallIntegrationEnabled: ((Bool) -> Void)?, updatePhoneDiscovery: ((Bool) -> Void)?, copyPhoneLink: ((String) -> Void)?, setPublicPhoto: (() -> Void)?, removePublicPhoto: (() -> Void)?) {
self.context = context self.context = context
self.updateType = updateType self.updateType = updateType
self.openSelective = openSelective self.openSelective = openSelective
@ -63,12 +67,16 @@ private final class SelectivePrivacySettingsControllerArguments {
self.updateCallIntegrationEnabled = updateCallIntegrationEnabled self.updateCallIntegrationEnabled = updateCallIntegrationEnabled
self.updatePhoneDiscovery = updatePhoneDiscovery self.updatePhoneDiscovery = updatePhoneDiscovery
self.copyPhoneLink = copyPhoneLink self.copyPhoneLink = copyPhoneLink
self.setPublicPhoto = setPublicPhoto
self.removePublicPhoto = removePublicPhoto
} }
} }
private enum SelectivePrivacySettingsSection: Int32 { private enum SelectivePrivacySettingsSection: Int32 {
case forwards case forwards
case setting case setting
case photo
case peers case peers
case callsP2P case callsP2P
case callsP2PPeers case callsP2PPeers
@ -96,6 +104,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
case contacts(PresentationTheme, String, Bool) case contacts(PresentationTheme, String, Bool)
case nobody(PresentationTheme, String, Bool) case nobody(PresentationTheme, String, Bool)
case settingInfo(PresentationTheme, String, String) case settingInfo(PresentationTheme, String, String)
case setPublicPhoto(PresentationTheme, String)
case removePublicPhoto(PresentationTheme, String, EnginePeer, TelegramMediaImage?)
case publicPhotoInfo(PresentationTheme, String)
case exceptionsHeader(PresentationTheme, String) case exceptionsHeader(PresentationTheme, String)
case disableFor(PresentationTheme, String, String) case disableFor(PresentationTheme, String, String)
case enableFor(PresentationTheme, String, String) case enableFor(PresentationTheme, String, String)
@ -121,6 +132,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return SelectivePrivacySettingsSection.forwards.rawValue return SelectivePrivacySettingsSection.forwards.rawValue
case .settingHeader, .everybody, .contacts, .nobody, .settingInfo: case .settingHeader, .everybody, .contacts, .nobody, .settingInfo:
return SelectivePrivacySettingsSection.setting.rawValue return SelectivePrivacySettingsSection.setting.rawValue
case .setPublicPhoto, .removePublicPhoto, .publicPhotoInfo:
return SelectivePrivacySettingsSection.photo.rawValue
case .exceptionsHeader, .disableFor, .enableFor, .peersInfo: case .exceptionsHeader, .disableFor, .enableFor, .peersInfo:
return SelectivePrivacySettingsSection.peers.rawValue return SelectivePrivacySettingsSection.peers.rawValue
case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo: case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo:
@ -150,42 +163,48 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return 5 return 5
case .settingInfo: case .settingInfo:
return 6 return 6
case .phoneDiscoveryHeader: case .setPublicPhoto:
return 7 return 7
case .phoneDiscoveryEverybody: case .removePublicPhoto:
return 8 return 8
case .phoneDiscoveryMyContacts: case .publicPhotoInfo:
return 9 return 9
case .phoneDiscoveryInfo: case .phoneDiscoveryHeader:
return 10 return 10
case .exceptionsHeader: case .phoneDiscoveryEverybody:
return 11 return 11
case .disableFor: case .phoneDiscoveryMyContacts:
return 12 return 12
case .enableFor: case .phoneDiscoveryInfo:
return 13 return 13
case .peersInfo: case .exceptionsHeader:
return 14 return 14
case .callsP2PHeader: case .disableFor:
return 15 return 15
case .callsP2PAlways: case .enableFor:
return 16 return 16
case .callsP2PContacts: case .peersInfo:
return 17 return 17
case .callsP2PNever: case .callsP2PHeader:
return 18 return 18
case .callsP2PInfo: case .callsP2PAlways:
return 19 return 19
case .callsP2PDisableFor: case .callsP2PContacts:
return 20 return 20
case .callsP2PEnableFor: case .callsP2PNever:
return 21 return 21
case .callsP2PPeersInfo: case .callsP2PInfo:
return 22 return 22
case .callsIntegrationEnabled: case .callsP2PDisableFor:
return 23 return 23
case .callsIntegrationInfo: case .callsP2PEnableFor:
return 24 return 24
case .callsP2PPeersInfo:
return 25
case .callsIntegrationEnabled:
return 26
case .callsIntegrationInfo:
return 27
} }
} }
@ -222,7 +241,25 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return false return false
} }
case let .nobody(lhsTheme, lhsText, lhsValue): case let .nobody(lhsTheme, lhsText, lhsValue):
if case let nobody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { if case let .nobody(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue {
return true
} else {
return false
}
case let .setPublicPhoto(lhsTheme, lhsText):
if case let .setPublicPhoto(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true
} else {
return false
}
case let .removePublicPhoto(lhsTheme, lhsText, lhsPeer, lhsRep):
if case let .removePublicPhoto(rhsTheme, rhsText, rhsPeer, rhsRep) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsPeer == rhsPeer, lhsRep == rhsRep {
return true
} else {
return false
}
case let .publicPhotoInfo(lhsTheme, lhsText):
if case let .publicPhotoInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText {
return true return true
} else { } else {
return false return false
@ -373,6 +410,17 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry {
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
arguments.copyPhoneLink?(link) arguments.copyPhoneLink?(link)
}) })
case let .setPublicPhoto(theme, text):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.addPhotoIcon(theme), title: text, sectionId: self.section, height: .generic, color: .accent, editing: false, action: {
arguments.setPublicPhoto?()
})
case let .removePublicPhoto(theme, text, _, _):
return ItemListPeerActionItem(presentationData: presentationData, icon: PresentationResourcesItemList.deleteIconImage(theme), title: text, sectionId: self.section, height: .generic, color: .destructive, editing: false, action: {
arguments.removePublicPhoto?()
})
case let .publicPhotoInfo(_, text):
return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in
})
case let .exceptionsHeader(_, text): case let .exceptionsHeader(_, text):
return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section)
case let .disableFor(_, title, value): case let .disableFor(_, title, value):
@ -539,7 +587,7 @@ private struct SelectivePrivacySettingsControllerState: Equatable {
} }
} }
private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String) -> [SelectivePrivacySettingsEntry] { private func selectivePrivacySettingsControllerEntries(presentationData: PresentationData, kind: SelectivePrivacySettingsKind, state: SelectivePrivacySettingsControllerState, peerName: String, phoneNumber: String, peer: EnginePeer?, publicPhoto: TelegramMediaImage?) -> [SelectivePrivacySettingsEntry] {
var entries: [SelectivePrivacySettingsEntry] = [] var entries: [SelectivePrivacySettingsEntry] = []
let settingTitle: String let settingTitle: String
@ -628,6 +676,16 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink)) entries.append(.phoneDiscoveryInfo(presentationData.theme, state.phoneDiscoveryEnabled != false ? presentationData.strings.PrivacyPhoneNumberSettings_CustomPublicLink("+\(phoneNumber)").string : presentationData.strings.PrivacyPhoneNumberSettings_CustomDisabledHelp, phoneLink))
} }
if case .profilePhoto = kind, let peer = peer {
if let publicPhoto = publicPhoto {
entries.append(.setPublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_UpdatePublicPhoto))
entries.append(.removePublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_RemovePublicPhoto, peer, publicPhoto))
} else {
entries.append(.setPublicPhoto(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_SetPublicPhoto))
}
entries.append(.publicPhotoInfo(presentationData.theme, presentationData.strings.Privacy_ProfilePhoto_PublicPhotoInfo))
}
entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions)) entries.append(.exceptionsHeader(presentationData.theme, presentationData.strings.GroupInfo_Permissions_Exceptions))
switch state.setting { switch state.setting {
@ -671,7 +729,18 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present
return entries return entries
} }
func selectivePrivacySettingsController(context: AccountContext, kind: SelectivePrivacySettingsKind, current: SelectivePrivacySettings, callSettings: (SelectivePrivacySettings, VoiceCallSettings)? = nil, phoneDiscoveryEnabled: Bool? = nil, voipConfiguration: VoipConfiguration? = nil, callIntegrationAvailable: Bool? = nil, updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?) -> Void) -> ViewController { func selectivePrivacySettingsController(
context: AccountContext,
kind: SelectivePrivacySettingsKind,
current: SelectivePrivacySettings,
callSettings: (SelectivePrivacySettings, VoiceCallSettings)? = nil,
phoneDiscoveryEnabled: Bool? = nil,
voipConfiguration: VoipConfiguration? = nil,
callIntegrationAvailable: Bool? = nil,
requestPublicPhotoSetup: (() -> Void)? = nil,
requestPublicPhotoRemove: (() -> Void)? = nil,
updated: @escaping (SelectivePrivacySettings, (SelectivePrivacySettings, VoiceCallSettings)?, Bool?) -> Void
) -> ViewController {
let strings = context.sharedContext.currentPresentationData.with { $0 }.strings let strings = context.sharedContext.currentPresentationData.with { $0 }.strings
var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:] var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:]
@ -965,12 +1034,29 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil) presentControllerImpl?(UndoOverlayController(presentationData: presentationData, content: .linkCopied(text: strings.Conversation_LinkCopied), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), nil)
}, setPublicPhoto: {
requestPublicPhotoSetup?()
}, removePublicPhoto: {
requestPublicPhotoRemove?()
}) })
let peer = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId)) let peer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
let publicPhoto = context.account.postbox.peerView(id: context.account.peerId)
|> map { view -> TelegramMediaImage? in
if let cachedUserData = view.cachedData as? CachedUserData, case let .known(photo) = cachedUserData.fallbackPhoto {
return photo
} else {
return nil
}
}
let signal = combineLatest(context.sharedContext.presentationData, statePromise.get(), peer) |> deliverOnMainQueue let signal = combineLatest(
|> map { presentationData, state, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in context.sharedContext.presentationData,
statePromise.get(),
peer,
publicPhoto
) |> deliverOnMainQueue
|> map { presentationData, state, peer, publicPhoto -> (ItemListControllerState, (ItemListNodeState, Any)) in
let peerName = peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) let peerName = peer?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)
var phoneNumber = "" var phoneNumber = ""
if case let .user(user) = peer { if case let .user(user) = peer {
@ -995,7 +1081,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective
title = presentationData.strings.Privacy_VoiceMessages title = presentationData.strings.Privacy_VoiceMessages
} }
let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false)
let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName ?? "", phoneNumber: phoneNumber), style: .blocks, animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: selectivePrivacySettingsControllerEntries(presentationData: presentationData, kind: kind, state: state, peerName: peerName ?? "", phoneNumber: phoneNumber, peer: peer, publicPhoto: publicPhoto), style: .blocks, animateChanges: false)
return (controllerState, (listState, arguments)) return (controllerState, (listState, arguments))
} |> afterDisposed { } |> afterDisposed {

View File

@ -427,6 +427,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[940666592] = { return Api.Message.parse_message($0) } dict[940666592] = { return Api.Message.parse_message($0) }
dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($0) }
dict[721967202] = { return Api.Message.parse_messageService($0) } dict[721967202] = { return Api.Message.parse_messageService($0) }
dict[-404267113] = { return Api.MessageAction.parse_messageActionAttachMenuBotAllowed($0) }
dict[-1410748418] = { return Api.MessageAction.parse_messageActionBotAllowed($0) } dict[-1410748418] = { return Api.MessageAction.parse_messageActionBotAllowed($0) }
dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) } dict[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($0) }
dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) } dict[-365344535] = { return Api.MessageAction.parse_messageActionChannelMigrateFrom($0) }
@ -886,7 +887,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = {
dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) } dict[-1831650802] = { return Api.UrlAuthResult.parse_urlAuthResultRequest($0) }
dict[-1885878744] = { return Api.User.parse_user($0) } dict[-1885878744] = { return Api.User.parse_user($0) }
dict[-742634630] = { return Api.User.parse_userEmpty($0) } dict[-742634630] = { return Api.User.parse_userEmpty($0) }
dict[-328384029] = { return Api.UserFull.parse_userFull($0) } dict[-120378643] = { return Api.UserFull.parse_userFull($0) }
dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) } dict[-2100168954] = { return Api.UserProfilePhoto.parse_userProfilePhoto($0) }
dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) }
dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) }

View File

@ -988,6 +988,7 @@ public extension Api {
} }
public extension Api { public extension Api {
enum MessageAction: TypeConstructorDescription { enum MessageAction: TypeConstructorDescription {
case messageActionAttachMenuBotAllowed
case messageActionBotAllowed(domain: String) case messageActionBotAllowed(domain: String)
case messageActionChannelCreate(title: String) case messageActionChannelCreate(title: String)
case messageActionChannelMigrateFrom(title: String, chatId: Int64) case messageActionChannelMigrateFrom(title: String, chatId: Int64)
@ -1027,6 +1028,12 @@ public extension Api {
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .messageActionAttachMenuBotAllowed:
if boxed {
buffer.appendInt32(-404267113)
}
break
case .messageActionBotAllowed(let domain): case .messageActionBotAllowed(let domain):
if boxed { if boxed {
buffer.appendInt32(-1410748418) buffer.appendInt32(-1410748418)
@ -1302,6 +1309,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .messageActionAttachMenuBotAllowed:
return ("messageActionAttachMenuBotAllowed", [])
case .messageActionBotAllowed(let domain): case .messageActionBotAllowed(let domain):
return ("messageActionBotAllowed", [("domain", String(describing: domain))]) return ("messageActionBotAllowed", [("domain", String(describing: domain))])
case .messageActionChannelCreate(let title): case .messageActionChannelCreate(let title):
@ -1377,6 +1386,9 @@ public extension Api {
} }
} }
public static func parse_messageActionAttachMenuBotAllowed(_ reader: BufferReader) -> MessageAction? {
return Api.MessageAction.messageActionAttachMenuBotAllowed
}
public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? { public static func parse_messageActionBotAllowed(_ reader: BufferReader) -> MessageAction? {
var _1: String? var _1: String?
_1 = parseString(reader) _1 = parseString(reader)

View File

@ -586,13 +586,13 @@ public extension Api {
} }
public extension Api { public extension Api {
enum UserFull: TypeConstructorDescription { enum UserFull: TypeConstructorDescription {
case userFull(flags: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?) case userFull(flags: Int32, id: Int64, about: String?, settings: Api.PeerSettings, personalPhoto: Api.Photo?, profilePhoto: Api.Photo?, fallbackPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, botInfo: Api.BotInfo?, pinnedMsgId: Int32?, commonChatsCount: Int32, folderId: Int32?, ttlPeriod: Int32?, themeEmoticon: String?, privateForwardName: String?, botGroupAdminRights: Api.ChatAdminRights?, botBroadcastAdminRights: Api.ChatAdminRights?, premiumGifts: [Api.PremiumGiftOption]?)
public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) {
switch self { switch self {
case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts): case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts):
if boxed { if boxed {
buffer.appendInt32(-328384029) buffer.appendInt32(-120378643)
} }
serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(flags, buffer: buffer, boxed: false)
serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false)
@ -600,6 +600,7 @@ public extension Api {
settings.serialize(buffer, true) settings.serialize(buffer, true)
if Int(flags) & Int(1 << 21) != 0 {personalPhoto!.serialize(buffer, true)} if Int(flags) & Int(1 << 21) != 0 {personalPhoto!.serialize(buffer, true)}
if Int(flags) & Int(1 << 2) != 0 {profilePhoto!.serialize(buffer, true)} if Int(flags) & Int(1 << 2) != 0 {profilePhoto!.serialize(buffer, true)}
if Int(flags) & Int(1 << 22) != 0 {fallbackPhoto!.serialize(buffer, true)}
notifySettings.serialize(buffer, true) notifySettings.serialize(buffer, true)
if Int(flags) & Int(1 << 3) != 0 {botInfo!.serialize(buffer, true)} if Int(flags) & Int(1 << 3) != 0 {botInfo!.serialize(buffer, true)}
if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)}
@ -621,8 +622,8 @@ public extension Api {
public func descriptionFields() -> (String, [(String, Any)]) { public func descriptionFields() -> (String, [(String, Any)]) {
switch self { switch self {
case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts): case .userFull(let flags, let id, let about, let settings, let personalPhoto, let profilePhoto, let fallbackPhoto, let notifySettings, let botInfo, let pinnedMsgId, let commonChatsCount, let folderId, let ttlPeriod, let themeEmoticon, let privateForwardName, let botGroupAdminRights, let botBroadcastAdminRights, let premiumGifts):
return ("userFull", [("flags", String(describing: flags)), ("id", String(describing: id)), ("about", String(describing: about)), ("settings", String(describing: settings)), ("personalPhoto", String(describing: personalPhoto)), ("profilePhoto", String(describing: profilePhoto)), ("notifySettings", String(describing: notifySettings)), ("botInfo", String(describing: botInfo)), ("pinnedMsgId", String(describing: pinnedMsgId)), ("commonChatsCount", String(describing: commonChatsCount)), ("folderId", String(describing: folderId)), ("ttlPeriod", String(describing: ttlPeriod)), ("themeEmoticon", String(describing: themeEmoticon)), ("privateForwardName", String(describing: privateForwardName)), ("botGroupAdminRights", String(describing: botGroupAdminRights)), ("botBroadcastAdminRights", String(describing: botBroadcastAdminRights)), ("premiumGifts", String(describing: premiumGifts))]) return ("userFull", [("flags", String(describing: flags)), ("id", String(describing: id)), ("about", String(describing: about)), ("settings", String(describing: settings)), ("personalPhoto", String(describing: personalPhoto)), ("profilePhoto", String(describing: profilePhoto)), ("fallbackPhoto", String(describing: fallbackPhoto)), ("notifySettings", String(describing: notifySettings)), ("botInfo", String(describing: botInfo)), ("pinnedMsgId", String(describing: pinnedMsgId)), ("commonChatsCount", String(describing: commonChatsCount)), ("folderId", String(describing: folderId)), ("ttlPeriod", String(describing: ttlPeriod)), ("themeEmoticon", String(describing: themeEmoticon)), ("privateForwardName", String(describing: privateForwardName)), ("botGroupAdminRights", String(describing: botGroupAdminRights)), ("botBroadcastAdminRights", String(describing: botBroadcastAdminRights)), ("premiumGifts", String(describing: premiumGifts))])
} }
} }
@ -645,37 +646,41 @@ public extension Api {
if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() {
_6 = Api.parse(reader, signature: signature) as? Api.Photo _6 = Api.parse(reader, signature: signature) as? Api.Photo
} } } }
var _7: Api.PeerNotifySettings? var _7: Api.Photo?
if Int(_1!) & Int(1 << 22) != 0 {if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.Photo
} }
var _8: Api.PeerNotifySettings?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
_7 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings _8 = Api.parse(reader, signature: signature) as? Api.PeerNotifySettings
} }
var _8: Api.BotInfo? var _9: Api.BotInfo?
if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 3) != 0 {if let signature = reader.readInt32() {
_8 = Api.parse(reader, signature: signature) as? Api.BotInfo _9 = Api.parse(reader, signature: signature) as? Api.BotInfo
} } } }
var _9: Int32?
if Int(_1!) & Int(1 << 6) != 0 {_9 = reader.readInt32() }
var _10: Int32? var _10: Int32?
_10 = reader.readInt32() if Int(_1!) & Int(1 << 6) != 0 {_10 = reader.readInt32() }
var _11: Int32? var _11: Int32?
if Int(_1!) & Int(1 << 11) != 0 {_11 = reader.readInt32() } _11 = reader.readInt32()
var _12: Int32? var _12: Int32?
if Int(_1!) & Int(1 << 14) != 0 {_12 = reader.readInt32() } if Int(_1!) & Int(1 << 11) != 0 {_12 = reader.readInt32() }
var _13: String? var _13: Int32?
if Int(_1!) & Int(1 << 15) != 0 {_13 = parseString(reader) } if Int(_1!) & Int(1 << 14) != 0 {_13 = reader.readInt32() }
var _14: String? var _14: String?
if Int(_1!) & Int(1 << 16) != 0 {_14 = parseString(reader) } if Int(_1!) & Int(1 << 15) != 0 {_14 = parseString(reader) }
var _15: Api.ChatAdminRights? var _15: String?
if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) }
_15 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights
} }
var _16: Api.ChatAdminRights? var _16: Api.ChatAdminRights?
if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() { if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() {
_16 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights _16 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights
} } } }
var _17: [Api.PremiumGiftOption]? var _17: Api.ChatAdminRights?
if Int(_1!) & Int(1 << 18) != 0 {if let signature = reader.readInt32() {
_17 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights
} }
var _18: [Api.PremiumGiftOption]?
if Int(_1!) & Int(1 << 19) != 0 {if let _ = reader.readInt32() { if Int(_1!) & Int(1 << 19) != 0 {if let _ = reader.readInt32() {
_17 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftOption.self) _18 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PremiumGiftOption.self)
} } } }
let _c1 = _1 != nil let _c1 = _1 != nil
let _c2 = _2 != nil let _c2 = _2 != nil
@ -683,19 +688,20 @@ public extension Api {
let _c4 = _4 != nil let _c4 = _4 != nil
let _c5 = (Int(_1!) & Int(1 << 21) == 0) || _5 != nil let _c5 = (Int(_1!) & Int(1 << 21) == 0) || _5 != nil
let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil
let _c7 = _7 != nil let _c7 = (Int(_1!) & Int(1 << 22) == 0) || _7 != nil
let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil let _c8 = _8 != nil
let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil
let _c10 = _10 != nil let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil
let _c11 = (Int(_1!) & Int(1 << 11) == 0) || _11 != nil let _c11 = _11 != nil
let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil let _c12 = (Int(_1!) & Int(1 << 11) == 0) || _12 != nil
let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil let _c13 = (Int(_1!) & Int(1 << 14) == 0) || _13 != nil
let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil
let _c15 = (Int(_1!) & Int(1 << 17) == 0) || _15 != nil let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil
let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil
let _c17 = (Int(_1!) & Int(1 << 19) == 0) || _17 != nil let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil
if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { let _c18 = (Int(_1!) & Int(1 << 19) == 0) || _18 != nil
return Api.UserFull.userFull(flags: _1!, id: _2!, about: _3, settings: _4!, personalPhoto: _5, profilePhoto: _6, notifySettings: _7!, botInfo: _8, pinnedMsgId: _9, commonChatsCount: _10!, folderId: _11, ttlPeriod: _12, themeEmoticon: _13, privateForwardName: _14, botGroupAdminRights: _15, botBroadcastAdminRights: _16, premiumGifts: _17) if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 {
return Api.UserFull.userFull(flags: _1!, id: _2!, about: _3, settings: _4!, personalPhoto: _5, profilePhoto: _6, fallbackPhoto: _7, notifySettings: _8!, botInfo: _9, pinnedMsgId: _10, commonChatsCount: _11!, folderId: _12, ttlPeriod: _13, themeEmoticon: _14, privateForwardName: _15, botGroupAdminRights: _16, botBroadcastAdminRights: _17, premiumGifts: _18)
} }
else { else {
return nil return nil

View File

@ -6559,12 +6559,13 @@ public extension Api.functions.messages {
} }
} }
public extension Api.functions.messages { public extension Api.functions.messages {
static func toggleBotInAttachMenu(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) { static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.Bool>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(451818415) buffer.appendInt32(1777704297)
serializeInt32(flags, buffer: buffer, boxed: false)
bot.serialize(buffer, true) bot.serialize(buffer, true)
enabled.serialize(buffer, true) enabled.serialize(buffer, true)
return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("bot", String(describing: bot)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in return (FunctionDescription(name: "messages.toggleBotInAttachMenu", parameters: [("flags", String(describing: flags)), ("bot", String(describing: bot)), ("enabled", String(describing: enabled))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.Bool? var result: Api.Bool?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {
@ -7548,11 +7549,12 @@ public extension Api.functions.photos {
} }
} }
public extension Api.functions.photos { public extension Api.functions.photos {
static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) { static func updateProfilePhoto(flags: Int32, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse<Api.photos.Photo>) {
let buffer = Buffer() let buffer = Buffer()
buffer.appendInt32(1926525996) buffer.appendInt32(473782614)
serializeInt32(flags, buffer: buffer, boxed: false)
id.serialize(buffer, true) id.serialize(buffer, true)
return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in return (FunctionDescription(name: "photos.updateProfilePhoto", parameters: [("flags", String(describing: flags)), ("id", String(describing: id))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.photos.Photo? in
let reader = BufferReader(buffer) let reader = BufferReader(buffer)
var result: Api.photos.Photo? var result: Api.photos.Photo?
if let signature = reader.readInt32() { if let signature = reader.readInt32() {

View File

@ -1254,7 +1254,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else { } else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
} }
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), text: text, action: nil), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(participant.peer), text: text, action: nil, duration: 3), action: { _ in return false })
} }
} else { } else {
if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) { if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) {
@ -1362,7 +1362,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else { } else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
} }
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil, duration: 3), action: { _ in return false })
} }
})) }))
} else if case let .legacyGroup(groupPeer) = groupPeer { } else if case let .legacyGroup(groupPeer) = groupPeer {
@ -1430,7 +1430,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else { } else {
text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string text = strongSelf.presentationData.strings.VoiceChat_InvitedPeerText(peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
} }
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: text, action: nil, duration: 3), action: { _ in return false })
} }
})) }))
} }
@ -2262,7 +2262,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
return return
} }
let text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(EnginePeer(event.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string let text = strongSelf.presentationData.strings.VoiceChat_PeerJoinedText(EnginePeer(event.peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(event.peer), text: text, action: nil), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(event.peer), text: text, action: nil, duration: 3), action: { _ in return false })
} }
})) }))
@ -2277,7 +2277,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController
} else { } else {
text = strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string text = strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(EnginePeer(peer).displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).string
} }
strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(peer), text: text, action: nil), action: { _ in return false }) strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: EnginePeer(peer), text: text, action: nil, duration: 3), action: { _ in return false })
})) }))
self.stateVersionDisposable.set((self.call.stateVersion self.stateVersionDisposable.set((self.call.stateVersion

View File

@ -76,6 +76,9 @@ extension ReplyMarkupMessageAttribute {
if (markupFlags & (1 << 2)) != 0 { if (markupFlags & (1 << 2)) != 0 {
flags.insert(.personal) flags.insert(.personal)
} }
if (markupFlags & (1 << 4)) != 0 {
flags.insert(.persistent)
}
placeholder = apiPlaceholder placeholder = apiPlaceholder
case let .replyInlineMarkup(apiRows): case let .replyInlineMarkup(apiRows):
rows = apiRows.map { ReplyMarkupRow(apiRow: $0) } rows = apiRows.map { ReplyMarkupRow(apiRow: $0) }

View File

@ -205,7 +205,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] {
} }
switch action { switch action {
case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto: case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionAttachMenuBotAllowed:
break break
case let .messageActionChannelMigrateFrom(_, chatId): case let .messageActionChannelMigrateFrom(_, chatId):
result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId)))

View File

@ -106,6 +106,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe
return TelegramMediaAction(action: .topicEdited(components: components)) return TelegramMediaAction(action: .topicEdited(components: components))
case let.messageActionSuggestProfilePhoto(photo): case let.messageActionSuggestProfilePhoto(photo):
return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo))) return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo)))
case .messageActionAttachMenuBotAllowed:
return TelegramMediaAction(action: .attachMenuBotAllowed)
} }
} }

View File

@ -153,6 +153,7 @@ public final class CachedUserData: CachedPeerData {
public let themeEmoticon: String? public let themeEmoticon: String?
public let photo: CachedPeerProfilePhoto public let photo: CachedPeerProfilePhoto
public let personalPhoto: CachedPeerProfilePhoto public let personalPhoto: CachedPeerProfilePhoto
public let fallbackPhoto: CachedPeerProfilePhoto
public let premiumGiftOptions: [CachedPremiumGiftOption] public let premiumGiftOptions: [CachedPremiumGiftOption]
public let voiceMessagesAvailable: Bool public let voiceMessagesAvailable: Bool
@ -176,13 +177,14 @@ public final class CachedUserData: CachedPeerData {
self.themeEmoticon = nil self.themeEmoticon = nil
self.photo = .unknown self.photo = .unknown
self.personalPhoto = .unknown self.personalPhoto = .unknown
self.fallbackPhoto = .unknown
self.premiumGiftOptions = [] self.premiumGiftOptions = []
self.voiceMessagesAvailable = true self.voiceMessagesAvailable = true
self.peerIds = Set() self.peerIds = Set()
self.messageIds = Set() self.messageIds = Set()
} }
public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool) { public init(about: String?, botInfo: BotInfo?, peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, isBlocked: Bool, commonGroupCount: Int32, voiceCallsAvailable: Bool, videoCallsAvailable: Bool, callsPrivate: Bool, canPinMessages: Bool, hasScheduledMessages: Bool, autoremoveTimeout: CachedPeerAutoremoveTimeout, themeEmoticon: String?, photo: CachedPeerProfilePhoto, personalPhoto: CachedPeerProfilePhoto, fallbackPhoto: CachedPeerProfilePhoto, premiumGiftOptions: [CachedPremiumGiftOption], voiceMessagesAvailable: Bool) {
self.about = about self.about = about
self.botInfo = botInfo self.botInfo = botInfo
self.peerStatusSettings = peerStatusSettings self.peerStatusSettings = peerStatusSettings
@ -198,6 +200,7 @@ public final class CachedUserData: CachedPeerData {
self.themeEmoticon = themeEmoticon self.themeEmoticon = themeEmoticon
self.photo = photo self.photo = photo
self.personalPhoto = personalPhoto self.personalPhoto = personalPhoto
self.fallbackPhoto = fallbackPhoto
self.premiumGiftOptions = premiumGiftOptions self.premiumGiftOptions = premiumGiftOptions
self.voiceMessagesAvailable = voiceMessagesAvailable self.voiceMessagesAvailable = voiceMessagesAvailable
@ -237,6 +240,7 @@ public final class CachedUserData: CachedPeerData {
self.photo = decoder.decodeObjectForKey("phv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown self.photo = decoder.decodeObjectForKey("phv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown
self.personalPhoto = decoder.decodeObjectForKey("pphv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown self.personalPhoto = decoder.decodeObjectForKey("pphv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown
self.fallbackPhoto = decoder.decodeObjectForKey("fphv", decoder: CachedPeerProfilePhoto.init(decoder:)) as? CachedPeerProfilePhoto ?? .unknown
self.premiumGiftOptions = decoder.decodeObjectArrayWithDecoderForKey("pgo") as [CachedPremiumGiftOption] self.premiumGiftOptions = decoder.decodeObjectArrayWithDecoderForKey("pgo") as [CachedPremiumGiftOption]
self.voiceMessagesAvailable = decoder.decodeInt32ForKey("vma", orElse: 0) != 0 self.voiceMessagesAvailable = decoder.decodeInt32ForKey("vma", orElse: 0) != 0
@ -291,6 +295,7 @@ public final class CachedUserData: CachedPeerData {
encoder.encodeObject(self.photo, forKey: "phv") encoder.encodeObject(self.photo, forKey: "phv")
encoder.encodeObject(self.personalPhoto, forKey: "pphv") encoder.encodeObject(self.personalPhoto, forKey: "pphv")
encoder.encodeObject(self.fallbackPhoto, forKey: "fphv")
encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo") encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo")
encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma") encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma")
@ -308,74 +313,78 @@ public final class CachedUserData: CachedPeerData {
return false return false
} }
return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable return other.about == self.about && other.botInfo == self.botInfo && self.peerStatusSettings == other.peerStatusSettings && self.isBlocked == other.isBlocked && self.commonGroupCount == other.commonGroupCount && self.voiceCallsAvailable == other.voiceCallsAvailable && self.videoCallsAvailable == other.videoCallsAvailable && self.callsPrivate == other.callsPrivate && self.hasScheduledMessages == other.hasScheduledMessages && self.autoremoveTimeout == other.autoremoveTimeout && self.themeEmoticon == other.themeEmoticon && self.photo == other.photo && self.personalPhoto == other.personalPhoto && self.fallbackPhoto == other.fallbackPhoto && self.premiumGiftOptions == other.premiumGiftOptions && self.voiceMessagesAvailable == other.voiceMessagesAvailable
} }
public func withUpdatedAbout(_ about: String?) -> CachedUserData { public func withUpdatedAbout(_ about: String?) -> CachedUserData {
return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData { public func withUpdatedBotInfo(_ botInfo: BotInfo?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData { public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData { public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData { public func withUpdatedIsBlocked(_ isBlocked: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData { public func withUpdatedCommonGroupCount(_ commonGroupCount: Int32) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData { public func withUpdatedVoiceCallsAvailable(_ voiceCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData { public func withUpdatedVideoCallsAvailable(_ videoCallsAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData { public func withUpdatedCallsPrivate(_ callsPrivate: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData { public func withUpdatedCanPinMessages(_ canPinMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData { public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData { public func withUpdatedAutoremoveTimeout(_ autoremoveTimeout: CachedPeerAutoremoveTimeout) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData { public func withUpdatedThemeEmoticon(_ themeEmoticon: String?) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData { public func withUpdatedPhoto(_ photo: CachedPeerProfilePhoto) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedPersonalPhoto(_ personalPhoto: CachedPeerProfilePhoto) -> CachedUserData { public func withUpdatedPersonalPhoto(_ personalPhoto: CachedPeerProfilePhoto) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
}
public func withUpdatedFallbackPhoto(_ fallbackPhoto: CachedPeerProfilePhoto) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> CachedUserData { public func withUpdatedPremiumGiftOptions(_ premiumGiftOptions: [CachedPremiumGiftOption]) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: premiumGiftOptions, voiceMessagesAvailable: self.voiceMessagesAvailable)
} }
public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData { public func withUpdatedVoiceMessagesAvailable(_ voiceMessagesAvailable: Bool) -> CachedUserData {
return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable) return CachedUserData(about: self.about, botInfo: self.botInfo, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, isBlocked: self.isBlocked, commonGroupCount: self.commonGroupCount, voiceCallsAvailable: self.voiceCallsAvailable, videoCallsAvailable: self.videoCallsAvailable, callsPrivate: self.callsPrivate, canPinMessages: self.canPinMessages, hasScheduledMessages: self.hasScheduledMessages, autoremoveTimeout: self.autoremoveTimeout, themeEmoticon: self.themeEmoticon, photo: self.photo, personalPhoto: self.personalPhoto, fallbackPhoto: self.fallbackPhoto, premiumGiftOptions: self.premiumGiftOptions, voiceMessagesAvailable: voiceMessagesAvailable)
} }
} }

View File

@ -158,6 +158,7 @@ public struct ReplyMarkupMessageFlags: OptionSet {
public static let setupReply = ReplyMarkupMessageFlags(rawValue: 1 << 2) public static let setupReply = ReplyMarkupMessageFlags(rawValue: 1 << 2)
public static let inline = ReplyMarkupMessageFlags(rawValue: 1 << 3) public static let inline = ReplyMarkupMessageFlags(rawValue: 1 << 3)
public static let fit = ReplyMarkupMessageFlags(rawValue: 1 << 4) public static let fit = ReplyMarkupMessageFlags(rawValue: 1 << 4)
public static let persistent = ReplyMarkupMessageFlags(rawValue: 1 << 5)
} }
public class ReplyMarkupMessageAttribute: MessageAttribute, Equatable { public class ReplyMarkupMessageAttribute: MessageAttribute, Equatable {

View File

@ -99,6 +99,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?) case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?)
case topicEdited(components: [ForumTopicEditComponent]) case topicEdited(components: [ForumTopicEditComponent])
case suggestedProfilePhoto(image: TelegramMediaImage?) case suggestedProfilePhoto(image: TelegramMediaImage?)
case attachMenuBotAllowed
public init(decoder: PostboxDecoder) { public init(decoder: PostboxDecoder) {
let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0)
@ -175,6 +176,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
self = .topicEdited(components: decoder.decodeObjectArrayWithDecoderForKey("components")) self = .topicEdited(components: decoder.decodeObjectArrayWithDecoderForKey("components"))
case 30: case 30:
self = .suggestedProfilePhoto(image: decoder.decodeObjectForKey("image") as? TelegramMediaImage) self = .suggestedProfilePhoto(image: decoder.decodeObjectForKey("image") as? TelegramMediaImage)
case 31:
self = .attachMenuBotAllowed
default: default:
self = .unknown self = .unknown
} }
@ -326,6 +329,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable {
if let image = image { if let image = image {
encoder.encodeObject(image, forKey: "image") encoder.encodeObject(image, forKey: "image")
} }
case .attachMenuBotAllowed:
encoder.encodeInt32(31, forKey: "_rawValue")
} }
} }

View File

@ -44,7 +44,7 @@ public extension TelegramEngine {
} }
public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> { public func updateAccountPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, fallback: false, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
} }
public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> { public func updatePeerPhotoExisting(reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
@ -52,7 +52,15 @@ public extension TelegramEngine {
} }
public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> { public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
return _internal_removeAccountPhoto(network: self.account.network, reference: reference) return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: false)
}
public func updateFallbackPhoto(resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, fallback: true, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
}
public func removeFallbackPhoto(reference: TelegramMediaImageReference?) -> Signal<Void, NoError> {
return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true)
} }
public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal<Never, NoError> { public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal<Never, NoError> {

View File

@ -11,6 +11,7 @@ public final class AttachMenuBots: Equatable, Codable {
case botIcons case botIcons
case peerTypes case peerTypes
case hasSettings case hasSettings
case flags
} }
public enum IconName: Int32, Codable { public enum IconName: Int32, Codable {
@ -38,6 +39,21 @@ public final class AttachMenuBots: Equatable, Codable {
} }
} }
public struct Flags: OptionSet {
public var rawValue: Int32
public init(rawValue: Int32) {
self.rawValue = rawValue
}
public init() {
self.rawValue = 0
}
public static let hasSettings = Flags(rawValue: 1 << 0)
public static let requiresWriteAccess = Flags(rawValue: 1 << 1)
}
public struct PeerFlags: OptionSet, Codable { public struct PeerFlags: OptionSet, Codable {
public var rawValue: UInt32 public var rawValue: UInt32
@ -94,20 +110,20 @@ public final class AttachMenuBots: Equatable, Codable {
public let name: String public let name: String
public let icons: [IconName: TelegramMediaFile] public let icons: [IconName: TelegramMediaFile]
public let peerTypes: PeerFlags public let peerTypes: PeerFlags
public let hasSettings: Bool public let flags: Flags
public init( public init(
peerId: PeerId, peerId: PeerId,
name: String, name: String,
icons: [IconName: TelegramMediaFile], icons: [IconName: TelegramMediaFile],
peerTypes: PeerFlags, peerTypes: PeerFlags,
hasSettings: Bool flags: Flags
) { ) {
self.peerId = peerId self.peerId = peerId
self.name = name self.name = name
self.icons = icons self.icons = icons
self.peerTypes = peerTypes self.peerTypes = peerTypes
self.hasSettings = hasSettings self.flags = flags
} }
public static func ==(lhs: Bot, rhs: Bot) -> Bool { public static func ==(lhs: Bot, rhs: Bot) -> Bool {
@ -123,7 +139,7 @@ public final class AttachMenuBots: Equatable, Codable {
if lhs.peerTypes != rhs.peerTypes { if lhs.peerTypes != rhs.peerTypes {
return false return false
} }
if lhs.hasSettings != rhs.hasSettings { if lhs.flags != rhs.flags {
return false return false
} }
return true return true
@ -147,7 +163,12 @@ public final class AttachMenuBots: Equatable, Codable {
let value = try container.decodeIfPresent(Int32.self, forKey: .peerTypes) ?? Int32(PeerFlags.default.rawValue) let value = try container.decodeIfPresent(Int32.self, forKey: .peerTypes) ?? Int32(PeerFlags.default.rawValue)
self.peerTypes = PeerFlags(rawValue: UInt32(value)) self.peerTypes = PeerFlags(rawValue: UInt32(value))
self.hasSettings = try container.decodeIfPresent(Bool.self, forKey: .hasSettings) ?? false if let flags = try container.decodeIfPresent(Int32.self, forKey: .flags) {
self.flags = Flags(rawValue: flags)
} else {
let hasSettings = try container.decodeIfPresent(Bool.self, forKey: .hasSettings) ?? false
self.flags = hasSettings ? [.hasSettings] : []
}
} }
public func encode(to encoder: Encoder) throws { public func encode(to encoder: Encoder) throws {
@ -164,7 +185,7 @@ public final class AttachMenuBots: Equatable, Codable {
try container.encode(Int32(self.peerTypes.rawValue), forKey: .peerTypes) try container.encode(Int32(self.peerTypes.rawValue), forKey: .peerTypes)
try container.encode(self.hasSettings, forKey: .hasSettings) try container.encode(Int32(self.flags.rawValue), forKey: .flags)
} }
} }
@ -276,7 +297,7 @@ func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network, force:
var resultBots: [AttachMenuBots.Bot] = [] var resultBots: [AttachMenuBots.Bot] = []
for bot in bots { for bot in bots {
switch bot { switch bot {
case let .attachMenuBot(flags, botId, name, apiPeerTypes, botIcons): case let .attachMenuBot(apiFlags, botId, name, apiPeerTypes, botIcons):
var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:] var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:]
for icon in botIcons { for icon in botIcons {
switch icon { switch icon {
@ -302,7 +323,14 @@ func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network, force:
peerTypes.insert(.channel) peerTypes.insert(.channel)
} }
} }
resultBots.append(AttachMenuBots.Bot(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), name: name, icons: icons, peerTypes: peerTypes, hasSettings: (flags & (1 << 1)) != 0)) var flags: AttachMenuBots.Bot.Flags = []
if (apiFlags & (1 << 1)) != 0 {
flags.insert(.hasSettings)
}
if (apiFlags & (1 << 2)) != 0 {
flags.insert(.requiresWriteAccess)
}
resultBots.append(AttachMenuBots.Bot(peerId: PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(botId)), name: name, icons: icons, peerTypes: peerTypes, flags: flags))
} }
} }
} }
@ -340,12 +368,16 @@ public enum AddBotToAttachMenuError {
} }
func _internal_addBotToAttachMenu(postbox: Postbox, network: Network, botId: PeerId) -> Signal<Bool, AddBotToAttachMenuError> { func _internal_addBotToAttachMenu(postbox: Postbox, network: Network, botId: PeerId, allowWrite: Bool) -> Signal<Bool, AddBotToAttachMenuError> {
return postbox.transaction { transaction -> Signal<Bool, AddBotToAttachMenuError> in return postbox.transaction { transaction -> Signal<Bool, AddBotToAttachMenuError> in
guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else { guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else {
return .complete() return .complete()
} }
return network.request(Api.functions.messages.toggleBotInAttachMenu(bot: inputUser, enabled: .boolTrue)) var flags: Int32 = 0
if allowWrite {
flags |= (1 << 0)
}
return network.request(Api.functions.messages.toggleBotInAttachMenu(flags: flags, bot: inputUser, enabled: .boolTrue))
|> map { value -> Bool in |> map { value -> Bool in
switch value { switch value {
case .boolTrue: case .boolTrue:
@ -379,7 +411,7 @@ func _internal_removeBotFromAttachMenu(postbox: Postbox, network: Network, botId
guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else { guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else {
return .complete() return .complete()
} }
return network.request(Api.functions.messages.toggleBotInAttachMenu(bot: inputUser, enabled: .boolFalse)) return network.request(Api.functions.messages.toggleBotInAttachMenu(flags: 0, bot: inputUser, enabled: .boolFalse))
|> map { value -> Bool in |> map { value -> Bool in
switch value { switch value {
case .boolTrue: case .boolTrue:
@ -406,14 +438,14 @@ public struct AttachMenuBot {
public let shortName: String public let shortName: String
public let icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] public let icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile]
public let peerTypes: AttachMenuBots.Bot.PeerFlags public let peerTypes: AttachMenuBots.Bot.PeerFlags
public let hasSettings: Bool public let flags: AttachMenuBots.Bot.Flags
init(peer: Peer, shortName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], peerTypes: AttachMenuBots.Bot.PeerFlags, hasSettings: Bool) { init(peer: Peer, shortName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], peerTypes: AttachMenuBots.Bot.PeerFlags, flags: AttachMenuBots.Bot.Flags) {
self.peer = peer self.peer = peer
self.shortName = shortName self.shortName = shortName
self.icons = icons self.icons = icons
self.peerTypes = peerTypes self.peerTypes = peerTypes
self.hasSettings = hasSettings self.flags = flags
} }
} }
@ -425,7 +457,7 @@ func _internal_attachMenuBots(postbox: Postbox) -> Signal<[AttachMenuBot], NoErr
var resultBots: [AttachMenuBot] = [] var resultBots: [AttachMenuBot] = []
for bot in cachedBots { for bot in cachedBots {
if let peer = transaction.getPeer(bot.peerId) { if let peer = transaction.getPeer(bot.peerId) {
resultBots.append(AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, hasSettings: bot.hasSettings)) resultBots.append(AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags))
} }
} }
return resultBots return resultBots
@ -440,7 +472,7 @@ public func _internal_getAttachMenuBot(postbox: Postbox, network: Network, botId
return postbox.transaction { transaction -> Signal<AttachMenuBot, GetAttachMenuBotError> in return postbox.transaction { transaction -> Signal<AttachMenuBot, GetAttachMenuBotError> in
if cached, let cachedBots = cachedAttachMenuBots(transaction: transaction)?.bots { if cached, let cachedBots = cachedAttachMenuBots(transaction: transaction)?.bots {
if let bot = cachedBots.first(where: { $0.peerId == botId }), let peer = transaction.getPeer(bot.peerId) { if let bot = cachedBots.first(where: { $0.peerId == botId }), let peer = transaction.getPeer(bot.peerId) {
return .single(AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, hasSettings: bot.hasSettings)) return .single(AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags))
} }
} }
@ -474,7 +506,7 @@ public func _internal_getAttachMenuBot(postbox: Postbox, network: Network, botId
} }
switch bot { switch bot {
case let .attachMenuBot(flags, _, name, apiPeerTypes, botIcons): case let .attachMenuBot(apiFlags, _, name, apiPeerTypes, botIcons):
var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:] var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:]
for icon in botIcons { for icon in botIcons {
switch icon { switch icon {
@ -499,7 +531,14 @@ public func _internal_getAttachMenuBot(postbox: Postbox, network: Network, botId
peerTypes.insert(.channel) peerTypes.insert(.channel)
} }
} }
return .single(AttachMenuBot(peer: peer, shortName: name, icons: icons, peerTypes: peerTypes, hasSettings: (flags & (1 << 1)) != 0)) var flags: AttachMenuBots.Bot.Flags = []
if (apiFlags & (1 << 1)) != 0 {
flags.insert(.hasSettings)
}
if (apiFlags & (1 << 2)) != 0 {
flags.insert(.requiresWriteAccess)
}
return .single(AttachMenuBot(peer: peer, shortName: name, icons: icons, peerTypes: peerTypes, flags: flags))
} }
} }
} }

View File

@ -395,8 +395,8 @@ public extension TelegramEngine {
return _internal_sendWebViewData(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, botId: botId, buttonText: buttonText, data: data) return _internal_sendWebViewData(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, botId: botId, buttonText: buttonText, data: data)
} }
public func addBotToAttachMenu(botId: PeerId) -> Signal<Bool, AddBotToAttachMenuError> { public func addBotToAttachMenu(botId: PeerId, allowWrite: Bool) -> Signal<Bool, AddBotToAttachMenuError> {
return _internal_addBotToAttachMenu(postbox: self.account.postbox, network: self.account.network, botId: botId) return _internal_addBotToAttachMenu(postbox: self.account.postbox, network: self.account.network, botId: botId, allowWrite: allowWrite)
} }
public func removeBotFromAttachMenu(botId: PeerId) -> Signal<Bool, NoError> { public func removeBotFromAttachMenu(botId: PeerId) -> Signal<Bool, NoError> {

View File

@ -14,8 +14,8 @@ public enum UploadPeerPhotoError {
case generic case generic
} }
func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> { func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, fallback: Bool, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: mapResourceToAvatarSizes) return _internal_updatePeerPhoto(postbox: account.postbox, network: account.network, stateManager: account.stateManager, accountPeerId: account.peerId, peerId: account.peerId, photo: resource.flatMap({ _internal_uploadedPeerPhoto(postbox: account.postbox, network: account.network, resource: $0) }), video: videoResource.flatMap({ _internal_uploadedPeerVideo(postbox: account.postbox, network: account.network, messageMediaPreuploadManager: account.messageMediaPreuploadManager, resource: $0) |> map(Optional.init) }), videoStartTimestamp: videoStartTimestamp, fallback: fallback, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
} }
public enum SetCustomPeerPhotoMode { public enum SetCustomPeerPhotoMode {
@ -76,11 +76,11 @@ func _internal_uploadedPeerVideo(postbox: Postbox, network: Network, messageMedi
} }
} }
func _internal_updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> { func _internal_updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>? = nil, videoStartTimestamp: Double? = nil, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return _internal_updatePeerPhotoInternal(postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId, peer: postbox.loadedPeerWithId(peerId), photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, customPeerPhotoMode: customPeerPhotoMode, mapResourceToAvatarSizes: mapResourceToAvatarSizes) return _internal_updatePeerPhotoInternal(postbox: postbox, network: network, stateManager: stateManager, accountPeerId: accountPeerId, peer: postbox.loadedPeerWithId(peerId), photo: photo, video: video, videoStartTimestamp: videoStartTimestamp, fallback: fallback, customPeerPhotoMode: customPeerPhotoMode, mapResourceToAvatarSizes: mapResourceToAvatarSizes)
} }
func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal<Peer, NoError>, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> { func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal<Peer, NoError>, photo: Signal<UploadedPeerPhotoData, NoError>?, video: Signal<UploadedPeerPhotoData?, NoError>?, videoStartTimestamp: Double?, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> {
return peer return peer
|> mapError { _ -> UploadPeerPhotoError in } |> mapError { _ -> UploadPeerPhotoError in }
|> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in |> mapToSignal { peer -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
@ -150,9 +150,11 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
flags |= (1 << 2) flags |= (1 << 2)
} }
} }
let request: Signal<Api.photos.Photo, MTRpcError> let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId { if peer.id == accountPeerId {
if fallback {
flags |= (1 << 3)
}
request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)) request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp))
} else if let inputUser = apiInputUser(peer) { } else if let inputUser = apiInputUser(peer) {
if let customPeerPhotoMode = customPeerPhotoMode { if let customPeerPhotoMode = customPeerPhotoMode {
@ -177,8 +179,10 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
|> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in |> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in
var representations: [TelegramMediaImageRepresentation] = [] var representations: [TelegramMediaImageRepresentation] = []
var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = [] var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = []
var image: TelegramMediaImage?
switch photo { switch photo {
case let .photo(apiPhoto, _): case let .photo(apiPhoto, _):
image = telegramMediaImageFromApiPhoto(apiPhoto)
switch apiPhoto { switch apiPhoto {
case .photoEmpty: case .photoEmpty:
break break
@ -225,7 +229,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
if let peer = transaction.getPeer(peer.id) { if let peer = transaction.getPeer(peer.id) {
updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in
if let peer = peer as? TelegramUser { if let peer = peer as? TelegramUser {
if customPeerPhotoMode == .suggest { if customPeerPhotoMode == .suggest || fallback {
return peer return peer
} else { } else {
return peer.withUpdatedPhoto(representations) return peer.withUpdatedPhoto(representations)
@ -234,6 +238,16 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
return peer return peer
} }
}) })
if fallback {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedFallbackPhoto(.known(image))
} else {
return nil
}
}
}
} }
return (.complete(representations), photoResult.resource, videoResult?.resource) return (.complete(representations), photoResult.resource, videoResult?.resource)
} |> mapError { _ -> UploadPeerPhotoError in } } |> mapError { _ -> UploadPeerPhotoError in }
@ -327,7 +341,11 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
if let _ = peer as? TelegramUser { if let _ = peer as? TelegramUser {
let request: Signal<Api.photos.Photo, MTRpcError> let request: Signal<Api.photos.Photo, MTRpcError>
if peer.id == accountPeerId { if peer.id == accountPeerId {
request = network.request(Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty)) var flags: Int32 = 0
if fallback {
flags |= (1 << 0)
}
request = network.request(Api.functions.photos.updateProfilePhoto(flags: flags, id: Api.InputPhoto.inputPhotoEmpty))
} else if let inputUser = apiInputUser(peer) { } else if let inputUser = apiInputUser(peer) {
let flags: Int32 = (1 << 4) let flags: Int32 = (1 << 4)
request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil)) request = network.request(Api.functions.photos.uploadContactProfilePhoto(flags: flags, userId: inputUser, file: nil, video: nil, videoStartTs: nil))
@ -350,6 +368,15 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
updatePeers(transaction: transaction, peers: updatedUsers, update: { (_, updatedPeer) -> Peer? in updatePeers(transaction: transaction, peers: updatedUsers, update: { (_, updatedPeer) -> Peer? in
return updatedPeer return updatedPeer
}) })
if fallback {
transaction.updatePeerCachedData(peerIds: Set([peer.id])) { peerId, cachedPeerData in
if let cachedPeerData = cachedPeerData as? CachedUserData {
return cachedPeerData.withUpdatedFallbackPhoto(.known(nil))
} else {
return nil
}
}
}
return .complete([]) return .complete([])
} |> mapError { _ -> UploadPeerPhotoError in } } |> mapError { _ -> UploadPeerPhotoError in }
} }
@ -405,7 +432,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state
func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> { func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal<TelegramMediaImage?, NoError> {
switch reference { switch reference {
case let .cloud(imageId, accessHash, fileReference): case let .cloud(imageId, accessHash, fileReference):
return network.request(Api.functions.photos.updateProfilePhoto(id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference)))) return network.request(Api.functions.photos.updateProfilePhoto(flags: 0, id: .inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))))
|> `catch` { _ -> Signal<Api.photos.Photo, NoError> in |> `catch` { _ -> Signal<Api.photos.Photo, NoError> in
return .complete() return .complete()
} }
@ -419,12 +446,12 @@ func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMedi
} }
} }
func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal<Void, NoError> { func _internal_removeAccountPhoto(account: Account, reference: TelegramMediaImageReference?, fallback: Bool) -> Signal<Void, NoError> {
if let reference = reference { if let reference = reference {
switch reference { switch reference {
case let .cloud(imageId, accessHash, fileReference): case let .cloud(imageId, accessHash, fileReference):
if let fileReference = fileReference { if let fileReference = fileReference {
return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))])) return account.network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))]))
|> `catch` { _ -> Signal<[Int64], NoError> in |> `catch` { _ -> Signal<[Int64], NoError> in
return .single([]) return .single([])
} }
@ -436,7 +463,29 @@ func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImag
} }
} }
} else { } else {
let api = Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty) var flags: Int32 = 0
return network.request(api) |> map { _ in } |> retryRequest if fallback {
flags |= (1 << 0)
}
let api = Api.functions.photos.updateProfilePhoto(flags: flags, id: Api.InputPhoto.inputPhotoEmpty)
return account.network.request(api)
|> map { _ in }
|> retryRequest
|> mapToSignal { _ -> Signal<Void, NoError> in
if fallback {
return account.postbox.transaction { transaction -> Void in
transaction.updatePeerCachedData(peerIds: Set([account.peerId]), update: { _, current in
if let current = current as? CachedUserData {
return current.withUpdatedFallbackPhoto(.known(nil))
} else {
return current
}
})
return Void()
}
} else {
return .complete()
}
}
} }
} }

View File

@ -212,7 +212,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
} }
switch fullUser { switch fullUser {
case let .userFull(_, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _): case let .userFull(_, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _):
updatePeers(transaction: transaction, peers: peers, update: { previous, updated -> Peer in updatePeers(transaction: transaction, peers: peers, update: { previous, updated -> Peer in
if previous?.id == accountPeerId, let accountUser = accountUser, let user = TelegramUser.merge(previous as? TelegramUser, rhs: accountUser) { if previous?.id == accountPeerId, let accountUser = accountUser, let user = TelegramUser.merge(previous as? TelegramUser, rhs: accountUser) {
return user return user
@ -230,7 +230,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
previous = CachedUserData() previous = CachedUserData()
} }
switch fullUser { switch fullUser {
case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions): case let .userFull(userFullFlags, _, userFullAbout, userFullSettings, personalPhoto, profilePhoto, fallbackPhoto, _, userFullBotInfo, userFullPinnedMsgId, userFullCommonChatsCount, _, userFullTtlPeriod, userFullThemeEmoticon, _, _, _, userPremiumGiftOptions):
let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:)) let botInfo = userFullBotInfo.flatMap(BotInfo.init(apiBotInfo:))
let isBlocked = (userFullFlags & (1 << 0)) != 0 let isBlocked = (userFullFlags & (1 << 0)) != 0
let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0 let voiceCallsAvailable = (userFullFlags & (1 << 4)) != 0
@ -250,6 +250,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
let personalPhoto = personalPhoto.flatMap { telegramMediaImageFromApiPhoto($0) } let personalPhoto = personalPhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let photo = profilePhoto.flatMap { telegramMediaImageFromApiPhoto($0) } let photo = profilePhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let fallbackPhoto = fallbackPhoto.flatMap { telegramMediaImageFromApiPhoto($0) }
let premiumGiftOptions: [CachedPremiumGiftOption] let premiumGiftOptions: [CachedPremiumGiftOption]
if let userPremiumGiftOptions = userPremiumGiftOptions { if let userPremiumGiftOptions = userPremiumGiftOptions {
@ -270,6 +271,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee
.withUpdatedThemeEmoticon(userFullThemeEmoticon) .withUpdatedThemeEmoticon(userFullThemeEmoticon)
.withUpdatedPhoto(.known(photo)) .withUpdatedPhoto(.known(photo))
.withUpdatedPersonalPhoto(.known(personalPhoto)) .withUpdatedPersonalPhoto(.known(personalPhoto))
.withUpdatedFallbackPhoto(.known(fallbackPhoto))
.withUpdatedPremiumGiftOptions(premiumGiftOptions) .withUpdatedPremiumGiftOptions(premiumGiftOptions)
.withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable) .withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable)
} }

View File

@ -51,6 +51,7 @@ public enum PresentationResourceKey: Int32 {
case itemListCreateGroupIcon case itemListCreateGroupIcon
case itemListAddExceptionIcon case itemListAddExceptionIcon
case itemListAddPhoneIcon case itemListAddPhoneIcon
case itemListAddPhotoIcon
case itemListClearInputIcon case itemListClearInputIcon
case itemListStickerItemUnreadDot case itemListStickerItemUnreadDot
case itemListVerifiedPeerIcon case itemListVerifiedPeerIcon

View File

@ -169,6 +169,12 @@ public struct PresentationResourcesItemList {
}) })
} }
public static func addPhotoIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListAddPhotoIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Settings/SetAvatar"), color: theme.list.itemAccentColor)
})
}
public static func itemListClearInputIcon(_ theme: PresentationTheme) -> UIImage? { public static func itemListClearInputIcon(_ theme: PresentationTheme) -> UIImage? {
return theme.image(PresentationResourceKey.itemListClearInputIcon.rawValue, { theme in return theme.image(PresentationResourceKey.itemListClearInputIcon.rawValue, { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.list.inputClearButtonColor) return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.list.inputClearButtonColor)

View File

@ -831,6 +831,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme,
} else { } else {
attributedString = NSAttributedString(string: strings.Notification_SuggestedProfileVideo, font: titleFont, textColor: primaryTextColor) attributedString = NSAttributedString(string: strings.Notification_SuggestedProfileVideo, font: titleFont, textColor: primaryTextColor)
} }
case .attachMenuBotAllowed:
attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor)
case .unknown: case .unknown:
attributedString = nil attributedString = nil
} }

View File

@ -9518,7 +9518,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let hapticFeedback = HapticFeedback() let hapticFeedback = HapticFeedback()
hapticFeedback.impact() hapticFeedback.impact()
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action), elevatedLayout: false, action: { [weak self] action in strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.Conversation_SendMesageAsPremiumInfo, action: strongSelf.presentationData.strings.EmojiInput_PremiumEmojiToast_Action, duration: 3), elevatedLayout: false, action: { [weak self] action in
guard let strongSelf = self else { guard let strongSelf = self else {
return true return true
} }
@ -12063,8 +12063,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
let _ = (context.engine.messages.getAttachMenuBot(botId: botId) let _ = (context.engine.messages.getAttachMenuBot(botId: botId)
|> deliverOnMainQueue).start(next: { bot in |> deliverOnMainQueue).start(next: { bot in
let peer = EnginePeer(bot.peer) let peer = EnginePeer(bot.peer)
let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), icons: bot.icons, completion: {
let _ = (context.engine.messages.addBotToAttachMenu(botId: botId) let controller = addWebAppToAttachmentController(context: context, peerName: peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), icons: bot.icons, requestWriteAccess: bot.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = (context.engine.messages.addBotToAttachMenu(botId: botId, allowWrite: allowWrite)
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
}, completed: { }, completed: {

View File

@ -1993,7 +1993,9 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
let _ = strongSelf.controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets) let _ = strongSelf.controllerInteraction.sendSticker(fileReference, silentPosting, schedule, query, clearInput, sourceView, sourceRect, sourceLayer, bubbleUpEmojiOrStickersets)
} }
if let chatPeerId = strongSelf.chatPeerId { let isLocked = file.isPremiumSticker && !hasPremium
if let chatPeerId = strongSelf.chatPeerId, !isLocked {
if chatPeerId != strongSelf.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat { if chatPeerId != strongSelf.context.account.peerId && chatPeerId.namespace != Namespaces.Peer.SecretChat {
menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in menuItems.append(.action(ContextMenuActionItem(text: presentationData.strings.Conversation_SendMessage_SendSilently, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Menu/SilentIcon"), color: theme.actionSheet.primaryTextColor)
@ -2093,8 +2095,8 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior {
guard let view = view else { guard let view = view else {
return nil return nil
} }
return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: file.isPremiumSticker && !hasPremium, menu: menuItems, openPremiumIntro: { return (view, itemLayer.convert(itemLayer.bounds, to: view.layer), StickerPreviewPeekContent(account: context.account, theme: presentationData.theme, strings: presentationData.strings, item: .pack(file), isLocked: isLocked && !isStarred, menu: menuItems, openPremiumIntro: {
guard let strongSelf = self else { guard let strongSelf = self else {
return return
} }

View File

@ -632,8 +632,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur
let choose = filterChooseTypes(choose, peerTypes: bot.peerTypes) let choose = filterChooseTypes(choose, peerTypes: bot.peerTypes)
let botPeer = EnginePeer(bot.peer) let botPeer = EnginePeer(bot.peer)
let controller = addWebAppToAttachmentController(context: context, peerName: botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), icons: bot.icons, completion: { let controller = addWebAppToAttachmentController(context: context, peerName: botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), icons: bot.icons, requestWriteAccess: bot.flags.contains(.requiresWriteAccess), completion: { allowWrite in
let _ = (context.engine.messages.addBotToAttachMenu(botId: peerId) let _ = (context.engine.messages.addBotToAttachMenu(botId: peerId, allowWrite: allowWrite)
|> deliverOnMainQueue).start(error: { _ in |> deliverOnMainQueue).start(error: { _ in
presentError(presentationData.strings.WebApp_AddToAttachmentUnavailableError) presentError(presentationData.strings.WebApp_AddToAttachmentUnavailableError)
}, completed: { }, completed: {

View File

@ -399,7 +399,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if peer.isDeleted { if peer.isDeleted {
overrideImage = .deletedIcon overrideImage = .deletedIcon
} else if let previousItem = previousItem, item == nil { } else if let previousItem = previousItem, item == nil {
if case let .image(_, representations, _, _) = previousItem, let rep = representations.last { if case let .image(_, representations, _, _, _) = previousItem, let rep = representations.last {
self.removedPhotoResourceIds.insert(rep.representation.resource.id.stringRepresentation) self.removedPhotoResourceIds.insert(rep.representation.resource.id.stringRepresentation)
} }
overrideImage = AvatarNodeImageOverride.none overrideImage = AvatarNodeImageOverride.none
@ -499,7 +499,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode {
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
videoId = videoId &+ resource.photoId videoId = videoId &+ resource.photoId
} }
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _):
representations = imageRepresentations representations = imageRepresentations
videoRepresentations = videoRepresentationsValue videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail immediateThumbnailData = immediateThumbnail
@ -784,7 +784,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
if canEdit, peer.profileImageRepresentations.isEmpty { if canEdit, peer.profileImageRepresentations.isEmpty {
overrideImage = .editAvatarIcon(forceNone: true) overrideImage = .editAvatarIcon(forceNone: true)
} else if let previousItem = previousItem, item == nil { } else if let previousItem = previousItem, item == nil {
if case let .image(_, representations, _, _) = previousItem, let rep = representations.last { if case let .image(_, representations, _, _, _) = previousItem, let rep = representations.last {
self.removedPhotoResourceIds.insert(rep.representation.resource.id.stringRepresentation) self.removedPhotoResourceIds.insert(rep.representation.resource.id.stringRepresentation)
} }
overrideImage = canEdit ? .editAvatarIcon(forceNone: true) : AvatarNodeImageOverride.none overrideImage = canEdit ? .editAvatarIcon(forceNone: true) : AvatarNodeImageOverride.none
@ -831,7 +831,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode {
if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource {
id = id &+ resource.photoId id = id &+ resource.photoId
} }
case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _):
representations = imageRepresentations representations = imageRepresentations
videoRepresentations = videoRepresentationsValue videoRepresentations = videoRepresentationsValue
immediateThumbnailData = immediateThumbnail immediateThumbnailData = immediateThumbnail

View File

@ -1359,7 +1359,8 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
let ItemSuggest = 0 let ItemSuggest = 0
let ItemCustom = 1 let ItemCustom = 1
let ItemReset = 2 let ItemReset = 2
let ItemDelete = 3 let ItemInfo = 3
let ItemDelete = 4
let compactName = EnginePeer(user).compactDisplayTitle let compactName = EnginePeer(user).compactDisplayTitle
@ -1381,6 +1382,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
interaction.resetCustomPhoto() interaction.resetCustomPhoto()
})) }))
} }
items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string))
if data.isContact { if data.isContact {
items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: { items[.peerSettings]!.append(PeerInfoScreenActionItem(id: ItemDelete, text: presentationData.strings.UserInfo_DeleteContact, color: .destructive, action: {
@ -3476,7 +3478,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
var currentIsVideo = false var currentIsVideo = false
let item = strongSelf.headerNode.avatarListNode.listContainerNode.currentItemNode?.item let item = strongSelf.headerNode.avatarListNode.listContainerNode.currentItemNode?.item
if let item = item, case let .image(_, _, videoRepresentations, _) = item { if let item = item, case let .image(_, _, videoRepresentations, _, _) = item {
currentIsVideo = !videoRepresentations.isEmpty currentIsVideo = !videoRepresentations.isEmpty
} }
@ -6754,7 +6756,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data) self.context.account.postbox.mediaBox.storeResourceData(resource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: resource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false)
if case .suggest = mode { if [.suggest, .fallback].contains(mode) {
} else { } else {
self.state = self.state.withUpdatingAvatar(.image(representation)) self.state = self.state.withUpdatingAvatar(.image(representation))
} }
@ -6767,9 +6769,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let postbox = self.context.account.postbox let postbox = self.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if self.isSettings { if self.isSettings {
signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in if case .fallback = mode {
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) signal = self.context.engine.accountData.updateFallbackPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
}) return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else {
signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
}
} else if case .custom = mode { } else if case .custom = mode {
signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in signal = self.context.engine.contacts.updateContactPhoto(peerId: self.peerId, resource: resource, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
@ -6803,7 +6811,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer { if let strongSelf = self, let peer {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessPhotoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
} }
}) })
} }
@ -6826,7 +6834,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data) self.context.account.postbox.mediaBox.storeResourceData(photoResource.id, data: data)
let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false) let representation = TelegramMediaImageRepresentation(dimensions: PixelDimensions(width: 640, height: 640), resource: photoResource, progressiveSizes: [], immediateThumbnailData: nil, hasVideo: false, isPersonal: mode == .custom ? true : false)
if case .suggest = mode { if [.suggest, .fallback].contains(mode) {
} else { } else {
self.state = self.state.withUpdatingAvatar(.image(representation)) self.state = self.state.withUpdatingAvatar(.image(representation))
} }
@ -6927,9 +6935,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self.updateAvatarDisposable.set((signal self.updateAvatarDisposable.set((signal
|> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in |> mapToSignal { videoResource -> Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError> in
if isSettings { if isSettings {
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in if case .fallback = mode {
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) return context.engine.accountData.updateFallbackPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
}) return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
} else {
return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
})
}
} else if case .custom = mode { } else if case .custom = mode {
return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mode: .custom, mapResourceToAvatarSizes: { resource, representations in return context.engine.contacts.updateContactPhoto(peerId: peerId, resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations)
@ -6962,7 +6976,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId))
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
if let strongSelf = self, let peer { if let strongSelf = self, let peer {
strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) strongSelf.controller?.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .invitedToVoiceChat(context: strongSelf.context, peer: peer, text: strongSelf.presentationData.strings.UserInfo_SetCustomPhoto_SuccessVideoText(peer.compactDisplayTitle).string, action: nil, duration: 5), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current)
} }
}) })
} }
@ -6973,16 +6987,17 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
case generic case generic
case suggest case suggest
case custom case custom
case fallback
} }
private func openAvatarForEditing(mode: AvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping () -> Void = {}) { fileprivate func openAvatarForEditing(mode: AvatarEditingMode = .generic, fromGallery: Bool = false, completion: @escaping () -> Void = {}) {
guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else { guard let peer = self.data?.peer, mode != .generic || canEditPeerInfo(context: self.context, peer: peer, chatLocation: self.chatLocation, threadData: self.data?.threadData) else {
return return
} }
var currentIsVideo = false var currentIsVideo = false
let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item let item = self.headerNode.avatarListNode.listContainerNode.currentItemNode?.item
if let item = item, case let .image(_, _, videoRepresentations, _) = item { if let item = item, case let .image(_, _, videoRepresentations, _, _) = item {
currentIsVideo = !videoRepresentations.isEmpty currentIsVideo = !videoRepresentations.isEmpty
} }
@ -7009,7 +7024,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
legacyController.bind(controller: navigationController) legacyController.bind(controller: navigationController)
strongSelf.view.endEditing(true) strongSelf.view.endEditing(true)
strongSelf.controller?.present(legacyController, in: .window(.root)) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.present(legacyController, in: .window(.root))
var hasPhotos = false var hasPhotos = false
if !peer.profileImageRepresentations.isEmpty { if !peer.profileImageRepresentations.isEmpty {
@ -7026,7 +7041,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
return true return true
}) })
strongSelf.controller?.present(controller, in: .window(.root)) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root))
return controller return controller
} }
@ -7040,6 +7055,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
hasDeleteButton = hasPhotos && !fromGallery hasDeleteButton = hasPhotos && !fromGallery
} else if case .custom = mode { } else if case .custom = mode {
hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true hasDeleteButton = peer.profileImageRepresentations.first?.isPersonal == true
} else if case .fallback = mode {
if let cachedData = strongSelf.data?.cachedData as? CachedUserData, case let .known(photo) = cachedData.fallbackPhoto {
hasDeleteButton = photo != nil
}
} }
let title: String? let title: String?
@ -7076,7 +7095,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
self?.updateProfilePhoto(result, mode: mode) self?.updateProfilePhoto(result, mode: mode)
})) }))
controller.navigationPresentation = .modal controller.navigationPresentation = .modal
strongSelf.controller?.push(controller) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller)
if fromGallery { if fromGallery {
completion() completion()
@ -7088,7 +7107,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: { let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextPhoto, doneTitle: confirmationAction, commit: {
commit?() commit?()
}) })
strongSelf.controller?.presentInGlobalOverlay(controller) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller)
} }
} }
} }
@ -7098,7 +7117,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, commit: { let controller = photoUpdateConfirmationController(context: strongSelf.context, peer: peer, image: image, text: confirmationTextVideo, doneTitle: confirmationAction, commit: {
commit?() commit?()
}) })
strongSelf.controller?.presentInGlobalOverlay(controller) (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller)
} }
} }
} }
@ -7119,55 +7138,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
return return
} }
let proceed = { strongSelf.openAvatarRemoval(mode: mode, peer: peer, item: item)
if let item = item {
strongSelf.deleteProfilePhoto(item)
}
let _ = strongSelf.currentAvatarMixin.swap(nil)
if let _ = peer.smallProfileImage {
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
let postbox = strongSelf.context.account.postbox
strongSelf.updateAvatarDisposable.set((strongSelf.context.engine.peers.updatePeerPhoto(peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
}
}))
}
let actionSheet = ActionSheetController(presentationData: presentationData)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
proceed()
})
]
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
strongSelf.controller?.present(actionSheet, in: .window(.root))
} }
mixin.didDismiss = { [weak legacyController] in mixin.didDismiss = { [weak legacyController] in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -7185,6 +7156,79 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
}) })
} }
fileprivate func openAvatarRemoval(mode: AvatarEditingMode, peer: EnginePeer? = nil, item: PeerInfoAvatarListItem? = nil) {
let proceed = { [weak self] in
guard let strongSelf = self else {
return
}
if let item = item {
strongSelf.deleteProfilePhoto(item)
}
let _ = strongSelf.currentAvatarMixin.swap(nil)
if mode != .fallback {
if let peer = peer, let _ = peer.smallProfileImage {
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
}
}
let postbox = strongSelf.context.account.postbox
let signal: Signal<UpdatePeerPhotoStatus, UploadPeerPhotoError>
if case .custom = mode {
signal = strongSelf.context.engine.contacts.updateContactPhoto(peerId: strongSelf.peerId, resource: nil, videoResource: nil, videoStartTimestamp: nil, mode: .custom, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
} else if case .fallback = mode {
signal = strongSelf.context.engine.accountData.removeFallbackPhoto(reference: nil)
|> castError(UploadPeerPhotoError.self)
|> map { _ in
return .complete([])
}
} else {
signal = strongSelf.context.engine.peers.updatePeerPhoto(peerId: strongSelf.peerId, photo: nil, mapResourceToAvatarSizes: { resource, representations in
return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations)
})
}
strongSelf.updateAvatarDisposable.set((signal
|> deliverOnMainQueue).start(next: { result in
guard let strongSelf = self else {
return
}
switch result {
case .complete:
strongSelf.state = strongSelf.state.withUpdatingAvatar(nil)
if let (layout, navigationHeight) = strongSelf.validLayout {
strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false)
}
case .progress:
break
}
}))
}
let presentationData = self.presentationData
let actionSheet = ActionSheetController(presentationData: presentationData)
let items: [ActionSheetItem] = [
ActionSheetButtonItem(title: presentationData.strings.Settings_RemoveConfirmation, color: .destructive, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
proceed()
})
]
actionSheet.setItemGroups([
ActionSheetItemGroup(items: items),
ActionSheetItemGroup(items: [
ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { [weak actionSheet] in
actionSheet?.dismissAnimated()
})
])
])
(self.controller?.navigationController?.topViewController as? ViewController)?.present(actionSheet, in: .window(.root))
}
private func openAddMember() { private func openAddMember() {
guard let data = self.data, let groupPeer = data.peer, let controller = self.controller else { guard let data = self.data, let groupPeer = data.peer, let controller = self.controller else {
return return
@ -7290,6 +7334,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate
} }
) )
} }
}, requestPublicPhotoSetup: { [weak self] in
if let strongSelf = self {
strongSelf.openAvatarForEditing(mode: .fallback)
}
}, requestPublicPhotoRemove: { [weak self] in
if let strongSelf = self {
strongSelf.openAvatarRemoval(mode: .fallback)
}
})) }))
} }
}) })
@ -9322,18 +9374,18 @@ public final class PeerInfoScreenImpl: ViewController, PeerInfoScreen, KeyShortc
} }
} }
func updateProfilePhoto(_ image: UIImage) { func updateProfilePhoto(_ image: UIImage, fallback: Bool = false) {
if !self.isNodeLoaded { if !self.isNodeLoaded {
self.loadDisplayNode() self.loadDisplayNode()
} }
self.controllerNode.updateProfilePhoto(image, mode: .generic) self.controllerNode.updateProfilePhoto(image, mode: fallback ? .fallback : .generic)
} }
func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?) { func updateProfileVideo(_ image: UIImage, asset: Any?, adjustments: TGVideoEditAdjustments?, fallback: Bool = false) {
if !self.isNodeLoaded { if !self.isNodeLoaded {
self.loadDisplayNode() self.loadDisplayNode()
} }
self.controllerNode.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: .generic) self.controllerNode.updateProfileVideo(image, asset: asset, adjustments: adjustments, mode: fallback ? .fallback : .generic)
} }
static func displayChatNavigationMenu(context: AccountContext, chatNavigationStack: [ChatNavigationStackItem], nextFolderId: Int32?, parentController: ViewController, backButtonView: UIView, navigationController: NavigationController, gesture: ContextGesture) { static func displayChatNavigationMenu(context: AccountContext, chatNavigationStack: [ChatNavigationStackItem], nextFolderId: Int32?, parentController: ViewController, backButtonView: UIView, navigationController: NavigationController, gesture: ContextGesture) {

View File

@ -22,7 +22,7 @@ public enum UndoOverlayContent {
case chatRemovedFromFolder(chatTitle: String, folderTitle: String) case chatRemovedFromFolder(chatTitle: String, folderTitle: String)
case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool) case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: Bool)
case setProximityAlert(title: String, text: String, cancelled: Bool) case setProximityAlert(title: String, text: String, cancelled: Bool)
case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, text: String, action: String?) case invitedToVoiceChat(context: AccountContext, peer: EnginePeer, text: String, action: String?, duration: Double)
case linkCopied(text: String) case linkCopied(text: String)
case banned(text: String) case banned(text: String)
case importedMessage(text: String) case importedMessage(text: String)

View File

@ -518,7 +518,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 3 self.originalRemainingSeconds = 3
case let .invitedToVoiceChat(context, peer, text, action): case let .invitedToVoiceChat(context, peer, text, action, duration):
self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0)) self.avatarNode = AvatarNode(font: avatarPlaceholderFont(size: 15.0))
self.iconNode = nil self.iconNode = nil
self.iconCheckNode = nil self.iconCheckNode = nil
@ -536,11 +536,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode {
if let action = action { if let action = action {
displayUndo = true displayUndo = true
undoText = action undoText = action
self.originalRemainingSeconds = 5
} else { } else {
displayUndo = false displayUndo = false
self.originalRemainingSeconds = 3
} }
self.originalRemainingSeconds = duration
case let .audioRate(slowdown, text): case let .audioRate(slowdown, text):
self.avatarNode = nil self.avatarNode = nil
self.iconNode = nil self.iconNode = nil

View File

@ -28,6 +28,8 @@ swift_library(
"//submodules/BotPaymentsUI:BotPaymentsUI", "//submodules/BotPaymentsUI:BotPaymentsUI",
"//submodules/PromptUI:PromptUI", "//submodules/PromptUI:PromptUI",
"//submodules/PhoneNumberFormat:PhoneNumberFormat", "//submodules/PhoneNumberFormat:PhoneNumberFormat",
"//submodules/QrCodeUI:QrCodeUI",
"//submodules/InstantPageUI:InstantPageUI",
], ],
visibility = [ visibility = [
"//visibility:public", "//visibility:public",

View File

@ -10,6 +10,16 @@ import TelegramUIPreferences
import AccountContext import AccountContext
import AppBundle import AppBundle
import PhotoResources import PhotoResources
import CheckNode
import Markdown
private let textFont = Font.regular(13.0)
private let boldTextFont = Font.semibold(13.0)
private func formattedText(_ text: String, color: UIColor, textAlignment: NSTextAlignment = .natural) -> NSAttributedString {
return parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: MarkdownAttributeSet(font: textFont, textColor: color), bold: MarkdownAttributeSet(font: boldTextFont, textColor: color), link: MarkdownAttributeSet(font: textFont, textColor: color), linkAttribute: { _ in return nil}), textAlignment: textAlignment)
}
private final class WebAppAlertContentNode: AlertContentNode { private final class WebAppAlertContentNode: AlertContentNode {
private let strings: PresentationStrings private let strings: PresentationStrings
@ -20,6 +30,9 @@ private final class WebAppAlertContentNode: AlertContentNode {
private let appIconNode: ASImageNode private let appIconNode: ASImageNode
private let iconNode: ASImageNode private let iconNode: ASImageNode
private let allowWriteCheckNode: InteractiveCheckNode
private let allowWriteLabelNode: ASTextNode
private let actionNodesSeparator: ASDisplayNode private let actionNodesSeparator: ASDisplayNode
private let actionNodes: [TextAlertContentActionNode] private let actionNodes: [TextAlertContentActionNode]
private let actionVerticalSeparators: [ASDisplayNode] private let actionVerticalSeparators: [ASDisplayNode]
@ -32,7 +45,13 @@ private final class WebAppAlertContentNode: AlertContentNode {
return self.isUserInteractionEnabled return self.isUserInteractionEnabled
} }
init(account: Account, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peerName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], actions: [TextAlertAction]) { var allowWriteAccess: Bool = true {
didSet {
self.allowWriteCheckNode.setSelected(self.allowWriteAccess, animated: true)
}
}
init(account: Account, theme: AlertControllerTheme, ptheme: PresentationTheme, strings: PresentationStrings, peerName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], requestWriteAccess: Bool, actions: [TextAlertAction]) {
self.strings = strings self.strings = strings
self.peerName = peerName self.peerName = peerName
@ -54,6 +73,12 @@ private final class WebAppAlertContentNode: AlertContentNode {
self.iconNode = ASImageNode() self.iconNode = ASImageNode()
self.iconNode.displaysAsynchronously = false self.iconNode.displaysAsynchronously = false
self.iconNode.displayWithoutProcessing = true self.iconNode.displayWithoutProcessing = true
self.allowWriteCheckNode = InteractiveCheckNode(theme: CheckNodeTheme(backgroundColor: theme.accentColor, strokeColor: theme.contrastColor, borderColor: theme.controlBorderColor, overlayBorder: false, hasInset: false, hasShadow: false))
self.allowWriteCheckNode.setSelected(true, animated: false)
self.allowWriteLabelNode = ASTextNode()
self.allowWriteLabelNode.maximumNumberOfLines = 4
self.allowWriteLabelNode.isUserInteractionEnabled = true
self.actionNodesSeparator = ASDisplayNode() self.actionNodesSeparator = ASDisplayNode()
self.actionNodesSeparator.isLayerBacked = true self.actionNodesSeparator.isLayerBacked = true
@ -77,6 +102,12 @@ private final class WebAppAlertContentNode: AlertContentNode {
self.addSubnode(self.textNode) self.addSubnode(self.textNode)
self.addSubnode(self.appIconNode) self.addSubnode(self.appIconNode)
self.addSubnode(self.iconNode) self.addSubnode(self.iconNode)
if requestWriteAccess {
self.addSubnode(self.allowWriteCheckNode)
self.addSubnode(self.allowWriteLabelNode)
}
self.addSubnode(self.actionNodesSeparator) self.addSubnode(self.actionNodesSeparator)
@ -88,6 +119,12 @@ private final class WebAppAlertContentNode: AlertContentNode {
self.addSubnode(separatorNode) self.addSubnode(separatorNode)
} }
self.allowWriteCheckNode.valueChanged = { [weak self] value in
if let strongSelf = self {
strongSelf.allowWriteAccess = !strongSelf.allowWriteAccess
}
}
self.updateTheme(theme) self.updateTheme(theme)
if let peerIcon = self.peerIcon { if let peerIcon = self.peerIcon {
@ -109,12 +146,26 @@ private final class WebAppAlertContentNode: AlertContentNode {
self.iconDisposable?.dispose() self.iconDisposable?.dispose()
} }
override func didLoad() {
super.didLoad()
self.allowWriteLabelNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.allowWriteTap(_:))))
}
@objc private func allowWriteTap(_ gestureRecognizer: UITapGestureRecognizer) {
if self.allowWriteCheckNode.isUserInteractionEnabled {
self.allowWriteAccess = !self.allowWriteAccess
}
}
override func updateTheme(_ theme: AlertControllerTheme) { override func updateTheme(_ theme: AlertControllerTheme) {
self.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText(self.peerName).string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) self.textNode.attributedText = NSAttributedString(string: strings.WebApp_AddToAttachmentText(self.peerName).string, font: Font.bold(17.0), textColor: theme.primaryColor, paragraphAlignment: .center)
self.appIconNode.image = generateTintedImage(image: self.appIconNode.image, color: theme.accentColor) self.appIconNode.image = generateTintedImage(image: self.appIconNode.image, color: theme.accentColor)
self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/BotPlus"), color: theme.accentColor) self.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Attach Menu/BotPlus"), color: theme.accentColor)
self.allowWriteLabelNode.attributedText = formattedText(strings.WebApp_AddToAttachmentAllowMessages(self.peerName).string, color: theme.primaryColor)
self.actionNodesSeparator.backgroundColor = theme.separatorColor self.actionNodesSeparator.backgroundColor = theme.separatorColor
for actionNode in self.actionNodes { for actionNode in self.actionNodes {
actionNode.updateTheme(theme) actionNode.updateTheme(theme)
@ -146,6 +197,23 @@ private final class WebAppAlertContentNode: AlertContentNode {
let textSize = self.textNode.measure(size) let textSize = self.textNode.measure(size)
var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize) var textFrame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: origin.y), size: textSize)
origin.y += textSize.height
var entriesHeight: CGFloat = 0.0
if self.allowWriteLabelNode.supernode != nil {
origin.y += 16.0
entriesHeight += 16.0
let checkSize = CGSize(width: 22.0, height: 22.0)
let condensedSize = CGSize(width: size.width - 76.0, height: size.height)
let allowWriteSize = self.allowWriteLabelNode.measure(condensedSize)
transition.updateFrame(node: self.allowWriteLabelNode, frame: CGRect(origin: CGPoint(x: 46.0, y: origin.y), size: allowWriteSize))
transition.updateFrame(node: self.allowWriteCheckNode, frame: CGRect(origin: CGPoint(x: 12.0, y: origin.y - 2.0), size: checkSize))
origin.y += allowWriteSize.height
entriesHeight += allowWriteSize.height
}
let actionButtonHeight: CGFloat = 44.0 let actionButtonHeight: CGFloat = 44.0
var minActionsWidth: CGFloat = 0.0 var minActionsWidth: CGFloat = 0.0
@ -180,7 +248,7 @@ private final class WebAppAlertContentNode: AlertContentNode {
} }
let resultWidth = contentWidth + insets.left + insets.right let resultWidth = contentWidth + insets.left + insets.right
let resultSize = CGSize(width: resultWidth, height: iconSize.height + textSize.height + actionsHeight + 17.0 + insets.top + insets.bottom) let resultSize = CGSize(width: resultWidth, height: iconSize.height + textSize.height + entriesHeight + actionsHeight + 17.0 + insets.top + insets.bottom)
transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) transition.updateFrame(node: self.actionNodesSeparator, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)))
@ -227,7 +295,7 @@ private final class WebAppAlertContentNode: AlertContentNode {
nodeIndex += 1 nodeIndex += 1
} }
iconFrame.origin.x = floorToScreenPixels((resultSize.width - iconFrame.width) / 2.0) + 19.0 iconFrame.origin.x = floorToScreenPixels((resultSize.width - iconFrame.width) / 2.0) + 21.0
transition.updateFrame(node: self.appIconNode, frame: CGRect(x: iconFrame.minX - 50.0, y: iconFrame.minY + 3.0, width: 42.0, height: 42.0)) transition.updateFrame(node: self.appIconNode, frame: CGRect(x: iconFrame.minX - 50.0, y: iconFrame.minY + 3.0, width: 42.0, height: 42.0))
transition.updateFrame(node: self.iconNode, frame: iconFrame) transition.updateFrame(node: self.iconNode, frame: iconFrame)
@ -239,7 +307,7 @@ private final class WebAppAlertContentNode: AlertContentNode {
} }
} }
public func addWebAppToAttachmentController(context: AccountContext, peerName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], completion: @escaping () -> Void) -> AlertController { public func addWebAppToAttachmentController(context: AccountContext, peerName: String, icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile], requestWriteAccess: Bool, completion: @escaping (Bool) -> Void) -> AlertController {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
let theme = presentationData.theme let theme = presentationData.theme
let strings = presentationData.strings let strings = presentationData.strings
@ -251,10 +319,10 @@ public func addWebAppToAttachmentController(context: AccountContext, peerName: S
}), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: { }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: {
dismissImpl?(true) dismissImpl?(true)
completion() completion(true)
})] })]
contentNode = WebAppAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peerName: peerName, icons: icons, actions: actions) contentNode = WebAppAlertContentNode(account: context.account, theme: AlertControllerTheme(presentationData: presentationData), ptheme: theme, strings: strings, peerName: peerName, icons: icons, requestWriteAccess: requestWriteAccess, actions: actions)
let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!)
dismissImpl = { [weak controller] animated in dismissImpl = { [weak controller] animated in

View File

@ -21,6 +21,8 @@ import MoreButtonNode
import BotPaymentsUI import BotPaymentsUI
import PromptUI import PromptUI
import PhoneNumberFormat import PhoneNumberFormat
import QrCodeUI
import InstantPageUI
private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] private let durgerKingBotIds: [Int64] = [5104055776, 2200339955]
@ -680,10 +682,27 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
case "web_app_open_link": case "web_app_open_link":
if let json = json, let url = json["url"] as? String { if let json = json, let url = json["url"] as? String {
let tryInstantView = json["try_instant_view"] as? Bool ?? false
let currentTimestamp = CACurrentMediaTime() let currentTimestamp = CACurrentMediaTime()
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 {
self.webView?.lastTouchTimestamp = nil self.webView?.lastTouchTimestamp = nil
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {}) if tryInstantView {
let _ = (resolveInstantViewUrl(account: self.context.account, url: url)
|> deliverOnMainQueue).start(next: { [weak self] result in
guard let strongSelf = self else {
return
}
switch result {
case let .instantView(webPage, anchor):
let controller = InstantPageController(context: strongSelf.context, webPage: webPage, sourcePeerType: .otherPrivate, anchor: anchor)
strongSelf.controller?.getNavigationController()?.pushViewController(controller)
default:
strongSelf.context.sharedContext.openExternalUrl(context: strongSelf.context, urlContext: .generic, url: url, forceExternal: true, presentationData: strongSelf.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}
})
} else {
self.context.sharedContext.openExternalUrl(context: self.context, urlContext: .generic, url: url, forceExternal: true, presentationData: self.context.sharedContext.currentPresentationData.with { $0 }, navigationController: nil, dismissInput: {})
}
} }
} }
case "web_app_setup_back_button": case "web_app_setup_back_button":
@ -799,8 +818,28 @@ public final class WebAppController: ViewController, AttachmentContainable {
if let json = json, let needConfirmation = json["need_confirmation"] as? Bool { if let json = json, let needConfirmation = json["need_confirmation"] as? Bool {
self.needDismissConfirmation = needConfirmation self.needDismissConfirmation = needConfirmation
} }
case "web_app_request_phone": case "web_app_open_scan_qr_popup":
break var info: String = ""
if let json = json, let text = json["text"] as? String {
info = text
}
let controller = QrCodeScanScreen(context: self.context, subject: .custom(info: info))
controller.completion = { [weak self] result in
if let strongSelf = self {
strongSelf.sendQrCodeScannedEvent(data: result)
}
}
self.controller?.present(controller, in: .window(.root))
case "web_app_read_text_from_clipboard":
if let json = json, let requestId = json["req_id"] as? String {
let currentTimestamp = CACurrentMediaTime()
var fillData = false
if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0, self.controller?.url == nil {
self.webView?.lastTouchTimestamp = nil
fillData = true
}
self.sendClipboardTextEvent(requestId: requestId, fillData: fillData)
}
default: default:
break break
} }
@ -930,6 +969,22 @@ public final class WebAppController: ViewController, AttachmentContainable {
} }
self.webView?.sendEvent(name: "phone_requested", data: paramsString) self.webView?.sendEvent(name: "phone_requested", data: paramsString)
} }
fileprivate func sendQrCodeScannedEvent(data: String?) {
let paramsString = data.flatMap { "{data: \"\($0)\"}" } ?? "{}"
self.webView?.sendEvent(name: "scan_qr_popup_closed", data: paramsString)
}
fileprivate func sendClipboardTextEvent(requestId: String, fillData: Bool) {
var paramsString: String
if fillData {
let data = UIPasteboard.general.string ?? ""
paramsString = "{req_id: \"\(requestId)\", data: \"\(data)\"}"
} else {
paramsString = "{req_id: \"\(requestId)\"}"
}
self.webView?.sendEvent(name: "clipboard_text_received", data: paramsString)
}
} }
fileprivate var controllerNode: Node { fileprivate var controllerNode: Node {
@ -1067,7 +1122,7 @@ public final class WebAppController: ViewController, AttachmentContainable {
let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId}) let attachMenuBot = attachMenuBots.first(where: { $0.peer.id == botId})
if self?.url == nil, let attachMenuBot = attachMenuBot, attachMenuBot.hasSettings { if self?.url == nil, let attachMenuBot = attachMenuBot, attachMenuBot.flags.contains(.hasSettings) {
items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in items.append(.action(ContextMenuActionItem(text: presentationData.strings.WebApp_Settings, icon: { theme in
return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor)
}, action: { [weak self] c, _ in }, action: { [weak self] c, _ in