From 47ffcd2e4d144860b2dca47461a930f6baf5eb36 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 13 Dec 2022 17:42:32 +0400 Subject: [PATCH] Update API [skip ci] --- .../Telegram-iOS/en.lproj/Localizable.strings | 13 ++ .../GalleryData/Sources/GalleryData.swift | 2 +- .../Sources/InviteRequestsController.swift | 2 +- .../Sources/MediaPickerGridItem.swift | 10 +- .../Sources/MediaPickerSelectedListNode.swift | 8 +- .../Sources/AvatarGalleryController.swift | 77 ++++--- .../AvatarGalleryItemFooterContentNode.swift | 2 +- .../Sources/PeerAvatarImageGalleryItem.swift | 6 +- .../Sources/PeerInfoAvatarListNode.swift | 108 ++++++++-- .../QrCodeUI/Sources/QrCodeScanScreen.swift | 45 +++- .../PrivacyAndSecurityController.swift | 23 +- .../SelectivePrivacySettingsController.swift | 138 +++++++++--- submodules/TelegramApi/Sources/Api0.swift | 3 +- submodules/TelegramApi/Sources/Api11.swift | 12 ++ submodules/TelegramApi/Sources/Api21.swift | 80 +++---- submodules/TelegramApi/Sources/Api29.swift | 14 +- .../Sources/VoiceChatController.swift | 10 +- .../ReplyMarkupMessageAttribute.swift | 3 + .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 2 + .../SyncCore/SyncCore_CachedUserData.swift | 47 +++-- ...SyncCore_ReplyMarkupMessageAttribute.swift | 1 + .../SyncCore_TelegramMediaAction.swift | 5 + .../TelegramEngineAccountData.swift | 12 +- .../Messages/AttachMenuBots.swift | 75 +++++-- .../Messages/TelegramEngineMessages.swift | 4 +- .../Peers/PeerPhotoUpdater.swift | 75 +++++-- .../Peers/UpdateCachedPeerData.swift | 6 +- .../Resources/PresentationResourceKey.swift | 1 + .../PresentationResourcesItemList.swift | 6 + .../Sources/ServiceMessageStrings.swift | 2 + .../TelegramUI/Sources/ChatController.swift | 7 +- .../Sources/ChatEntityKeyboardInputNode.swift | 8 +- .../TelegramUI/Sources/OpenResolvedUrl.swift | 4 +- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 8 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 196 +++++++++++------- .../Sources/UndoOverlayController.swift | 2 +- .../Sources/UndoOverlayControllerNode.swift | 5 +- submodules/WebUI/BUILD | 2 + .../Sources/WebAppAlertContentNode.swift | 80 ++++++- .../WebUI/Sources/WebAppController.swift | 63 +++++- 41 files changed, 867 insertions(+), 302 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 782cf7a55e..81a0208547 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8450,6 +8450,7 @@ Sorry for the inconvenience."; "UserInfo.SuggestPhoto" = "Suggest Photo for %@"; "UserInfo.SetCustomPhoto" = "Set Photo for %@"; "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.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.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.ResetToOriginalAlertReset" = "Reset"; @@ -8491,3 +8495,12 @@ Sorry for the inconvenience."; "PhotoEditor.SetAsMyPhoto" = "Set as My Photo"; "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"; diff --git a/submodules/GalleryData/Sources/GalleryData.swift b/submodules/GalleryData/Sources/GalleryData.swift index 44d4244548..7af4708784 100644 --- a/submodules/GalleryData/Sources/GalleryData.swift +++ b/submodules/GalleryData/Sources/GalleryData.swift @@ -116,7 +116,7 @@ public func chatMessageGalleryControllerData(context: AccountContext, chatLocati switch action.action { case let .photoUpdated(image), let .suggestedProfilePhoto(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 if case .photoUpdated = action.action { diff --git a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift index ae6e8244e2..b7ee300160 100644 --- a/submodules/InviteLinksUI/Sources/InviteRequestsController.swift +++ b/submodules/InviteLinksUI/Sources/InviteRequestsController.swift @@ -198,7 +198,7 @@ public func inviteRequestsController(context: AccountContext, updatedPresentatio } else { 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) }) } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift index 71e44ef289..0d0ae20e0b 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerGridItem.swift @@ -190,13 +190,13 @@ final class MediaPickerGridItemNode: GridItemNode { func animateFadeIn(animateCheckNode: Bool, animateSpoilerNode: Bool) { 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.typeIconNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - self.durationNode.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.3) + self.durationNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) 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) } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift index d07169ce38..9035dd281e 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerSelectedListNode.swift @@ -285,18 +285,18 @@ private class MediaPickerSelectedItemNode: ASDisplayNode { self.isHidden = self.interaction?.hiddenMediaId == asset.uniqueIdentifier if !self.isHidden && wasHidden { 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 { - 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 { - 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 { - spoilerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + spoilerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) } } } diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift index 325cd4b8b1..3816e4b866 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryController.swift @@ -37,10 +37,16 @@ public func peerInfoProfilePhotos(context: AccountContext, peerId: EnginePeer.Id |> mapToSignal { peerView -> Signal<(Bool, [AvatarGalleryEntry])?, NoError>in if let peer = peerViewMainPeer(peerView) { var secondEntry: TelegramMediaImage? - if firstEntry.representations.first?.representation.isPersonal == true, let cachedData = peerView.cachedData as? CachedUserData, case let .known(photo) = cachedData.photo { - secondEntry = photo + var lastEntry: TelegramMediaImage? + 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) } else { return .single(nil) @@ -74,7 +80,7 @@ public func peerInfoProfilePhotosWithCache(context: AccountContext, peerId: Peer public enum AvatarGalleryEntry: Equatable { 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) { 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 .topImage - case let .image(id, _, representations, _, _, _, _, _, _, _): + case let .image(id, _, representations, _, _, _, _, _, _, _, _): if let last = representations.last { return .resource(last.representation.resource.id.stringRepresentation) } @@ -99,7 +105,7 @@ public enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(_, _, peer, _, _, _): return peer - case let .image(_, _, _, _, peer, _, _, _, _, _): + case let .image(_, _, _, _, peer, _, _, _, _, _, _): return peer } } @@ -108,7 +114,7 @@ public enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(representations, _, _, _, _, _): return representations - case let .image(_, _, representations, _, _, _, _, _, _, _): + case let .image(_, _, representations, _, _, _, _, _, _, _, _): return representations } } @@ -117,7 +123,7 @@ public enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(_, _, _, _, immediateThumbnailData, _): return immediateThumbnailData - case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _): + case let .image(_, _, _, _, _, _, _, _, immediateThumbnailData, _, _): return immediateThumbnailData } } @@ -126,7 +132,7 @@ public enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(_, videoRepresentations, _, _, _, _): return videoRepresentations - case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _): + case let .image(_, _, _, videoRepresentations, _, _, _, _, _, _, _): return videoRepresentations } } @@ -135,7 +141,7 @@ public enum AvatarGalleryEntry: Equatable { switch self { case let .topImage(_, _, _, indexData, _, _): return indexData - case let .image(_, _, _, _, _, _, indexData, _, _, _): + case let .image(_, _, _, _, _, _, indexData, _, _, _, _): return indexData } } @@ -148,8 +154,8 @@ public enum AvatarGalleryEntry: Equatable { } else { return false } - case let .image(lhsId, lhsImageReference, lhsRepresentations, lhsVideoRepresentations, lhsPeer, lhsDate, lhsIndexData, lhsMessageId, lhsImmediateThumbnailData, lhsCategory): - 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 { + 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, 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 } else { return false @@ -176,8 +182,8 @@ public func normalizeEntries(_ entries: [AvatarGalleryEntry]) -> [AvatarGalleryE let indexData = GalleryItemIndexData(position: index, totalCount: count) if case let .topImage(representations, videoRepresentations, peer, _, immediateThumbnailData, category) = entry { updatedEntries.append(.topImage(representations, videoRepresentations, peer, indexData, immediateThumbnailData, category)) - } else if case let .image(id, reference, representations, videoRepresentations, peer, date, _, messageId, immediateThumbnailData, category) = entry { - updatedEntries.append(.image(id, reference, representations, videoRepresentations, peer, date, indexData, messageId, immediateThumbnailData, category)) + } 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, isFallback)) } 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 { 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 { if case .known = peerPhoto { return [] @@ -235,7 +241,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { var initialMediaIds = Set() for entry in initialEntries { - if case let .image(mediaId, _, _, _, _, _, _, _, _, _) = entry { + if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) } } @@ -247,24 +253,24 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account photosCount += 1 for entry in initialEntries { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) - if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _) = entry { - result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil)) + if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _, _) = entry { + result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil, false)) index += 1 } } } 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 } } else { for photo in photos { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) 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 { - 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 } @@ -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] return Signal<(Bool, [AvatarGalleryEntry]), NoError>.single((false, initialEntries)) |> then( @@ -292,7 +298,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peer.id.namespace) { var initialMediaIds = Set() for entry in initialEntries { - if case let .image(mediaId, _, _, _, _, _, _, _, _, _) = entry { + if case let .image(mediaId, _, _, _, _, _, _, _, _, _, _) = entry { initialMediaIds.insert(mediaId) } } @@ -306,8 +312,8 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account photosCount += 1 for entry in initialEntries { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photosCount)) - if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _) = entry { - result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil)) + if case let .image(mediaId, imageReference, representations, videoRepresentations, peer, _, _, _, thumbnailData, _, _) = entry { + result.append(.image(mediaId, imageReference, representations, videoRepresentations, peer, nil, indexData, nil, thumbnailData, nil, false)) index += 1 } } @@ -317,7 +323,7 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account } 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 } } else { @@ -325,12 +331,15 @@ public func fetchedAvatarGalleryEntries(engine: TelegramEngine, account: Account if let secondEntry { 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 { let indexData = GalleryItemIndexData(position: index, totalCount: Int32(photos.count)) 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 { - 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 } @@ -441,8 +450,8 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr let isFirstTime = strongSelf.entries.isEmpty 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 { - let firstEntry = AvatarGalleryEntry.image(mediaId, imageReference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) + 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, false) entries.remove(at: 0) entries.insert(firstEntry, at: 0) } @@ -758,13 +767,13 @@ public class AvatarGalleryController: ViewController, StandalonePresentableContr if self.peer.id == self.context.account.peerId { } else { } - case let .image(_, reference, _, _, _, _, _, _, _, _): + case let .image(_, reference, _, _, _, _, _, _, _, _, _): if self.peer.id == self.context.account.peerId, let peerReference = PeerReference(self.peer) { if let reference = reference { let _ = (self.context.engine.accountData.updatePeerPhotoExisting(reference: reference) |> 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 { - 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) + 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, false) for (lhs, rhs) in zip(firstEntry.representations, updatedEntry.representations) { 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 let reference = reference { let _ = self.context.engine.accountData.removeAccountPhoto(reference: reference).start() diff --git a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift index 2f36df53a5..e770298e0e 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/AvatarGalleryItemFooterContentNode.swift @@ -107,7 +107,7 @@ final class AvatarGalleryItemFooterContentNode: GalleryFooterContentNode { var buttonText: String? var canShare = true 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) ?? "" if let date = date { dateText = humanReadableStringForTimestamp(strings: self.strings, dateTimeFormat: self.dateTimeFormat, timestamp: date).string diff --git a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift index 9b021ea44c..7e5d6a386c 100644 --- a/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift +++ b/submodules/PeerAvatarGalleryUI/Sources/PeerAvatarImageGalleryItem.swift @@ -106,7 +106,7 @@ class PeerAvatarImageGalleryItem: GalleryItem { switch self.entry { case let .topImage(representations, _, _, _, _, _): content = representations - case let .image(_, _, representations, _, _, _, _, _, _, _): + case let .image(_, _, representations, _, _, _, _, _, _, _, _): content = representations } @@ -268,7 +268,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { var id: Int64 var category: String? - if case let .image(mediaId, _, _, _, _, _, _, _, _, categoryValue) = entry { + if case let .image(mediaId, _, _, _, _, _, _, _, _, categoryValue, _) = entry { id = mediaId.id category = categoryValue } else { @@ -608,7 +608,7 @@ final class PeerAvatarImageGalleryItemNode: ZoomableContentGalleryItemNode { switch entry { case let .topImage(topRepresentations, _, _, _, _, _): representations = topRepresentations - case let .image(_, _, imageRepresentations, _, _, _, _, _, _, _): + case let .image(_, _, imageRepresentations, _, _, _, _, _, _, _, _): representations = imageRepresentations } diff --git a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift index ba18cf0991..4c54231775 100644 --- a/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift +++ b/submodules/PeerInfoAvatarListNode/Sources/PeerInfoAvatarListNode.swift @@ -15,6 +15,7 @@ import GalleryUI import UniversalMediaPlayer import RadialStatusNode import TelegramUIPreferences +import AvatarNode private class PeerInfoAvatarListLoadingStripNode: ASImageNode { private var currentInHierarchy = false @@ -90,7 +91,7 @@ private struct CustomListItemResourceId { public enum PeerInfoAvatarListItem: Equatable { case custom(ASDisplayNode) case topImage([ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) - case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?) + case image(TelegramMediaImageReference?, [ImageRepresentationWithReference], [VideoRepresentationWithReference], Data?, Bool) var id: MediaResourceId { switch self { @@ -99,7 +100,7 @@ public enum PeerInfoAvatarListItem: Equatable { case let .topImage(representations, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation return representation.resource.id - case let .image(_, representations, _, _): + case let .image(_, representations, _, _, _): let representation = largestImageRepresentation(representations.map { $0.representation }) ?? representations[representations.count - 1].representation return representation.resource.id } @@ -114,7 +115,7 @@ public enum PeerInfoAvatarListItem: Equatable { } else { return false } - } else if case let .image(_, rhsRepresentations, _, _) = self { + } else if case let .image(_, rhsRepresentations, _, _, _) = self { if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation) @@ -124,7 +125,7 @@ public enum PeerInfoAvatarListItem: Equatable { } else { return false } - } else if case let .image(_, lhsRepresentations, _, _) = self { + } else if case let .image(_, lhsRepresentations, _, _, _) = self { if case let .topImage(rhsRepresentations, _, _) = self { if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { @@ -132,7 +133,7 @@ public enum PeerInfoAvatarListItem: Equatable { } else { return false } - } else if case let .image(_, rhsRepresentations, _, _) = self { + } else if case let .image(_, rhsRepresentations, _, _, _) = self { if let lhsRepresentation = largestImageRepresentation(lhsRepresentations.map { $0.representation }), let rhsRepresentation = largestImageRepresentation(rhsRepresentations.map { $0.representation }) { return lhsRepresentation.isSemanticallyEqual(to: rhsRepresentation) @@ -153,7 +154,7 @@ public enum PeerInfoAvatarListItem: Equatable { return [] case let .topImage(representations, _, _): return representations - case let .image(_, representations, _, _): + case let .image(_, representations, _, _, _): return representations } } @@ -165,20 +166,29 @@ public enum PeerInfoAvatarListItem: Equatable { return [] case let .topImage(_, videoRepresentations, _): return videoRepresentations - case let .image(_, _, videoRepresentations, _): + case let .image(_, _, videoRepresentations, _, _): return videoRepresentations } } + var isFallback: Bool { + switch self { + case .custom, .topImage: + return false + case let .image(_, _, _, _, isFallback): + return isFallback + } + } + public init?(entry: AvatarGalleryEntry) { switch entry { case let .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 { 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 { id = id &+ resource.photoId } - case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): + case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _): representations = imageRepresentations videoRepresentations = videoRepresentationsValue immediateThumbnailData = immediateThumbnail @@ -523,6 +533,9 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { public let stripContainerNode: ASDisplayNode public let highlightContainerNode: ASDisplayNode private let setByYouNode: ImmediateTextNode + private let setByYouImageNode: ImageNode + private var setByYouTapRecognizer: UITapGestureRecognizer? + public private(set) var galleryEntries: [AvatarGalleryEntry] = [] private var items: [PeerInfoAvatarListItem] = [] private var itemNodes: [MediaResourceId: PeerInfoAvatarListItemNode] = [:] @@ -694,6 +707,10 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { self.setByYouNode.alpha = 0.0 self.setByYouNode.isUserInteractionEnabled = false + self.setByYouImageNode = ImageNode() + self.setByYouImageNode.alpha = 0.0 + self.setByYouImageNode.isUserInteractionEnabled = false + self.controlsContainerNode = ASDisplayNode() self.controlsContainerNode.isUserInteractionEnabled = false @@ -764,6 +781,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { self.controlsClippingNode.addSubnode(self.controlsContainerNode) self.controlsClippingOffsetNode.addSubnode(self.controlsClippingNode) self.stripContainerNode.addSubnode(self.setByYouNode) + self.stripContainerNode.addSubnode(self.setByYouImageNode) self.view.disablesInteractiveTransitionGestureRecognizerNow = { [weak self] in guard let strongSelf = self else { @@ -830,6 +848,19 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { 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? { 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?) { if let entry = entry, let index = self.galleryEntries.firstIndex(of: entry) { self.currentItemNode?.isHidden = index == self.currentIndex @@ -956,7 +998,7 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { } func setMainItem(_ item: PeerInfoAvatarListItem) { - guard case let .image(imageReference, _, _, _) = item else { + guard case let .image(imageReference, _, _, _, _) = item else { return } var items: [PeerInfoAvatarListItem] = [] @@ -966,16 +1008,16 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): entries.append(entry) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) - case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _, isFallback): if representations.isEmpty { continue } if imageReference == reference { 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 { 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 { - guard case let .image(imageReference, _, _, _) = item else { + guard case let .image(imageReference, _, _, _, _) = item else { return false } @@ -1009,13 +1051,13 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { case let .topImage(representations, videoRepresentations, _, _, immediateThumbnailData, _): entries.append(entry) items.append(.topImage(representations, videoRepresentations, immediateThumbnailData)) - case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _): + case let .image(_, reference, representations, videoRepresentations, _, _, _, _, immediateThumbnailData, _, isFallback): if representations.isEmpty { continue } if imageReference != reference { entries.append(entry) - items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData)) + items.append(.image(reference, representations, videoRepresentations, immediateThumbnailData, isFallback)) } else { deletedIndex = index } @@ -1081,8 +1123,8 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { } 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 { - let firstEntry = AvatarGalleryEntry.image(mediaId, reference, representations, videoRepresentations, peer, index, indexData, messageId, thumbnailData, caption) + 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, false) entries.remove(at: 0) entries.insert(firstEntry, at: 0) synchronous = true @@ -1264,13 +1306,39 @@ public final class PeerInfoAvatarListContainerNode: ASDisplayNode { if !self.items.isEmpty, self.currentIndex >= 0 && self.currentIndex < self.items.count { let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } let currentItem = self.items[self.currentIndex] + + var photoTitle: String? + var hasLink = false + var fallbackImageSignal: Signal? 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) - 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) self.setByYouNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - setByYouSize.width) / 2.0), y: 17.0), size: setByYouSize) + self.setByYouNode.isUserInteractionEnabled = hasLink } else { 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) } } diff --git a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift index dd6715a40e..4982bdab7e 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScanScreen.swift @@ -82,6 +82,7 @@ public final class QrCodeScanScreen: ViewController { public enum Subject { case authTransfer(activeSessionsContext: ActiveSessionsContext) case peer + case custom(info: String) } private let context: AccountContext @@ -97,9 +98,12 @@ public final class QrCodeScanScreen: ViewController { } public var showMyCode: () -> Void = {} + public var completion: (String?) -> Void = { _ in } private var codeResolved = false + private var validLayout: ContainerViewLayout? + public init(context: AccountContext, subject: QrCodeScanScreen.Subject) { self.context = context self.subject = subject @@ -126,7 +130,9 @@ public final class QrCodeScanScreen: ViewController { (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)) } else { #if DEBUG @@ -145,6 +151,16 @@ public final class QrCodeScanScreen: ViewController { 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() { self.showMyCode() } @@ -153,6 +169,16 @@ public final class QrCodeScanScreen: ViewController { 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?) { guard case let .authTransfer(activeSessionsContext) = self.subject else { 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() { 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) { super.containerLayoutUpdated(layout, transition: transition) + self.validLayout = layout + (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: title = "" text = "" + case let .custom(info): + title = presentationData.strings.AuthSessions_AddDevice_ScanTitle + text = info } self.titleNode = ImmediateTextNode() @@ -445,6 +486,8 @@ private final class QrCodeScanScreenNode: ViewControllerTracingNode, UIScrollVie filteredCodes = codes.filter { $0.message.hasPrefix("tg://") } case .peer: 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 strongSelf.codeWithError != code.message { diff --git a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift index 6c7175ce58..0473ee57dc 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/PrivacyAndSecurityController.swift @@ -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? = 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? = nil, + updatedTwoStepAuthData: (() -> Void)? = nil, + requestPublicPhotoSetup: (() -> Void)? = nil, + requestPublicPhotoRemove: (() -> Void)? = nil +) -> ViewController { let statePromise = ValuePromise(PrivacyAndSecurityControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: PrivacyAndSecurityControllerState()) let updateState: ((PrivacyAndSecurityControllerState) -> PrivacyAndSecurityControllerState) -> Void = { f in @@ -804,7 +819,11 @@ public func privacyAndSecurityController(context: AccountContext, initialSetting |> deliverOnMainQueue currentInfoDisposable.set(signal.start(next: { [weak currentInfoDisposable] info in 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 { let applySetting: Signal = privacySettingsPromise.get() |> filter { $0 != nil } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift index ca363fb63f..6e4f784f84 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsController.swift @@ -10,6 +10,7 @@ import ItemListUI import PresentationDataUtils import AccountContext import UndoUI +import ItemListPeerActionItem enum SelectivePrivacySettingsKind { case presence @@ -54,7 +55,10 @@ private final class SelectivePrivacySettingsControllerArguments { let updatePhoneDiscovery: ((Bool) -> 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.updateType = updateType self.openSelective = openSelective @@ -63,12 +67,16 @@ private final class SelectivePrivacySettingsControllerArguments { self.updateCallIntegrationEnabled = updateCallIntegrationEnabled self.updatePhoneDiscovery = updatePhoneDiscovery self.copyPhoneLink = copyPhoneLink + + self.setPublicPhoto = setPublicPhoto + self.removePublicPhoto = removePublicPhoto } } private enum SelectivePrivacySettingsSection: Int32 { case forwards case setting + case photo case peers case callsP2P case callsP2PPeers @@ -96,6 +104,9 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { case contacts(PresentationTheme, String, Bool) case nobody(PresentationTheme, String, Bool) case settingInfo(PresentationTheme, String, String) + case setPublicPhoto(PresentationTheme, String) + case removePublicPhoto(PresentationTheme, String, EnginePeer, TelegramMediaImage?) + case publicPhotoInfo(PresentationTheme, String) case exceptionsHeader(PresentationTheme, String) case disableFor(PresentationTheme, String, String) case enableFor(PresentationTheme, String, String) @@ -121,6 +132,8 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return SelectivePrivacySettingsSection.forwards.rawValue case .settingHeader, .everybody, .contacts, .nobody, .settingInfo: return SelectivePrivacySettingsSection.setting.rawValue + case .setPublicPhoto, .removePublicPhoto, .publicPhotoInfo: + return SelectivePrivacySettingsSection.photo.rawValue case .exceptionsHeader, .disableFor, .enableFor, .peersInfo: return SelectivePrivacySettingsSection.peers.rawValue case .callsP2PHeader, .callsP2PAlways, .callsP2PContacts, .callsP2PNever, .callsP2PInfo: @@ -150,42 +163,48 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return 5 case .settingInfo: return 6 - case .phoneDiscoveryHeader: + case .setPublicPhoto: return 7 - case .phoneDiscoveryEverybody: + case .removePublicPhoto: return 8 - case .phoneDiscoveryMyContacts: + case .publicPhotoInfo: return 9 - case .phoneDiscoveryInfo: + case .phoneDiscoveryHeader: return 10 - case .exceptionsHeader: + case .phoneDiscoveryEverybody: return 11 - case .disableFor: + case .phoneDiscoveryMyContacts: return 12 - case .enableFor: + case .phoneDiscoveryInfo: return 13 - case .peersInfo: + case .exceptionsHeader: return 14 - case .callsP2PHeader: + case .disableFor: return 15 - case .callsP2PAlways: + case .enableFor: return 16 - case .callsP2PContacts: + case .peersInfo: return 17 - case .callsP2PNever: + case .callsP2PHeader: return 18 - case .callsP2PInfo: + case .callsP2PAlways: return 19 - case .callsP2PDisableFor: + case .callsP2PContacts: return 20 - case .callsP2PEnableFor: + case .callsP2PNever: return 21 - case .callsP2PPeersInfo: + case .callsP2PInfo: return 22 - case .callsIntegrationEnabled: + case .callsP2PDisableFor: return 23 - case .callsIntegrationInfo: + case .callsP2PEnableFor: return 24 + case .callsP2PPeersInfo: + return 25 + case .callsIntegrationEnabled: + return 26 + case .callsIntegrationInfo: + return 27 } } @@ -222,7 +241,25 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return false } 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 } else { return false @@ -373,6 +410,17 @@ private enum SelectivePrivacySettingsEntry: ItemListNodeEntry { return ItemListTextItem(presentationData: presentationData, text: .markdown(text), sectionId: self.section, linkAction: { _ in 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): return ItemListSectionHeaderItem(presentationData: presentationData, text: text, sectionId: self.section) 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] = [] 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)) } + 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)) switch state.setting { @@ -671,7 +729,18 @@ private func selectivePrivacySettingsControllerEntries(presentationData: Present 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 var initialEnableFor: [PeerId: SelectivePrivacyPeer] = [:] @@ -965,12 +1034,29 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective 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) + }, 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 - |> map { presentationData, state, peer -> (ItemListControllerState, (ItemListNodeState, Any)) in + let signal = combineLatest( + 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) var phoneNumber = "" if case let .user(user) = peer { @@ -995,7 +1081,7 @@ func selectivePrivacySettingsController(context: AccountContext, kind: Selective 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 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)) } |> afterDisposed { diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index d01153ef8d..cc05a6fe61 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -427,6 +427,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[940666592] = { return Api.Message.parse_message($0) } dict[-1868117372] = { return Api.Message.parse_messageEmpty($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[-1781355374] = { return Api.MessageAction.parse_messageActionChannelCreate($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[-1885878744] = { return Api.User.parse_user($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[1326562017] = { return Api.UserProfilePhoto.parse_userProfilePhotoEmpty($0) } dict[164646985] = { return Api.UserStatus.parse_userStatusEmpty($0) } diff --git a/submodules/TelegramApi/Sources/Api11.swift b/submodules/TelegramApi/Sources/Api11.swift index 1062c0f949..38443bc319 100644 --- a/submodules/TelegramApi/Sources/Api11.swift +++ b/submodules/TelegramApi/Sources/Api11.swift @@ -988,6 +988,7 @@ public extension Api { } public extension Api { enum MessageAction: TypeConstructorDescription { + case messageActionAttachMenuBotAllowed case messageActionBotAllowed(domain: String) case messageActionChannelCreate(title: String) case messageActionChannelMigrateFrom(title: String, chatId: Int64) @@ -1027,6 +1028,12 @@ public extension Api { public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { + case .messageActionAttachMenuBotAllowed: + if boxed { + buffer.appendInt32(-404267113) + } + + break case .messageActionBotAllowed(let domain): if boxed { buffer.appendInt32(-1410748418) @@ -1302,6 +1309,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { switch self { + case .messageActionAttachMenuBotAllowed: + return ("messageActionAttachMenuBotAllowed", []) case .messageActionBotAllowed(let domain): return ("messageActionBotAllowed", [("domain", String(describing: domain))]) 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? { var _1: String? _1 = parseString(reader) diff --git a/submodules/TelegramApi/Sources/Api21.swift b/submodules/TelegramApi/Sources/Api21.swift index 22c1ee93a2..8ddbc6c472 100644 --- a/submodules/TelegramApi/Sources/Api21.swift +++ b/submodules/TelegramApi/Sources/Api21.swift @@ -586,13 +586,13 @@ public extension Api { } public extension Api { 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) { 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 { - buffer.appendInt32(-328384029) + buffer.appendInt32(-120378643) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) @@ -600,6 +600,7 @@ public extension Api { settings.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 << 22) != 0 {fallbackPhoto!.serialize(buffer, true)} notifySettings.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)} @@ -621,8 +622,8 @@ public extension Api { public func descriptionFields() -> (String, [(String, Any)]) { 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): - 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))]) + 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)), ("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() { _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() { - _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() { - _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? - _10 = reader.readInt32() + if Int(_1!) & Int(1 << 6) != 0 {_10 = reader.readInt32() } var _11: Int32? - if Int(_1!) & Int(1 << 11) != 0 {_11 = reader.readInt32() } + _11 = reader.readInt32() var _12: Int32? - if Int(_1!) & Int(1 << 14) != 0 {_12 = reader.readInt32() } - var _13: String? - if Int(_1!) & Int(1 << 15) != 0 {_13 = parseString(reader) } + if Int(_1!) & Int(1 << 11) != 0 {_12 = reader.readInt32() } + var _13: Int32? + if Int(_1!) & Int(1 << 14) != 0 {_13 = reader.readInt32() } var _14: String? - if Int(_1!) & Int(1 << 16) != 0 {_14 = parseString(reader) } - var _15: Api.ChatAdminRights? - if Int(_1!) & Int(1 << 17) != 0 {if let signature = reader.readInt32() { - _15 = Api.parse(reader, signature: signature) as? Api.ChatAdminRights - } } + if Int(_1!) & Int(1 << 15) != 0 {_14 = parseString(reader) } + var _15: String? + if Int(_1!) & Int(1 << 16) != 0 {_15 = parseString(reader) } 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 } } - 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() { - _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 _c2 = _2 != nil @@ -683,19 +688,20 @@ public extension Api { let _c4 = _4 != nil let _c5 = (Int(_1!) & Int(1 << 21) == 0) || _5 != nil let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil - let _c7 = _7 != nil - let _c8 = (Int(_1!) & Int(1 << 3) == 0) || _8 != nil - let _c9 = (Int(_1!) & Int(1 << 6) == 0) || _9 != nil - let _c10 = _10 != nil - let _c11 = (Int(_1!) & Int(1 << 11) == 0) || _11 != nil - let _c12 = (Int(_1!) & Int(1 << 14) == 0) || _12 != nil - let _c13 = (Int(_1!) & Int(1 << 15) == 0) || _13 != nil - let _c14 = (Int(_1!) & Int(1 << 16) == 0) || _14 != nil - let _c15 = (Int(_1!) & Int(1 << 17) == 0) || _15 != nil - let _c16 = (Int(_1!) & Int(1 << 18) == 0) || _16 != nil - let _c17 = (Int(_1!) & Int(1 << 19) == 0) || _17 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 { - 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) + let _c7 = (Int(_1!) & Int(1 << 22) == 0) || _7 != nil + let _c8 = _8 != nil + let _c9 = (Int(_1!) & Int(1 << 3) == 0) || _9 != nil + let _c10 = (Int(_1!) & Int(1 << 6) == 0) || _10 != nil + let _c11 = _11 != nil + let _c12 = (Int(_1!) & Int(1 << 11) == 0) || _12 != nil + let _c13 = (Int(_1!) & Int(1 << 14) == 0) || _13 != nil + let _c14 = (Int(_1!) & Int(1 << 15) == 0) || _14 != nil + let _c15 = (Int(_1!) & Int(1 << 16) == 0) || _15 != nil + let _c16 = (Int(_1!) & Int(1 << 17) == 0) || _16 != nil + let _c17 = (Int(_1!) & Int(1 << 18) == 0) || _17 != nil + let _c18 = (Int(_1!) & Int(1 << 19) == 0) || _18 != nil + 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 { return nil diff --git a/submodules/TelegramApi/Sources/Api29.swift b/submodules/TelegramApi/Sources/Api29.swift index cbd701521f..261ba33e7c 100644 --- a/submodules/TelegramApi/Sources/Api29.swift +++ b/submodules/TelegramApi/Sources/Api29.swift @@ -6559,12 +6559,13 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func toggleBotInAttachMenu(bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func toggleBotInAttachMenu(flags: Int32, bot: Api.InputUser, enabled: Api.Bool) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(451818415) + buffer.appendInt32(1777704297) + serializeInt32(flags, buffer: buffer, boxed: false) bot.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) var result: Api.Bool? if let signature = reader.readInt32() { @@ -7548,11 +7549,12 @@ public extension Api.functions.photos { } } public extension Api.functions.photos { - static func updateProfilePhoto(id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func updateProfilePhoto(flags: Int32, id: Api.InputPhoto) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(1926525996) + buffer.appendInt32(473782614) + serializeInt32(flags, buffer: buffer, boxed: false) 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) var result: Api.photos.Photo? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 3ccf651249..00b9ef5c33 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1254,7 +1254,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { 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 { if case let .channel(groupPeer) = groupPeer, let listenerLink = inviteLinks?.listenerLink, !groupPeer.hasPermission(.inviteMembers) { @@ -1362,7 +1362,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { 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 { @@ -1430,7 +1430,7 @@ public final class VoiceChatControllerImpl: ViewController, VoiceChatController } else { 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 } 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 { 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 diff --git a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift index 3a97dfaaf4..26659984f5 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/ReplyMarkupMessageAttribute.swift @@ -76,6 +76,9 @@ extension ReplyMarkupMessageAttribute { if (markupFlags & (1 << 2)) != 0 { flags.insert(.personal) } + if (markupFlags & (1 << 4)) != 0 { + flags.insert(.persistent) + } placeholder = apiPlaceholder case let .replyInlineMarkup(apiRows): rows = apiRows.map { ReplyMarkupRow(apiRow: $0) } diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index b5b061b10a..fc1aa0a66b 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -205,7 +205,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } 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 case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 05a08f0ea4..35aad645f2 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -106,6 +106,8 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .topicEdited(components: components)) case let.messageActionSuggestProfilePhoto(photo): return TelegramMediaAction(action: .suggestedProfilePhoto(image: telegramMediaImageFromApiPhoto(photo))) + case .messageActionAttachMenuBotAllowed: + return TelegramMediaAction(action: .attachMenuBotAllowed) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift index 1090962e01..4cba49c15f 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_CachedUserData.swift @@ -153,6 +153,7 @@ public final class CachedUserData: CachedPeerData { public let themeEmoticon: String? public let photo: CachedPeerProfilePhoto public let personalPhoto: CachedPeerProfilePhoto + public let fallbackPhoto: CachedPeerProfilePhoto public let premiumGiftOptions: [CachedPremiumGiftOption] public let voiceMessagesAvailable: Bool @@ -176,13 +177,14 @@ public final class CachedUserData: CachedPeerData { self.themeEmoticon = nil self.photo = .unknown self.personalPhoto = .unknown + self.fallbackPhoto = .unknown self.premiumGiftOptions = [] self.voiceMessagesAvailable = true self.peerIds = 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.botInfo = botInfo self.peerStatusSettings = peerStatusSettings @@ -198,6 +200,7 @@ public final class CachedUserData: CachedPeerData { self.themeEmoticon = themeEmoticon self.photo = photo self.personalPhoto = personalPhoto + self.fallbackPhoto = fallbackPhoto self.premiumGiftOptions = premiumGiftOptions 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.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.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.personalPhoto, forKey: "pphv") + encoder.encodeObject(self.fallbackPhoto, forKey: "fphv") encoder.encodeObjectArray(self.premiumGiftOptions, forKey: "pgo") encoder.encodeInt32(self.voiceMessagesAvailable ? 1 : 0, forKey: "vma") @@ -308,74 +313,78 @@ public final class CachedUserData: CachedPeerData { 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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 { - 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) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift index 4c45bab232..7ee7a13882 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_ReplyMarkupMessageAttribute.swift @@ -158,6 +158,7 @@ public struct ReplyMarkupMessageFlags: OptionSet { public static let setupReply = ReplyMarkupMessageFlags(rawValue: 1 << 2) public static let inline = ReplyMarkupMessageFlags(rawValue: 1 << 3) public static let fit = ReplyMarkupMessageFlags(rawValue: 1 << 4) + public static let persistent = ReplyMarkupMessageFlags(rawValue: 1 << 5) } public class ReplyMarkupMessageAttribute: MessageAttribute, Equatable { diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index a2730d989f..88e73af9e4 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -99,6 +99,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case topicCreated(title: String, iconColor: Int32, iconFileId: Int64?) case topicEdited(components: [ForumTopicEditComponent]) case suggestedProfilePhoto(image: TelegramMediaImage?) + case attachMenuBotAllowed public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -175,6 +176,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .topicEdited(components: decoder.decodeObjectArrayWithDecoderForKey("components")) case 30: self = .suggestedProfilePhoto(image: decoder.decodeObjectForKey("image") as? TelegramMediaImage) + case 31: + self = .attachMenuBotAllowed default: self = .unknown } @@ -326,6 +329,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { if let image = image { encoder.encodeObject(image, forKey: "image") } + case .attachMenuBotAllowed: + encoder.encodeInt32(31, forKey: "_rawValue") } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift index f224727208..5a1b3fa536 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/AccountData/TelegramEngineAccountData.swift @@ -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 { - 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 { @@ -52,7 +52,15 @@ public extension TelegramEngine { } public func removeAccountPhoto(reference: TelegramMediaImageReference?) -> Signal { - 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 { + return _internal_updateAccountPhoto(account: self.account, resource: resource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, fallback: true, mapResourceToAvatarSizes: mapResourceToAvatarSizes) + } + + public func removeFallbackPhoto(reference: TelegramMediaImageReference?) -> Signal { + return _internal_removeAccountPhoto(account: self.account, reference: reference, fallback: true) } public func setEmojiStatus(file: TelegramMediaFile?, expirationDate: Int32?) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift index cfd6ff6e33..17316edc48 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/AttachMenuBots.swift @@ -11,6 +11,7 @@ public final class AttachMenuBots: Equatable, Codable { case botIcons case peerTypes case hasSettings + case flags } 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 var rawValue: UInt32 @@ -94,20 +110,20 @@ public final class AttachMenuBots: Equatable, Codable { public let name: String public let icons: [IconName: TelegramMediaFile] public let peerTypes: PeerFlags - public let hasSettings: Bool + public let flags: Flags public init( peerId: PeerId, name: String, icons: [IconName: TelegramMediaFile], peerTypes: PeerFlags, - hasSettings: Bool + flags: Flags ) { self.peerId = peerId self.name = name self.icons = icons self.peerTypes = peerTypes - self.hasSettings = hasSettings + self.flags = flags } public static func ==(lhs: Bot, rhs: Bot) -> Bool { @@ -123,7 +139,7 @@ public final class AttachMenuBots: Equatable, Codable { if lhs.peerTypes != rhs.peerTypes { return false } - if lhs.hasSettings != rhs.hasSettings { + if lhs.flags != rhs.flags { return false } 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) 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 { @@ -164,7 +185,7 @@ public final class AttachMenuBots: Equatable, Codable { 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] = [] for bot in bots { switch bot { - case let .attachMenuBot(flags, botId, name, apiPeerTypes, botIcons): + case let .attachMenuBot(apiFlags, botId, name, apiPeerTypes, botIcons): var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:] for icon in botIcons { switch icon { @@ -302,7 +323,14 @@ func managedSynchronizeAttachMenuBots(postbox: Postbox, network: Network, force: 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 { +func _internal_addBotToAttachMenu(postbox: Postbox, network: Network, botId: PeerId, allowWrite: Bool) -> Signal { return postbox.transaction { transaction -> Signal in guard let peer = transaction.getPeer(botId), let inputUser = apiInputUser(peer) else { 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 switch value { 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 { 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 switch value { case .boolTrue: @@ -406,14 +438,14 @@ public struct AttachMenuBot { public let shortName: String public let icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] 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.shortName = shortName self.icons = icons self.peerTypes = peerTypes - self.hasSettings = hasSettings + self.flags = flags } } @@ -425,7 +457,7 @@ func _internal_attachMenuBots(postbox: Postbox) -> Signal<[AttachMenuBot], NoErr var resultBots: [AttachMenuBot] = [] for bot in cachedBots { 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 @@ -440,7 +472,7 @@ public func _internal_getAttachMenuBot(postbox: Postbox, network: Network, botId return postbox.transaction { transaction -> Signal in if cached, let cachedBots = cachedAttachMenuBots(transaction: transaction)?.bots { 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 { - case let .attachMenuBot(flags, _, name, apiPeerTypes, botIcons): + case let .attachMenuBot(apiFlags, _, name, apiPeerTypes, botIcons): var icons: [AttachMenuBots.Bot.IconName: TelegramMediaFile] = [:] for icon in botIcons { switch icon { @@ -499,7 +531,14 @@ public func _internal_getAttachMenuBot(postbox: Postbox, network: Network, botId 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)) } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift index d2f61b6b56..f1c1afa03f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/TelegramEngineMessages.swift @@ -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) } - public func addBotToAttachMenu(botId: PeerId) -> Signal { - return _internal_addBotToAttachMenu(postbox: self.account.postbox, network: self.account.network, botId: botId) + public func addBotToAttachMenu(botId: PeerId, allowWrite: Bool) -> Signal { + return _internal_addBotToAttachMenu(postbox: self.account.postbox, network: self.account.network, botId: botId, allowWrite: allowWrite) } public func removeBotFromAttachMenu(botId: PeerId) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift index 67fd8f180c..b71521b026 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/PeerPhotoUpdater.swift @@ -14,8 +14,8 @@ public enum UploadPeerPhotoError { case generic } -func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - 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) +func _internal_updateAccountPhoto(account: Account, resource: MediaResource?, videoResource: MediaResource?, videoStartTimestamp: Double?, fallback: Bool, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + 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 { @@ -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?, video: Signal? = nil, videoStartTimestamp: Double? = nil, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { - 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) +func _internal_updatePeerPhoto(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peerId: PeerId, photo: Signal?, video: Signal? = nil, videoStartTimestamp: Double? = nil, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { + 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, photo: Signal?, video: Signal?, videoStartTimestamp: Double?, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { +func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, stateManager: AccountStateManager?, accountPeerId: PeerId, peer: Signal, photo: Signal?, video: Signal?, videoStartTimestamp: Double?, fallback: Bool = false, customPeerPhotoMode: SetCustomPeerPhotoMode? = nil, mapResourceToAvatarSizes: @escaping (MediaResource, [TelegramMediaImageRepresentation]) -> Signal<[Int: Data], NoError>) -> Signal { return peer |> mapError { _ -> UploadPeerPhotoError in } |> mapToSignal { peer -> Signal in @@ -150,9 +150,11 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state flags |= (1 << 2) } } - let request: Signal if peer.id == accountPeerId { + if fallback { + flags |= (1 << 3) + } request = network.request(Api.functions.photos.uploadProfilePhoto(flags: flags, file: file, video: videoFile, videoStartTs: videoStartTimestamp)) } else if let inputUser = apiInputUser(peer) { if let customPeerPhotoMode = customPeerPhotoMode { @@ -177,8 +179,10 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state |> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?, MediaResource?), UploadPeerPhotoError> in var representations: [TelegramMediaImageRepresentation] = [] var videoRepresentations: [TelegramMediaImage.VideoRepresentation] = [] + var image: TelegramMediaImage? switch photo { case let .photo(apiPhoto, _): + image = telegramMediaImageFromApiPhoto(apiPhoto) switch apiPhoto { case .photoEmpty: break @@ -225,7 +229,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state if let peer = transaction.getPeer(peer.id) { updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in if let peer = peer as? TelegramUser { - if customPeerPhotoMode == .suggest { + if customPeerPhotoMode == .suggest || fallback { return peer } else { return peer.withUpdatedPhoto(representations) @@ -234,6 +238,16 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state 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) } |> mapError { _ -> UploadPeerPhotoError in } @@ -327,7 +341,11 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state if let _ = peer as? TelegramUser { let request: Signal 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) { let flags: Int32 = (1 << 4) 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 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([]) } |> mapError { _ -> UploadPeerPhotoError in } } @@ -405,7 +432,7 @@ func _internal_updatePeerPhotoInternal(postbox: Postbox, network: Network, state func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMediaImageReference) -> Signal { switch reference { 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 in return .complete() } @@ -419,12 +446,12 @@ func _internal_updatePeerPhotoExisting(network: Network, reference: TelegramMedi } } -func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal { +func _internal_removeAccountPhoto(account: Account, reference: TelegramMediaImageReference?, fallback: Bool) -> Signal { if let reference = reference { switch reference { case let .cloud(imageId, accessHash, 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 return .single([]) } @@ -436,7 +463,29 @@ func _internal_removeAccountPhoto(network: Network, reference: TelegramMediaImag } } } else { - let api = Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty) - return network.request(api) |> map { _ in } |> retryRequest + var flags: Int32 = 0 + 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 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() + } + } } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift index 7f84024e62..6856e9b53b 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/UpdateCachedPeerData.swift @@ -212,7 +212,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee } switch fullUser { - case let .userFull(_, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _): + case let .userFull(_, _, _, _, _, _, _, userFullNotifySettings, _, _, _, _, _, _, _, _, _, _): 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) { return user @@ -230,7 +230,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee previous = CachedUserData() } 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 isBlocked = (userFullFlags & (1 << 0)) != 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 photo = profilePhoto.flatMap { telegramMediaImageFromApiPhoto($0) } + let fallbackPhoto = fallbackPhoto.flatMap { telegramMediaImageFromApiPhoto($0) } let premiumGiftOptions: [CachedPremiumGiftOption] if let userPremiumGiftOptions = userPremiumGiftOptions { @@ -270,6 +271,7 @@ func _internal_fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPee .withUpdatedThemeEmoticon(userFullThemeEmoticon) .withUpdatedPhoto(.known(photo)) .withUpdatedPersonalPhoto(.known(personalPhoto)) + .withUpdatedFallbackPhoto(.known(fallbackPhoto)) .withUpdatedPremiumGiftOptions(premiumGiftOptions) .withUpdatedVoiceMessagesAvailable(voiceMessagesAvailable) } diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 0d14db4cdb..4935c5ac9c 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -51,6 +51,7 @@ public enum PresentationResourceKey: Int32 { case itemListCreateGroupIcon case itemListAddExceptionIcon case itemListAddPhoneIcon + case itemListAddPhotoIcon case itemListClearInputIcon case itemListStickerItemUnreadDot case itemListVerifiedPeerIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift index 9a1a21b42b..9d009cdc17 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesItemList.swift @@ -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? { return theme.image(PresentationResourceKey.itemListClearInputIcon.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Components/Search Bar/Clear"), color: theme.list.inputClearButtonColor) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 5e94dfc092..3605d06c1c 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -831,6 +831,8 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { attributedString = NSAttributedString(string: strings.Notification_SuggestedProfileVideo, font: titleFont, textColor: primaryTextColor) } + case .attachMenuBotAllowed: + attributedString = NSAttributedString(string: strings.Notification_BotWriteAllowed, font: titleFont, textColor: primaryTextColor) case .unknown: attributedString = nil } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index dc08ea4b2d..ce9b23f0d4 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -9518,7 +9518,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let hapticFeedback = HapticFeedback() 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 { return true } @@ -12063,8 +12063,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = (context.engine.messages.getAttachMenuBot(botId: botId) |> deliverOnMainQueue).start(next: { bot in 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 }, completed: { diff --git a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift index efeee55908..06f6adb241 100644 --- a/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatEntityKeyboardInputNode.swift @@ -1993,7 +1993,9 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { 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 { 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) @@ -2093,8 +2095,8 @@ private final class EmojiContentPeekBehaviorImpl: EmojiContentPeekBehavior { guard let view = view else { 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 { return } diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 13b52b291b..f7e3cc0d7f 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -632,8 +632,8 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur let choose = filterChooseTypes(choose, peerTypes: bot.peerTypes) let botPeer = EnginePeer(bot.peer) - let controller = addWebAppToAttachmentController(context: context, peerName: botPeer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder), icons: bot.icons, completion: { - let _ = (context.engine.messages.addBotToAttachMenu(botId: peerId) + 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, allowWrite: allowWrite) |> deliverOnMainQueue).start(error: { _ in presentError(presentationData.strings.WebApp_AddToAttachmentUnavailableError) }, completed: { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 3ddb660643..b842fb8166 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -399,7 +399,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { if peer.isDeleted { overrideImage = .deletedIcon } 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) } overrideImage = AvatarNodeImageOverride.none @@ -499,7 +499,7 @@ final class PeerInfoAvatarTransformContainerNode: ASDisplayNode { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { videoId = videoId &+ resource.photoId } - case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): + case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _): representations = imageRepresentations videoRepresentations = videoRepresentationsValue immediateThumbnailData = immediateThumbnail @@ -784,7 +784,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { if canEdit, peer.profileImageRepresentations.isEmpty { overrideImage = .editAvatarIcon(forceNone: true) } 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) } overrideImage = canEdit ? .editAvatarIcon(forceNone: true) : AvatarNodeImageOverride.none @@ -831,7 +831,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { if let resource = videoRepresentations.first?.representation.resource as? CloudPhotoSizeMediaResource { id = id &+ resource.photoId } - case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail): + case let .image(reference, imageRepresentations, videoRepresentationsValue, immediateThumbnail, _): representations = imageRepresentations videoRepresentations = videoRepresentationsValue immediateThumbnailData = immediateThumbnail diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index a680bf711d..edb45767ab 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1359,7 +1359,8 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL let ItemSuggest = 0 let ItemCustom = 1 let ItemReset = 2 - let ItemDelete = 3 + let ItemInfo = 3 + let ItemDelete = 4 let compactName = EnginePeer(user).compactDisplayTitle @@ -1381,6 +1382,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL interaction.resetCustomPhoto() })) } + items[.peerDataSettings]!.append(PeerInfoScreenCommentItem(id: ItemInfo, text: presentationData.strings.UserInfo_CustomPhotoInfo(compactName).string)) if data.isContact { 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 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 } @@ -6754,7 +6756,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate 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) - if case .suggest = mode { + if [.suggest, .fallback].contains(mode) { } else { self.state = self.state.withUpdatingAvatar(.image(representation)) } @@ -6767,9 +6769,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let postbox = self.context.account.postbox let signal: Signal if self.isSettings { - signal = self.context.engine.accountData.updateAccountPhoto(resource: resource, videoResource: nil, videoStartTimestamp: nil, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: postbox, resource: resource, representations: representations) - }) + if case .fallback = mode { + 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 { 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) @@ -6803,7 +6811,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in 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) 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 { self.state = self.state.withUpdatingAvatar(.image(representation)) } @@ -6927,9 +6935,15 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.updateAvatarDisposable.set((signal |> mapToSignal { videoResource -> Signal in if isSettings { - return context.engine.accountData.updateAccountPhoto(resource: photoResource, videoResource: videoResource, videoStartTimestamp: videoStartTimestamp, mapResourceToAvatarSizes: { resource, representations in - return mapResourceToAvatarSizes(postbox: account.postbox, resource: resource, representations: representations) - }) + if case .fallback = mode { + 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 { 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) @@ -6962,7 +6976,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate let _ = (strongSelf.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: strongSelf.peerId)) |> deliverOnMainQueue).start(next: { [weak self] peer in 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 suggest 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 { return } var currentIsVideo = false 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 } @@ -7009,7 +7024,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate legacyController.bind(controller: navigationController) 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 if !peer.profileImageRepresentations.isEmpty { @@ -7026,7 +7041,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate } return true }) - strongSelf.controller?.present(controller, in: .window(.root)) + (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.present(controller, in: .window(.root)) return controller } @@ -7040,6 +7055,10 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate hasDeleteButton = hasPhotos && !fromGallery } else if case .custom = mode { 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? @@ -7076,7 +7095,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self?.updateProfilePhoto(result, mode: mode) })) controller.navigationPresentation = .modal - strongSelf.controller?.push(controller) + (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.push(controller) if fromGallery { 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: { 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: { commit?() }) - strongSelf.controller?.presentInGlobalOverlay(controller) + (strongSelf.controller?.navigationController?.topViewController as? ViewController)?.presentInGlobalOverlay(controller) } } } @@ -7119,55 +7138,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate return } - let proceed = { - 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)) + strongSelf.openAvatarRemoval(mode: mode, peer: peer, item: item) } mixin.didDismiss = { [weak legacyController] in 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 + 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() { guard let data = self.data, let groupPeer = data.peer, let controller = self.controller else { 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 { 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 { 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) { diff --git a/submodules/UndoUI/Sources/UndoOverlayController.swift b/submodules/UndoUI/Sources/UndoOverlayController.swift index 10868dea54..a64c9bb671 100644 --- a/submodules/UndoUI/Sources/UndoOverlayController.swift +++ b/submodules/UndoUI/Sources/UndoOverlayController.swift @@ -22,7 +22,7 @@ public enum UndoOverlayContent { case chatRemovedFromFolder(chatTitle: String, folderTitle: String) case messagesUnpinned(title: String, text: String, undo: Bool, isHidden: 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 banned(text: String) case importedMessage(text: String) diff --git a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift index 5abae7fa30..35b3a240a8 100644 --- a/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift +++ b/submodules/UndoUI/Sources/UndoOverlayControllerNode.swift @@ -518,7 +518,7 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { displayUndo = false 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.iconNode = nil self.iconCheckNode = nil @@ -536,11 +536,10 @@ final class UndoOverlayControllerNode: ViewControllerTracingNode { if let action = action { displayUndo = true undoText = action - self.originalRemainingSeconds = 5 } else { displayUndo = false - self.originalRemainingSeconds = 3 } + self.originalRemainingSeconds = duration case let .audioRate(slowdown, text): self.avatarNode = nil self.iconNode = nil diff --git a/submodules/WebUI/BUILD b/submodules/WebUI/BUILD index b8b9bbadc3..8f10d3c0a4 100644 --- a/submodules/WebUI/BUILD +++ b/submodules/WebUI/BUILD @@ -28,6 +28,8 @@ swift_library( "//submodules/BotPaymentsUI:BotPaymentsUI", "//submodules/PromptUI:PromptUI", "//submodules/PhoneNumberFormat:PhoneNumberFormat", + "//submodules/QrCodeUI:QrCodeUI", + "//submodules/InstantPageUI:InstantPageUI", ], visibility = [ "//visibility:public", diff --git a/submodules/WebUI/Sources/WebAppAlertContentNode.swift b/submodules/WebUI/Sources/WebAppAlertContentNode.swift index 652904f5ae..244e63734f 100644 --- a/submodules/WebUI/Sources/WebAppAlertContentNode.swift +++ b/submodules/WebUI/Sources/WebAppAlertContentNode.swift @@ -10,6 +10,16 @@ import TelegramUIPreferences import AccountContext import AppBundle 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 let strings: PresentationStrings @@ -20,6 +30,9 @@ private final class WebAppAlertContentNode: AlertContentNode { private let appIconNode: ASImageNode private let iconNode: ASImageNode + private let allowWriteCheckNode: InteractiveCheckNode + private let allowWriteLabelNode: ASTextNode + private let actionNodesSeparator: ASDisplayNode private let actionNodes: [TextAlertContentActionNode] private let actionVerticalSeparators: [ASDisplayNode] @@ -32,7 +45,13 @@ private final class WebAppAlertContentNode: AlertContentNode { 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.peerName = peerName @@ -54,6 +73,12 @@ private final class WebAppAlertContentNode: AlertContentNode { self.iconNode = ASImageNode() self.iconNode.displaysAsynchronously = false 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.isLayerBacked = true @@ -77,6 +102,12 @@ private final class WebAppAlertContentNode: AlertContentNode { self.addSubnode(self.textNode) self.addSubnode(self.appIconNode) self.addSubnode(self.iconNode) + + if requestWriteAccess { + self.addSubnode(self.allowWriteCheckNode) + self.addSubnode(self.allowWriteLabelNode) + } + self.addSubnode(self.actionNodesSeparator) @@ -88,6 +119,12 @@ private final class WebAppAlertContentNode: AlertContentNode { self.addSubnode(separatorNode) } + self.allowWriteCheckNode.valueChanged = { [weak self] value in + if let strongSelf = self { + strongSelf.allowWriteAccess = !strongSelf.allowWriteAccess + } + } + self.updateTheme(theme) if let peerIcon = self.peerIcon { @@ -109,12 +146,26 @@ private final class WebAppAlertContentNode: AlertContentNode { 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) { 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.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 for actionNode in self.actionNodes { actionNode.updateTheme(theme) @@ -146,6 +197,23 @@ private final class WebAppAlertContentNode: AlertContentNode { let textSize = self.textNode.measure(size) 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 var minActionsWidth: CGFloat = 0.0 @@ -180,7 +248,7 @@ private final class WebAppAlertContentNode: AlertContentNode { } 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))) @@ -227,7 +295,7 @@ private final class WebAppAlertContentNode: AlertContentNode { 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.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 theme = presentationData.theme let strings = presentationData.strings @@ -251,10 +319,10 @@ public func addWebAppToAttachmentController(context: AccountContext, peerName: S }), TextAlertAction(type: .defaultAction, title: presentationData.strings.WebApp_AddToAttachmentAdd, action: { 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!) dismissImpl = { [weak controller] animated in diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index 18d172acb6..631fbb1c85 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -21,6 +21,8 @@ import MoreButtonNode import BotPaymentsUI import PromptUI import PhoneNumberFormat +import QrCodeUI +import InstantPageUI private let durgerKingBotIds: [Int64] = [5104055776, 2200339955] @@ -680,10 +682,27 @@ public final class WebAppController: ViewController, AttachmentContainable { } case "web_app_open_link": if let json = json, let url = json["url"] as? String { + let tryInstantView = json["try_instant_view"] as? Bool ?? false let currentTimestamp = CACurrentMediaTime() if let lastTouchTimestamp = self.webView?.lastTouchTimestamp, currentTimestamp < lastTouchTimestamp + 10.0 { 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": @@ -799,8 +818,28 @@ public final class WebAppController: ViewController, AttachmentContainable { if let json = json, let needConfirmation = json["need_confirmation"] as? Bool { self.needDismissConfirmation = needConfirmation } - case "web_app_request_phone": - break + case "web_app_open_scan_qr_popup": + 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: break } @@ -930,6 +969,22 @@ public final class WebAppController: ViewController, AttachmentContainable { } 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 { @@ -1067,7 +1122,7 @@ public final class WebAppController: ViewController, AttachmentContainable { 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 return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Settings"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, _ in