diff --git a/Images.xcassets/Avatar/EditAvatarIcon.imageset/Contents.json b/Images.xcassets/Avatar/EditAvatarIcon.imageset/Contents.json new file mode 100644 index 0000000000..253c03f5f9 --- /dev/null +++ b/Images.xcassets/Avatar/EditAvatarIcon.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "SettingsCameraIcon@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "SettingsCameraIcon@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@2x.png b/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@2x.png new file mode 100644 index 0000000000..370abd3c85 Binary files /dev/null and b/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@2x.png differ diff --git a/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@3x.png b/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@3x.png new file mode 100644 index 0000000000..9be5ef3d09 Binary files /dev/null and b/Images.xcassets/Avatar/EditAvatarIcon.imageset/SettingsCameraIcon@3x.png differ diff --git a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/Contents.json b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/Contents.json index 868d3a72aa..ef12df2de0 100644 --- a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/Contents.json +++ b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/Contents.json @@ -6,11 +6,12 @@ }, { "idiom" : "universal", - "filename" : "SecretPhotoFire@2x.png", + "filename" : "SecretMediaIcon@2x.png", "scale" : "2x" }, { "idiom" : "universal", + "filename" : "SecretMediaIcon@3x.png", "scale" : "3x" } ], diff --git a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@2x.png b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@2x.png new file mode 100644 index 0000000000..9c98438f62 Binary files /dev/null and b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@2x.png differ diff --git a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@3x.png b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@3x.png new file mode 100644 index 0000000000..cbf683dca1 Binary files /dev/null and b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretMediaIcon@3x.png differ diff --git a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretPhotoFire@2x.png b/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretPhotoFire@2x.png deleted file mode 100644 index 803133c351..0000000000 Binary files a/Images.xcassets/Chat/Message/SecretMediaIcon.imageset/SecretPhotoFire@2x.png and /dev/null differ diff --git a/Images.xcassets/Open In/Maps.imageset/Contents.json b/Images.xcassets/Open In/Maps.imageset/Contents.json index d5bf557e5a..d46b1751fe 100644 --- a/Images.xcassets/Open In/Maps.imageset/Contents.json +++ b/Images.xcassets/Open In/Maps.imageset/Contents.json @@ -6,12 +6,12 @@ }, { "idiom" : "universal", - "filename" : "ShareSearchIcon@2x.png", + "filename" : "Maps@2x.png", "scale" : "2x" }, { "idiom" : "universal", - "filename" : "ShareSearchIcon@3x.png", + "filename" : "Maps@3x.png", "scale" : "3x" } ], diff --git a/Images.xcassets/Open In/Maps.imageset/Maps@2x.png b/Images.xcassets/Open In/Maps.imageset/Maps@2x.png new file mode 100644 index 0000000000..10ff707a5c Binary files /dev/null and b/Images.xcassets/Open In/Maps.imageset/Maps@2x.png differ diff --git a/Images.xcassets/Open In/Maps.imageset/Maps@3x.png b/Images.xcassets/Open In/Maps.imageset/Maps@3x.png new file mode 100644 index 0000000000..ceb3c880ee Binary files /dev/null and b/Images.xcassets/Open In/Maps.imageset/Maps@3x.png differ diff --git a/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@2x.png b/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@2x.png deleted file mode 100644 index 289227ed05..0000000000 Binary files a/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@2x.png and /dev/null differ diff --git a/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@3x.png b/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@3x.png deleted file mode 100644 index 837458279a..0000000000 Binary files a/Images.xcassets/Open In/Maps.imageset/ShareSearchIcon@3x.png and /dev/null differ diff --git a/Images.xcassets/Open In/Safari.imageset/Contents.json b/Images.xcassets/Open In/Safari.imageset/Contents.json index af75ade06d..be50f3de3e 100644 --- a/Images.xcassets/Open In/Safari.imageset/Contents.json +++ b/Images.xcassets/Open In/Safari.imageset/Contents.json @@ -6,7 +6,7 @@ }, { "idiom" : "universal", - "filename" : "OpenInSafariIcon@2x.png", + "filename" : "Safari@2x.png", "scale" : "2x" }, { diff --git a/Images.xcassets/Open In/Safari.imageset/OpenInSafariIcon@2x.png b/Images.xcassets/Open In/Safari.imageset/OpenInSafariIcon@2x.png deleted file mode 100644 index 4cecc92b07..0000000000 Binary files a/Images.xcassets/Open In/Safari.imageset/OpenInSafariIcon@2x.png and /dev/null differ diff --git a/Images.xcassets/Open In/Safari.imageset/Safari@2x.png b/Images.xcassets/Open In/Safari.imageset/Safari@2x.png new file mode 100644 index 0000000000..f287725682 Binary files /dev/null and b/Images.xcassets/Open In/Safari.imageset/Safari@2x.png differ diff --git a/Images.xcassets/Open In/Safari.imageset/Safari@3x.png b/Images.xcassets/Open In/Safari.imageset/Safari@3x.png index ea18cf1ea2..85e12f5b4b 100644 Binary files a/Images.xcassets/Open In/Safari.imageset/Safari@3x.png and b/Images.xcassets/Open In/Safari.imageset/Safari@3x.png differ diff --git a/TelegramUI.xcodeproj/project.pbxproj b/TelegramUI.xcodeproj/project.pbxproj index dbabc1e5ee..b7c69eed02 100644 --- a/TelegramUI.xcodeproj/project.pbxproj +++ b/TelegramUI.xcodeproj/project.pbxproj @@ -7,16 +7,16 @@ objects = { /* Begin PBXBuildFile section */ - 09310D1D213BC5DE0020033A /* anim_read.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D14213BC5DE0020033A /* anim_read.json */; }; - 09310D1E213BC5DE0020033A /* anim_pin.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D15213BC5DE0020033A /* anim_pin.json */; }; - 09310D1F213BC5DE0020033A /* anim_unmute.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D16213BC5DE0020033A /* anim_unmute.json */; }; - 09310D20213BC5DE0020033A /* anim_unpin.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D17213BC5DE0020033A /* anim_unpin.json */; }; - 09310D21213BC5DE0020033A /* anim_unread.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D18213BC5DE0020033A /* anim_unread.json */; }; - 09310D22213BC5DE0020033A /* anim_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D19213BC5DE0020033A /* anim_delete.json */; }; - 09310D23213BC5DE0020033A /* anim_ungroup.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1A213BC5DE0020033A /* anim_ungroup.json */; }; - 09310D24213BC5DE0020033A /* anim_group.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1B213BC5DE0020033A /* anim_group.json */; }; - 09310D25213BC5DE0020033A /* anim_mute.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1C213BC5DE0020033A /* anim_mute.json */; }; - 09310D29213BD8810020033A /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09310D28213BD8810020033A /* Lottie.framework */; }; + 09310D2B213ECC840020033A /* Lottie.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 09310D2A213ECC830020033A /* Lottie.framework */; }; + 09310D2C213ED5FB0020033A /* anim_read.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D14213BC5DE0020033A /* anim_read.json */; }; + 09310D2D213ED5FB0020033A /* anim_pin.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D15213BC5DE0020033A /* anim_pin.json */; }; + 09310D2E213ED5FB0020033A /* anim_unmute.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D16213BC5DE0020033A /* anim_unmute.json */; }; + 09310D2F213ED5FB0020033A /* anim_unpin.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D17213BC5DE0020033A /* anim_unpin.json */; }; + 09310D30213ED5FB0020033A /* anim_unread.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D18213BC5DE0020033A /* anim_unread.json */; }; + 09310D31213ED5FC0020033A /* anim_delete.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D19213BC5DE0020033A /* anim_delete.json */; }; + 09310D32213ED5FC0020033A /* anim_ungroup.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1A213BC5DE0020033A /* anim_ungroup.json */; }; + 09310D33213ED5FC0020033A /* anim_group.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1B213BC5DE0020033A /* anim_group.json */; }; + 09310D34213ED5FC0020033A /* anim_mute.json in Resources */ = {isa = PBXBuildFile; fileRef = 09310D1C213BC5DE0020033A /* anim_mute.json */; }; 0941A9A0210B057200EBE194 /* OpenInActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A99F210B057200EBE194 /* OpenInActionSheetController.swift */; }; 0941A9A4210B0E2E00EBE194 /* OpenInAppIconResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */; }; 0941A9A6210B822D00EBE194 /* OpenInOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */; }; @@ -1026,6 +1026,7 @@ 09310D1C213BC5DE0020033A /* anim_mute.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = anim_mute.json; sourceTree = ""; }; 09310D26213BD84E0020033A /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 09310D28213BD8810020033A /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 09310D2A213ECC830020033A /* Lottie.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Lottie.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0941A99F210B057200EBE194 /* OpenInActionSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInActionSheetController.swift; sourceTree = ""; }; 0941A9A3210B0E2E00EBE194 /* OpenInAppIconResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInAppIconResources.swift; sourceTree = ""; }; 0941A9A5210B822D00EBE194 /* OpenInOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenInOptions.swift; sourceTree = ""; }; @@ -2115,7 +2116,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 09310D29213BD8810020033A /* Lottie.framework in Frameworks */, + 09310D2B213ECC840020033A /* Lottie.framework in Frameworks */, D00ACA4B20222C280045D427 /* libtgvoip.framework in Frameworks */, D07BCBFE1F2B792300ED97AA /* LegacyComponents.framework in Frameworks */, 094981C62138D73B00A10660 /* Vision.framework in Frameworks */, @@ -2999,6 +3000,7 @@ D08D45281D5E340200A7428A /* Frameworks */ = { isa = PBXGroup; children = ( + 09310D2A213ECC830020033A /* Lottie.framework */, 09310D28213BD8810020033A /* Lottie.framework */, 09310D26213BD84E0020033A /* Lottie.framework */, 094981C52138D73B00A10660 /* Vision.framework */, @@ -4554,24 +4556,31 @@ 09874E5121078FA100E190B8 /* Instagram.html in Resources */, 09874E5221078FA100E190B8 /* Twitch.html in Resources */, 09874E5321078FA100E190B8 /* TwitchUserScript.js in Resources */, + 09310D34213ED5FC0020033A /* anim_mute.json in Resources */, 09874E5421078FA100E190B8 /* Vimeo.html in Resources */, + 09310D2C213ED5FB0020033A /* anim_read.json in Resources */, 09874E5521078FA100E190B8 /* VimeoUserScript.js in Resources */, 09874E5621078FA100E190B8 /* Youtube.html in Resources */, 09874E5721078FA100E190B8 /* YoutubeUserScript.js in Resources */, D0EB42051F3143AB00838FE6 /* LegacyComponentsResources.bundle in Resources */, D0E9BAA21F056F4C00F079A4 /* stp_card_discover@3x.png in Resources */, D0E9BAB01F056F4C00F079A4 /* stp_card_mastercard@3x.png in Resources */, + 09310D32213ED5FC0020033A /* anim_ungroup.json in Resources */, D0E9BAA31F056F4C00F079A4 /* stp_card_discover_template@2x.png in Resources */, D0E9BAB51F056F4C00F079A4 /* stp_card_visa@2x.png in Resources */, D0E9BA941F056F4C00F079A4 /* stp_card_amex_template@3x.png in Resources */, + 09310D2F213ED5FB0020033A /* anim_unpin.json in Resources */, + 09310D2D213ED5FB0020033A /* anim_pin.json in Resources */, D0E9BA961F056F4C00F079A4 /* stp_card_applepay@3x.png in Resources */, D0F9720F1FFE4BD5002595C8 /* notification.caf in Resources */, + 09310D33213ED5FC0020033A /* anim_group.json in Resources */, D0DE5803205AEB7600C356A8 /* include in Resources */, D0E9BA9A1F056F4C00F079A4 /* stp_card_cvc@3x.png in Resources */, D0E9BA921F056F4C00F079A4 /* stp_card_amex@3x.png in Resources */, D0E9BA9F1F056F4C00F079A4 /* stp_card_diners_template@2x.png in Resources */, D0E9BA9E1F056F4C00F079A4 /* stp_card_diners@3x.png in Resources */, D0B4AF861EC111FA00D51FF6 /* Images.xcassets in Resources */, + 09310D31213ED5FC0020033A /* anim_delete.json in Resources */, D0E9BAAD1F056F4C00F079A4 /* stp_card_jcb_template@2x.png in Resources */, D0F972101FFE4BD5002595C8 /* MessageSent.caf in Resources */, D0E9BAB71F056F4C00F079A4 /* stp_card_visa_template@2x.png in Resources */, @@ -4581,6 +4590,7 @@ D0E9BA971F056F4C00F079A4 /* stp_card_applepay_template@2x.png in Resources */, D0E9BAB41F056F4C00F079A4 /* stp_card_placeholder_template@3x.png in Resources */, D0E9BAA71F056F4C00F079A4 /* stp_card_form_back@2x.png in Resources */, + 09310D2E213ED5FB0020033A /* anim_unmute.json in Resources */, D0E9BAB11F056F4C00F079A4 /* stp_card_mastercard_template@2x.png in Resources */, D0E9BA9D1F056F4C00F079A4 /* stp_card_diners@2x.png in Resources */, D0E9BAAF1F056F4C00F079A4 /* stp_card_mastercard@2x.png in Resources */, @@ -4604,6 +4614,7 @@ D0E9BAA51F056F4C00F079A4 /* stp_card_form_applepay@2x.png in Resources */, D0E9BAB81F056F4C00F079A4 /* stp_card_visa_template@3x.png in Resources */, D0E9BA9B1F056F4C00F079A4 /* stp_card_cvc_amex@2x.png in Resources */, + 09310D30213ED5FB0020033A /* anim_unread.json in Resources */, D0E9BAB61F056F4C00F079A4 /* stp_card_visa@3x.png in Resources */, D0E9BAA61F056F4C00F079A4 /* stp_card_form_applepay@3x.png in Resources */, ); @@ -4613,15 +4624,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 09310D24213BC5DE0020033A /* anim_group.json in Resources */, - 09310D1E213BC5DE0020033A /* anim_pin.json in Resources */, - 09310D23213BC5DE0020033A /* anim_ungroup.json in Resources */, - 09310D20213BC5DE0020033A /* anim_unpin.json in Resources */, - 09310D1F213BC5DE0020033A /* anim_unmute.json in Resources */, - 09310D21213BC5DE0020033A /* anim_unread.json in Resources */, - 09310D1D213BC5DE0020033A /* anim_read.json in Resources */, - 09310D22213BC5DE0020033A /* anim_delete.json in Resources */, - 09310D25213BC5DE0020033A /* anim_mute.json in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/TelegramUI/ArhivedStickerPacksController.swift b/TelegramUI/ArhivedStickerPacksController.swift index e61af6e165..ecc80c2344 100644 --- a/TelegramUI/ArhivedStickerPacksController.swift +++ b/TelegramUI/ArhivedStickerPacksController.swift @@ -221,7 +221,7 @@ private func archivedStickerPacksControllerEntries(presentationData: Presentatio return entries } -public func archivedStickerPacksController(account: Account) -> ViewController { +public func archivedStickerPacksController(account: Account, archived: [ArchivedStickerPackItem]?) -> ViewController { let statePromise = ValuePromise(ArchivedStickerPacksControllerState(), ignoreRepeated: true) let stateValue = Atomic(value: ArchivedStickerPacksControllerState()) let updateState: ((ArchivedStickerPacksControllerState) -> ArchivedStickerPacksControllerState) -> Void = { f in @@ -239,7 +239,7 @@ public func archivedStickerPacksController(account: Account) -> ViewController { actionsDisposable.add(removePackDisposables) let stickerPacks = Promise<[ArchivedStickerPackItem]?>() - stickerPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) + stickerPacks.set(.single(archived) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) let installedStickerPacks = Promise() installedStickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionIds(namespaces: [Namespaces.ItemCollection.CloudStickerPacks])])) diff --git a/TelegramUI/AuthorizationSequenceCodeEntryController.swift b/TelegramUI/AuthorizationSequenceCodeEntryController.swift index 45f6e59f25..eef0423adf 100644 --- a/TelegramUI/AuthorizationSequenceCodeEntryController.swift +++ b/TelegramUI/AuthorizationSequenceCodeEntryController.swift @@ -127,7 +127,7 @@ final class AuthorizationSequenceCodeEntryController: ViewController { if let termsOfService = self.termsOfService { var acceptImpl: (() -> Void)? var declineImpl: (() -> Void)? - let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(authTheme: self.theme), strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { + let controller = TermsOfServiceController(theme: TermsOfServiceControllerTheme(authTheme: self.theme), strings: self.strings, text: termsOfService.text, entities: termsOfService.entities, ageConfirmation: termsOfService.ageConfirmation, signingUp: true, accept: { _ in acceptImpl?() }, decline: { declineImpl?() diff --git a/TelegramUI/AvatarNode.swift b/TelegramUI/AvatarNode.swift index ee3cc2d0c0..c5071a13c3 100644 --- a/TelegramUI/AvatarNode.swift +++ b/TelegramUI/AvatarNode.swift @@ -9,23 +9,31 @@ import SwiftSignalKit private let savedMessagesIcon = UIImage(bundleImageName: "Avatar/SavedMessagesIcon")?.precomposed() private class AvatarNodeParameters: NSObject { + let theme: PresentationTheme? let accountPeerId: PeerId? let peerId: PeerId? let letters: [String] let font: UIFont - let savedMessagesIcon: Bool + let icon: AvatarNodeIcon let explicitColorIndex: Int? + let hasImage: Bool - init(accountPeerId: PeerId?, peerId: PeerId?, letters: [String], font: UIFont, savedMessagesIcon: Bool, explicitColorIndex: Int?) { + init(theme: PresentationTheme?, accountPeerId: PeerId?, peerId: PeerId?, letters: [String], font: UIFont, icon: AvatarNodeIcon, explicitColorIndex: Int?, hasImage: Bool) { + self.theme = theme self.accountPeerId = accountPeerId self.peerId = peerId self.letters = letters self.font = font - self.savedMessagesIcon = savedMessagesIcon + self.icon = icon self.explicitColorIndex = explicitColorIndex + self.hasImage = hasImage super.init() } + + func withUpdatedHasImage(_ hasImage: Bool) -> AvatarNodeParameters { + return AvatarNodeParameters(theme: self.theme, accountPeerId: self.accountPeerId, peerId: self.peerId, letters: self.letters, font: self.font, icon: self.icon, explicitColorIndex: self.explicitColorIndex, hasImage: hasImage) + } } private let gradientColors: [NSArray] = [ @@ -65,10 +73,17 @@ private func ==(lhs: AvatarNodeState, rhs: AvatarNodeState) -> Bool { } } +enum AvatarNodeIcon { + case none + case savedMessagesIcon + case editAvatarIcon +} + public enum AvatarNodeImageOverride { case none case image(TelegramMediaImageRepresentation) case savedMessagesIcon + case editAvatarIcon } public enum AvatarNodeColorOverride { @@ -80,7 +95,7 @@ public final class AvatarNode: ASDisplayNode { didSet { if oldValue !== font { if let parameters = self.parameters { - self.parameters = AvatarNodeParameters(accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, letters: parameters.letters, font: self.font, savedMessagesIcon: parameters.savedMessagesIcon, explicitColorIndex: parameters.explicitColorIndex) + self.parameters = AvatarNodeParameters(theme: parameters.theme, accountPeerId: parameters.accountPeerId, peerId: parameters.peerId, letters: parameters.letters, font: self.font, icon: parameters.icon, explicitColorIndex: parameters.explicitColorIndex, hasImage: parameters.hasImage) } if !self.displaySuspended { @@ -90,8 +105,10 @@ public final class AvatarNode: ASDisplayNode { } } private var parameters: AvatarNodeParameters? + private var theme: PresentationTheme? let imageNode: ImageNode + private let imageReadyDisposable = MetaDisposable() private var state: AvatarNodeState = .empty private let imageReady = Promise(false) @@ -134,7 +151,7 @@ public final class AvatarNode: ASDisplayNode { public func setPeer(account: Account, peer: Peer, authorOfMessage: MessageReference? = nil, overrideImage: AvatarNodeImageOverride? = nil) { var representation: TelegramMediaImageRepresentation? - var savedMessagesIcon = false + var icon = AvatarNodeIcon.none if let overrideImage = overrideImage { switch overrideImage { case .none: @@ -143,7 +160,10 @@ public final class AvatarNode: ASDisplayNode { representation = image case .savedMessagesIcon: representation = nil - savedMessagesIcon = true + icon = .savedMessagesIcon + case .editAvatarIcon: + representation = peer.smallProfileImage + icon = .editAvatarIcon } } else if peer.restrictionText == nil { representation = peer.smallProfileImage @@ -152,7 +172,7 @@ public final class AvatarNode: ASDisplayNode { if updatedState != self.state { self.state = updatedState - let parameters = AvatarNodeParameters(accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font, savedMessagesIcon: savedMessagesIcon, explicitColorIndex: nil) + let parameters: AvatarNodeParameters self.displaySuspended = true self.contents = nil @@ -160,12 +180,16 @@ public final class AvatarNode: ASDisplayNode { if let signal = peerAvatarImage(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation) { self.imageReady.set(self.imageNode.ready) self.imageNode.setSignal(signal) + + parameters = AvatarNodeParameters(theme: account.telegramApplicationContext.currentPresentationData.with { $0 }.theme, accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: true) } else { self.imageReady.set(.single(true)) self.displaySuspended = false if self.isNodeLoaded { self.imageNode.contents = nil } + + parameters = AvatarNodeParameters(theme: account.telegramApplicationContext.currentPresentationData.with { $0 }.theme, accountPeerId: account.peerId, peerId: peer.id, letters: peer.displayLetters, font: self.font, icon: icon, explicitColorIndex: nil, hasImage: false) } if self.parameters == nil || self.parameters != parameters { self.parameters = parameters @@ -186,7 +210,7 @@ public final class AvatarNode: ASDisplayNode { if updatedState != self.state { self.state = updatedState - let parameters = AvatarNodeParameters(accountPeerId: nil, peerId: nil, letters: letters, font: self.font, savedMessagesIcon: false, explicitColorIndex: explicitIndex) + let parameters = AvatarNodeParameters(theme: nil, accountPeerId: nil, peerId: nil, letters: letters, font: self.font, icon: .none, explicitColorIndex: explicitIndex, hasImage: false) self.displaySuspended = true self.contents = nil @@ -241,8 +265,14 @@ public final class AvatarNode: ASDisplayNode { } let colorsArray: NSArray - if let parameters = parameters as? AvatarNodeParameters, parameters.savedMessagesIcon { - colorsArray = savedMessagesColors + if let parameters = parameters as? AvatarNodeParameters, parameters.icon != .none { + if case .savedMessagesIcon = parameters.icon { + colorsArray = savedMessagesColors + } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme { + colorsArray = [ theme.list.blocksBackgroundColor.cgColor, theme.list.blocksBackgroundColor.cgColor ] + } else { + colorsArray = grayscaleColors + } } else if colorIndex == -1 { colorsArray = grayscaleColors } else { @@ -259,7 +289,7 @@ public final class AvatarNode: ASDisplayNode { context.setBlendMode(.normal) if let parameters = parameters as? AvatarNodeParameters { - if parameters.savedMessagesIcon { + if case .savedMessagesIcon = parameters.icon { let factor = bounds.size.width / 60.0 context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) context.scaleBy(x: factor, y: -factor) @@ -268,6 +298,14 @@ public final class AvatarNode: ASDisplayNode { if let savedMessagesIcon = savedMessagesIcon { context.draw(savedMessagesIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - savedMessagesIcon.size.width) / 2.0), y: floor((bounds.size.height - savedMessagesIcon.size.height) / 2.0)), size: savedMessagesIcon.size)) } + } else if case .editAvatarIcon = parameters.icon, let theme = parameters.theme, !parameters.hasImage { + context.translateBy(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -bounds.size.width / 2.0, y: -bounds.size.height / 2.0) + + if let editAvatarIcon = generateTintedImage(image: UIImage(bundleImageName: "Avatar/EditAvatarIcon"), color: theme.list.freeMonoIcon) { + context.draw(editAvatarIcon.cgImage!, in: CGRect(origin: CGPoint(x: floor((bounds.size.width - editAvatarIcon.size.width) / 2.0), y: floor((bounds.size.height - editAvatarIcon.size.height) / 2.0)), size: editAvatarIcon.size)) + } } else { let letters = parameters.letters let string = letters.count == 0 ? "" : (letters[0] + (letters.count == 1 ? "" : letters[1])) diff --git a/TelegramUI/CallListCallItem.swift b/TelegramUI/CallListCallItem.swift index b8548ef307..f326c918cb 100644 --- a/TelegramUI/CallListCallItem.swift +++ b/TelegramUI/CallListCallItem.swift @@ -551,7 +551,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { strongSelf.updateLayout(size: nodeLayout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) } }) } else { diff --git a/TelegramUI/ChangePhoneNumberController.swift b/TelegramUI/ChangePhoneNumberController.swift index 5df48e9f47..7654b7c525 100644 --- a/TelegramUI/ChangePhoneNumberController.swift +++ b/TelegramUI/ChangePhoneNumberController.swift @@ -114,6 +114,7 @@ final class ChangePhoneNumberController: ViewController { let text: String switch error { + //TODO case .limitExceeded: text = "You have requested authorization code too many times. Please try again later." case .invalidPhoneNumber: diff --git a/TelegramUI/ChannelInfoController.swift b/TelegramUI/ChannelInfoController.swift index 908ea2e4dd..cace3d1627 100644 --- a/TelegramUI/ChannelInfoController.swift +++ b/TelegramUI/ChannelInfoController.swift @@ -25,8 +25,8 @@ private final class ChannelInfoControllerArguments { let displayAddressNameContextMenu: (String) -> Void let displayContextMenu: (ChannelInfoEntryTag, String) -> Void let aboutLinkAction: (TextLinkItemActionType, TextLinkItem) -> Void - - init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, openChannelTypeSetup: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdmins: @escaping () -> Void, openMembers: @escaping () -> Void, openBanned: @escaping () -> Void, reportChannel: @escaping () -> Void, leaveChannel: @escaping () -> Void, deleteChannel: @escaping () -> Void, displayAddressNameContextMenu: @escaping (String) -> Void, displayContextMenu: @escaping (ChannelInfoEntryTag, String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void) { + let toggleSignatures:(Bool) -> Void + init(account: Account, avatarAndNameInfoContext: ItemListAvatarAndNameInfoItemContext, tapAvatarAction: @escaping () -> Void, changeProfilePhoto: @escaping () -> Void, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updateEditingDescriptionText: @escaping (String) -> Void, openChannelTypeSetup: @escaping () -> Void, changeNotificationMuteSettings: @escaping () -> Void, changeNotificationSoundSettings: @escaping () -> Void, openSharedMedia: @escaping () -> Void, openAdmins: @escaping () -> Void, openMembers: @escaping () -> Void, openBanned: @escaping () -> Void, reportChannel: @escaping () -> Void, leaveChannel: @escaping () -> Void, deleteChannel: @escaping () -> Void, displayAddressNameContextMenu: @escaping (String) -> Void, displayContextMenu: @escaping (ChannelInfoEntryTag, String) -> Void, aboutLinkAction: @escaping (TextLinkItemActionType, TextLinkItem) -> Void, toggleSignatures: @escaping(Bool)->Void) { self.account = account self.avatarAndNameInfoContext = avatarAndNameInfoContext self.tapAvatarAction = tapAvatarAction @@ -46,12 +46,15 @@ private final class ChannelInfoControllerArguments { self.displayAddressNameContextMenu = displayAddressNameContextMenu self.displayContextMenu = displayContextMenu self.aboutLinkAction = aboutLinkAction + self.toggleSignatures = toggleSignatures } } private enum ChannelInfoSection: ItemListSectionId { case info + case discriptionAndType case sharedMediaAndNotifications + case signMessages case members case reportOrLeave } @@ -68,24 +71,31 @@ private enum ChannelInfoEntry: ItemListNodeEntry { case channelPhotoSetup(theme: PresentationTheme, text: String) case channelTypeSetup(theme: PresentationTheme, text: String, value: String) case channelDescriptionSetup(theme: PresentationTheme, placeholder: String, value: String) + case channelDescriptionSetupInfo(theme: PresentationTheme, text: String) case admins(theme: PresentationTheme, text: String, value: String) case members(theme: PresentationTheme, text: String, value: String) case banned(theme: PresentationTheme, text: String, value: String) case notifications(theme: PresentationTheme, text: String, value: String) case notificationSound(theme: PresentationTheme, text: String, value: String) case sharedMedia(theme: PresentationTheme, text: String) + case signMessages(theme: PresentationTheme, text: String, value: Bool) + case signInfo(theme: PresentationTheme, text: String) case report(theme: PresentationTheme, text: String) case leave(theme: PresentationTheme, text: String) case deleteChannel(theme: PresentationTheme, text: String) var section: ItemListSectionId { switch self { - case .info, .about, .addressName, .channelPhotoSetup, .channelTypeSetup, .channelDescriptionSetup: + case .info, .about, .addressName, .channelPhotoSetup: return ChannelInfoSection.info.rawValue + case .channelDescriptionSetup, .channelDescriptionSetupInfo, .channelTypeSetup: + return ChannelInfoSection.discriptionAndType.rawValue case .admins, .members, .banned: return ChannelInfoSection.members.rawValue case .sharedMedia, .notifications, .notificationSound: return ChannelInfoSection.sharedMediaAndNotifications.rawValue + case .signMessages, .signInfo: + return ChannelInfoSection.signMessages.rawValue case .report, .leave, .deleteChannel: return ChannelInfoSection.reportOrLeave.rawValue } @@ -95,34 +105,40 @@ private enum ChannelInfoEntry: ItemListNodeEntry { switch self { case .info: return 0 - case .about: - return 1 case .addressName: + return 1 + case .about: return 2 case .channelPhotoSetup: return 3 - case .channelDescriptionSetup: - return 4 case .channelTypeSetup: + return 4 + case .channelDescriptionSetup: return 5 - case .admins: + case .channelDescriptionSetupInfo: return 6 - case .members: + case .admins: return 7 case .banned: return 8 - case .notifications: + case .members: return 9 - case .notificationSound: + case .signMessages: return 10 - case .sharedMedia: + case .signInfo: return 11 - case .report: + case .notifications: return 12 - case .leave: + case .notificationSound: return 13 - case .deleteChannel: + case .sharedMedia: return 14 + case .report: + return 15 + case .leave: + return 16 + case .deleteChannel: + return 17 } } @@ -166,6 +182,7 @@ private enum ChannelInfoEntry: ItemListNodeEntry { } else { return false } + case let .addressName(lhsTheme, lhsText, lhsValue): if case let .addressName(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -190,6 +207,12 @@ private enum ChannelInfoEntry: ItemListNodeEntry { } else { return false } + case let .channelDescriptionSetupInfo(lhsTheme, lhsText): + if case let .channelDescriptionSetupInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .admins(lhsTheme, lhsText, lhsValue): if case let .admins(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { return true @@ -208,6 +231,18 @@ private enum ChannelInfoEntry: ItemListNodeEntry { } else { return false } + case let .signMessages(lhsTheme, lhsText, lhsValue): + if case let .signMessages(rhsTheme, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsText == rhsText, lhsValue == rhsValue { + return true + } else { + return false + } + case let .signInfo(lhsTheme, lhsText): + if case let .signInfo(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .sharedMedia(lhsTheme, lhsText): if case let .sharedMedia(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { return true @@ -285,6 +320,8 @@ private enum ChannelInfoEntry: ItemListNodeEntry { }, action: { }) + case let .channelDescriptionSetupInfo(theme, text): + return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section, style: .plain) case let .admins(theme, text, value): return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: { arguments.openAdmins() @@ -297,6 +334,12 @@ private enum ChannelInfoEntry: ItemListNodeEntry { return ItemListDisclosureItem(theme: theme, title: text, label: value, sectionId: self.section, style: .plain, action: { arguments.openBanned() }) + case let .signMessages(theme, text, value): + return ItemListSwitchItem(theme: theme, title: text, value: value, sectionId: self.section, style: .plain, updated: { updated in + arguments.toggleSignatures(updated) + }) + case let .signInfo(theme, text): + return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section, style: .plain) case let .sharedMedia(theme, text): return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .plain, action: { arguments.openSharedMedia() @@ -401,16 +444,6 @@ private func channelInfoEntries(account: Account, presentationData: Presentation entries.append(.channelPhotoSetup(theme: presentationData.theme, text: presentationData.strings.Channel_UpdatePhotoItem)) } - if let cachedChannelData = view.cachedData as? CachedChannelData { - if let editingState = state.editingState, canEditChannel { - entries.append(.channelDescriptionSetup(theme: presentationData.theme, placeholder: presentationData.strings.Channel_Edit_AboutItem, value: editingState.editingDescriptionText)) - } else { - if let about = cachedChannelData.about, !about.isEmpty { - entries.append(.about(theme: presentationData.theme, text: presentationData.strings.Channel_AboutItem, value: about)) - } - } - } - if state.editingState != nil && peer.flags.contains(.isCreator) { let linkText: String if let username = peer.username { @@ -419,23 +452,50 @@ private func channelInfoEntries(account: Account, presentationData: Presentation linkText = presentationData.strings.Channel_Setup_TypePrivate } entries.append(.channelTypeSetup(theme: presentationData.theme, text: presentationData.strings.Channel_Edit_LinkItem, value: linkText)) + + } else if let username = peer.username, !username.isEmpty { entries.append(.addressName(theme: presentationData.theme, text: presentationData.strings.Channel_LinkItem, value: username)) } if let cachedChannelData = view.cachedData as? CachedChannelData { - if state.editingState != nil && canEditMembers { - if let kickedCount = cachedChannelData.participantsSummary.kickedCount { - entries.append(.banned(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Banned, value: "\(kickedCount)")) + if let editingState = state.editingState, canEditChannel { + entries.append(.channelDescriptionSetup(theme: presentationData.theme, placeholder: presentationData.strings.Channel_Edit_AboutItem, value: editingState.editingDescriptionText)) + entries.append(.channelDescriptionSetupInfo(theme: presentationData.theme, text: presentationData.strings.Channel_About_Help)) + + let messagesShouldHaveSignatures:Bool + switch peer.info { + case let .broadcast(info): + messagesShouldHaveSignatures = info.flags.contains(.messagesShouldHaveSignatures) + default: + messagesShouldHaveSignatures = false } + + entries.append(.signMessages(theme: presentationData.theme, text: presentationData.strings.Channel_SignMessages, value: messagesShouldHaveSignatures)) + entries.append(.signInfo(theme: presentationData.theme, text: presentationData.strings.Channel_SignMessages_Help)) + } else { + if let about = cachedChannelData.about, !about.isEmpty { + entries.append(.about(theme: presentationData.theme, text: presentationData.strings.Channel_AboutItem, value: about)) + } + } + + + } + + + + if let cachedChannelData = view.cachedData as? CachedChannelData { + if state.editingState == nil && canEditMembers { if peer.adminRights != nil || peer.flags.contains(.isCreator) { - if let adminCount = cachedChannelData.participantsSummary.adminCount { - entries.append(.admins(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Management, value: "\(adminCount)")) - } - if let memberCount = cachedChannelData.participantsSummary.memberCount { - entries.append(.members(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Subscribers, value: "\(memberCount)")) - } + let adminCount = cachedChannelData.participantsSummary.adminCount ?? 0 + entries.append(.admins(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Management, value: "\(adminCount == 0 ? "" : "\(adminCount)")")) + + let bannedCount = cachedChannelData.participantsSummary.kickedCount ?? 0 + entries.append(.banned(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Banned, value: "\(bannedCount == 0 ? "" : "\(bannedCount)")")) + + let memberCount = cachedChannelData.participantsSummary.memberCount ?? 0 + entries.append(.members(theme: presentationData.theme, text: presentationData.strings.Channel_Info_Subscribers, value: "\(memberCount == 0 ? "" : "\(memberCount)")")) } } } @@ -461,9 +521,9 @@ private func channelInfoEntries(account: Account, presentationData: Presentation } if peer.flags.contains(.isCreator) { - if state.editingState != nil { - entries.append(ChannelInfoEntry.deleteChannel(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_DeleteChannel)) - } + //if state.editingState != nil { + entries.append(ChannelInfoEntry.deleteChannel(theme: presentationData.theme, text: presentationData.strings.ChannelInfo_DeleteChannel)) + //} } else { entries.append(ChannelInfoEntry.report(theme: presentationData.theme, text: presentationData.strings.ReportPeer_Report)) if peer.participationStatus == .member { @@ -762,7 +822,7 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr ActionSheetItemGroup(items: [ ActionSheetTextItem(title: presentationData.strings.ChannelInfo_DeleteChannelConfirmation), ActionSheetButtonItem(title: presentationData.strings.ChannelInfo_DeleteChannel, color: .destructive, action: { - actionsDisposable.add((deleteChannel(account: account, peerId: peerId) + actionsDisposable.add((removePeerChat(postbox: account.postbox, peerId: peerId, reportChatSpam: false) |> deliverOnMainQueue).start(completed: { popToRootControllerImpl?() })) @@ -779,6 +839,8 @@ public func channelInfoController(account: Account, peerId: PeerId) -> ViewContr displayContextMenuImpl?(tag, text) }, aboutLinkAction: { action, itemLink in aboutLinkActionImpl?(action, itemLink) + }, toggleSignatures: { enabled in + actionsDisposable.add(toggleShouldChannelMessagesSignatures(account: account, peerId: peerId, enabled: enabled).start()) }) let globalNotificationsKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.globalNotifications])) diff --git a/TelegramUI/ChannelMembersController.swift b/TelegramUI/ChannelMembersController.swift index e8dc3695dc..4f56839626 100644 --- a/TelegramUI/ChannelMembersController.swift +++ b/TelegramUI/ChannelMembersController.swift @@ -11,13 +11,14 @@ private final class ChannelMembersControllerArguments { let setPeerIdWithRevealedOptions: (PeerId?, PeerId?) -> Void let removePeer: (PeerId) -> Void let openPeer: (Peer) -> Void - - init(account: Account, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void) { + let inviteViaLink:()->Void + init(account: Account, addMember: @escaping () -> Void, setPeerIdWithRevealedOptions: @escaping (PeerId?, PeerId?) -> Void, removePeer: @escaping (PeerId) -> Void, openPeer: @escaping (Peer) -> Void, inviteViaLink:@escaping()->Void) { self.account = account self.addMember = addMember self.setPeerIdWithRevealedOptions = setPeerIdWithRevealedOptions self.removePeer = removePeer self.openPeer = openPeer + self.inviteViaLink = inviteViaLink } } @@ -60,11 +61,12 @@ private enum ChannelMembersEntryStableId: Hashable { private enum ChannelMembersEntry: ItemListNodeEntry { case addMember(PresentationTheme, String) case addMemberInfo(PresentationTheme, String) + case inviteLink(PresentationTheme, String) case peerItem(Int32, PresentationTheme, PresentationStrings, RenderedChannelParticipant, ItemListPeerItemEditing, Bool) var section: ItemListSectionId { switch self { - case .addMember, .addMemberInfo: + case .addMember, .addMemberInfo, .inviteLink: return ChannelMembersSection.addMembers.rawValue case .peerItem: return ChannelMembersSection.peers.rawValue @@ -77,6 +79,8 @@ private enum ChannelMembersEntry: ItemListNodeEntry { return .index(0) case .addMemberInfo: return .index(1) + case .inviteLink: + return .index(2) case let .peerItem(_, _, _, participant, _, _): return .peer(participant.peer.id) } @@ -96,6 +100,12 @@ private enum ChannelMembersEntry: ItemListNodeEntry { } else { return false } + case let .inviteLink(lhsTheme, lhsText): + if case let .inviteLink(rhsTheme, rhsText) = rhs, lhsTheme === rhsTheme, lhsText == rhsText { + return true + } else { + return false + } case let .peerItem(lhsIndex, lhsTheme, lhsStrings, lhsParticipant, lhsEditing, lhsEnabled): if case let .peerItem(rhsIndex, rhsTheme, rhsStrings, rhsParticipant, rhsEditing, rhsEnabled) = rhs { if lhsIndex != rhsIndex { @@ -127,18 +137,26 @@ private enum ChannelMembersEntry: ItemListNodeEntry { switch lhs { case .addMember: return true + case .inviteLink: + switch rhs { + case .addMember: + return false + default: + return true + } case .addMemberInfo: switch rhs { - case .addMember: + case .addMember, .inviteLink: return false default: return true } + case let .peerItem(index, _, _, _, _, _): switch rhs { case let .peerItem(rhsIndex, _, _, _, _, _): return index < rhsIndex - case .addMember, .addMemberInfo: + case .addMember, .addMemberInfo, .inviteLink: return false } } @@ -150,6 +168,10 @@ private enum ChannelMembersEntry: ItemListNodeEntry { return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { arguments.addMember() }) + case let .inviteLink(theme, text): + return ItemListActionItem(theme: theme, title: text, kind: .generic, alignment: .natural, sectionId: self.section, style: .blocks, action: { + arguments.inviteViaLink() + }) case let .addMemberInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .peerItem(_, theme, strings, participant, editing, enabled): @@ -212,8 +234,18 @@ private func ChannelMembersControllerEntries(account: Account, presentationData: var entries: [ChannelMembersEntry] = [] if let participants = participants { - entries.append(.addMember(presentationData.theme, presentationData.strings.Channel_Members_AddMembers)) - entries.append(.addMemberInfo(presentationData.theme, presentationData.strings.Channel_Members_AddMembersHelp)) + + var canAddMember: Bool = false + if let peer = view.peers[view.peerId] as? TelegramChannel { + canAddMember = peer.hasAdminRights(.canInviteUsers) + } + + if canAddMember { + entries.append(.addMember(presentationData.theme, presentationData.strings.Channel_Members_AddMembers)) + entries.append(.inviteLink(presentationData.theme, presentationData.strings.Channel_Members_InviteLink)) + entries.append(.addMemberInfo(presentationData.theme, presentationData.strings.Channel_Members_AddMembersHelp)) + } + var index: Int32 = 0 for participant in participants.sorted(by: { lhs, rhs in @@ -286,14 +318,26 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo return .single(false) } }) - confirmationImpl = { [weak contactsController] peerId in - return account.postbox.loadedPeerWithId(peerId) + confirmationImpl = { [weak contactsController] selectedId in + return combineLatest(account.postbox.loadedPeerWithId(selectedId), account.postbox.loadedPeerWithId(peerId)) |> deliverOnMainQueue - |> mapToSignal { peer in + |> mapToSignal { peer, channelPeer in let result = ValuePromise() + if let contactsController = contactsController { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } - let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0, actions: [ + let confirmationText: String + if let channel = channelPeer as? TelegramChannel { + switch channel.info { + case .broadcast: + confirmationText = presentationData.strings.ChannelInfo_AddParticipantConfirmation(peer.displayTitle).0 + case .group: + confirmationText = presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0 + } + } else { + confirmationText = presentationData.strings.GroupInfo_AddParticipantConfirmation(peer.displayTitle).0 + } + let alertController = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: confirmationText, actions: [ TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: { result.set(false) }), @@ -397,6 +441,8 @@ public func channelMembersController(account: Account, peerId: PeerId) -> ViewCo if let controller = peerInfoController(account: account, peer: peer) { pushControllerImpl?(controller) } + }, inviteViaLink: { + presentControllerImpl?(channelVisibilityController(account: account, peerId: peerId, mode: .privateLink), ViewControllerPresentationArguments(presentationAnimation: .modalSheet)) }) let peerView = account.viewTracker.peerView(peerId) diff --git a/TelegramUI/ChannelVisibilityController.swift b/TelegramUI/ChannelVisibilityController.swift index 3aa547062f..a68c7c0dce 100644 --- a/TelegramUI/ChannelVisibilityController.swift +++ b/TelegramUI/ChannelVisibilityController.swift @@ -239,7 +239,9 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { case let .typeInfo(theme, text): return ItemListTextItem(theme: theme, text: .plain(text), sectionId: self.section) case let .publicLinkAvailability(theme, text, value): - return ItemListActivityTextItem(displayActivity: value, theme: theme, text: NSAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor), sectionId: self.section) + let attr = NSMutableAttributedString(string: text, textColor: value ? theme.list.freeTextColor : theme.list.freeTextErrorColor) + attr.addAttribute(.font, value: Font.regular(13), range: NSMakeRange(0, attr.length)) + return ItemListActivityTextItem(displayActivity: value, theme: theme, text: attr, sectionId: self.section) case let .privateLink(theme, text, value): return ItemListActionItem(theme: theme, title: text, kind: value != nil ? .neutral : .disabled, alignment: .natural, sectionId: self.section, style: .blocks, action: { if let value = value { @@ -710,10 +712,11 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: return state.withUpdatedRevokingPeerId(nil) } }, completed: { - updateState { state in - return state.withUpdatedRevokingPeerId(nil) - } - peersDisablingAddressNameAssignment.set(.single([])) + peersDisablingAddressNameAssignment.set(.single([]) |> delay(0.2, queue: Queue.mainQueue()) |> afterNext { _ in + updateState { state in + return state.withUpdatedRevokingPeerId(nil) + } + }) })) }, copyPrivateLink: { let _ = (account.postbox.transaction { transaction -> String? in @@ -816,32 +819,42 @@ public func channelVisibilityController(account: Account, peerId: PeerId, mode: var updatedAddressNameValue: String? updateState { state in updatedAddressNameValue = updatedAddressName(state: state, peer: peer) - - if updatedAddressNameValue != nil { - return state.withUpdatedUpdatingAddressName(true) - } else { - return state - } + return state } if let updatedAddressNameValue = updatedAddressNameValue { - updateAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue) - |> deliverOnMainQueue).start(error: { _ in + + let confirm = standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Channel_Edit_PrivatePublicLinkAlert, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + updateState { state in - return state.withUpdatedUpdatingAddressName(false) - } - }, completed: { - updateState { state in - return state.withUpdatedUpdatingAddressName(false) + return state.withUpdatedUpdatingAddressName(true) } - switch mode { - case .initialSetup: - nextImpl?() - case .generic, .privateLink: - dismissImpl?() - } - })) + updateAddressNameDisposable.set((updateAddressName(account: account, domain: .peer(peerId), name: updatedAddressNameValue.isEmpty ? nil : updatedAddressNameValue) |> timeout(10, queue: Queue.mainQueue(), alternate: .fail(.generic)) + |> deliverOnMainQueue).start(error: { _ in + updateState { state in + return state.withUpdatedUpdatingAddressName(false) + } + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + + }, completed: { + updateState { state in + return state.withUpdatedUpdatingAddressName(false) + } + + switch mode { + case .initialSetup: + nextImpl?() + case .generic, .privateLink: + dismissImpl?() + } + })) + + })]) + + presentControllerImpl?(confirm, nil) + + } else { switch mode { case .initialSetup: diff --git a/TelegramUI/ChatController.swift b/TelegramUI/ChatController.swift index 946ec37eec..dae48a9f78 100644 --- a/TelegramUI/ChatController.swift +++ b/TelegramUI/ChatController.swift @@ -170,6 +170,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin private var screenCaptureEventsDisposable: Disposable? private let chatAdditionalDataDisposable = MetaDisposable() + var purposefulAction: (() -> Void)? + public init(account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, mode: ChatControllerPresentationMode = .standard(previewing: false)) { self.account = account self.chatLocation = chatLocation @@ -233,6 +235,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin let controllerInteraction = ChatControllerInteraction(openMessage: { [weak self] message in if let strongSelf = self, strongSelf.isNodeLoaded, let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(message.id) { + strongSelf.commitPurposefulAction() + for media in message.media { if let action = media as? TelegramMediaAction { switch action.action { @@ -280,7 +284,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self?.sendMessages([message]) }, sendSticker: canSendMessagesToChat(strongSelf.presentationInterfaceState) ? { fileReference in self?.controllerInteraction?.sendSticker(fileReference) - } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in + } : nil, setupTemporaryHiddenMedia: { signal, centralIndex, galleryMedia in if let strongSelf = self { strongSelf.temporaryHiddenGalleryMediaDisposable.set((signal |> deliverOnMainQueue).start(next: { entry in if let strongSelf = self, let controllerInteraction = strongSelf.controllerInteraction { @@ -617,6 +621,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self?.presentInGlobalOverlay(controller, with: arguments) }, callPeer: { [weak self] peerId in if let strongSelf = self { + strongSelf.commitPurposefulAction() + func getUserPeer(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Peer? in guard let peer = transaction.getPeer(peerId) else { @@ -815,6 +821,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin } }, openCheckoutOrReceipt: { [weak self] messageId in if let strongSelf = self { + strongSelf.commitPurposefulAction() if let message = strongSelf.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageId) { for media in message.media { if let invoice = media as? TelegramMediaInvoice { @@ -841,6 +848,19 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin return canReplyInChat(strongSelf.presentationInterfaceState) } return false + }, navigateToFirstDateMessage: { [weak self] timestamp in + guard let `self` = self else {return} + switch self.chatLocation { + case let .peer(peerId): + self.messageIndexDisposable.set((searchMessageIdByTimestamp(account: self.account, peerId: peerId, timestamp: timestamp - Int32(NSTimeZone.local.secondsFromGMT())) |> deliverOnMainQueue).start(next: { [weak self] messageId in + guard let `self` = self else {return} + if let messageId = messageId { + self.navigateToMessage(from: nil, to: .id(messageId), scrollPosition: .bottom(0)) + } + })) + default: + break + } }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) @@ -1298,12 +1318,16 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin var pinnedMessageId: MessageId? var peerIsBlocked: Bool = false var canReport: Bool = false + var callsAvailable: Bool = false + var callsPrivate: Bool = false if let cachedData = combinedInitialData.cachedData as? CachedChannelData { pinnedMessageId = cachedData.pinnedMessageId canReport = cachedData.reportStatus == .canReport } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { peerIsBlocked = cachedData.isBlocked canReport = cachedData.reportStatus == .canReport + callsAvailable = cachedData.callsAvailable + callsPrivate = cachedData.callsPrivate } else if let cachedData = combinedInitialData.cachedData as? CachedGroupData { canReport = cachedData.reportStatus == .canReport } else if let cachedData = combinedInitialData.cachedData as? CachedSecretChatData { @@ -1325,6 +1349,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin updated = updated.updatedPinnedMessage(pinnedMessage) updated = updated.updatedPeerIsBlocked(peerIsBlocked) updated = updated.updatedCanReportPeer(canReport) + updated = updated.updatedCallsAvailable(callsAvailable) + updated = updated.updatedCallsPrivate(callsPrivate) updated = updated.updatedTitlePanelContext({ context in if pinnedMessageId != nil { if !context.contains(where: { @@ -1401,12 +1427,16 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin var pinnedMessageId: MessageId? var peerIsBlocked: Bool = false var canReport: Bool = false + var callsAvailable: Bool = false + var callsPrivate: Bool = false if let cachedData = cachedData as? CachedChannelData { pinnedMessageId = cachedData.pinnedMessageId canReport = cachedData.reportStatus == .canReport } else if let cachedData = cachedData as? CachedUserData { peerIsBlocked = cachedData.isBlocked canReport = cachedData.reportStatus == .canReport + callsAvailable = cachedData.callsAvailable + callsPrivate = cachedData.callsPrivate } else if let cachedData = cachedData as? CachedGroupData { canReport = cachedData.reportStatus == .canReport } else if let cachedData = cachedData as? CachedSecretChatData { @@ -1429,7 +1459,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || strongSelf.presentationInterfaceState.canReportPeer != canReport || pinnedMessageUpdated { strongSelf.updateChatPresentationInterfaceState(animated: true, interactive: true, { state in - return state.updatedPinnedMessageId(pinnedMessageId).updatedPinnedMessage(pinnedMessage).updatedPeerIsBlocked(peerIsBlocked).updatedCanReportPeer(canReport).updatedTitlePanelContext({ context in + return state.updatedPinnedMessageId(pinnedMessageId).updatedPinnedMessage(pinnedMessage).updatedPeerIsBlocked(peerIsBlocked).updatedCanReportPeer(canReport).updatedCallsAvailable(callsAvailable).updatedCallsPrivate(callsPrivate).updatedTitlePanelContext({ context in if pinnedMessageId != nil { if !context.contains(where: { switch $0 { @@ -1577,6 +1607,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self.chatDisplayNode.sendMessages = { [weak self] messages in if let strongSelf = self, case let .peer(peerId) = strongSelf.chatLocation { + strongSelf.commitPurposefulAction() + let _ = (enqueueMessages(account: strongSelf.account, peerId: peerId, messages: strongSelf.transformEnqueueMessages(messages)) |> deliverOnMainQueue).start(next: { _ in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory() @@ -3434,6 +3466,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin private func sendMessages(_ messages: [EnqueueMessage]) { if case let .peer(peerId) = self.chatLocation { + self.commitPurposefulAction() + let _ = (enqueueMessages(account: self.account, peerId: peerId, messages: self.transformEnqueueMessages(messages)) |> deliverOnMainQueue).start(next: { [weak self] _ in self?.chatDisplayNode.historyNode.scrollToEndOfHistory() @@ -3777,7 +3811,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self.navigateToMessage(from: nil, to: messageLocation, rememberInStack: false, animated: animated, completion: completion) } - private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, rememberInStack: Bool = true, animated: Bool = true, completion: (() -> Void)? = nil) { + private func navigateToMessage(from fromId: MessageId?, to messageLocation: NavigateToMessageLocation, scrollPosition: ListViewScrollPosition = .center(.bottom), rememberInStack: Bool = true, animated: Bool = true, completion: (() -> Void)? = nil) { if self.isNodeLoaded { var fromIndex: MessageIndex? @@ -3798,7 +3832,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin if let message = self.chatDisplayNode.historyNode.messageInCurrentHistoryView(messageLocation.messageId) { self.loadingMessage.set(false) self.messageIndexDisposable.set(nil) - self.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: MessageIndex(message), animated: animated) + self.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: MessageIndex(message), animated: animated, scrollPosition: scrollPosition) completion?() } else { self.loadingMessage.set(true) @@ -3829,7 +3863,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin |> take(1) self.messageIndexDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] index in if let strongSelf = self, let index = index { - strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated) + strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated, scrollPosition: scrollPosition) completion?() } }, completed: { [weak self] in @@ -3876,7 +3910,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin self.messageIndexDisposable.set((signal |> deliverOnMainQueue).start(next: { [weak self] index in if let strongSelf = self { if let index = index { - strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated) + strongSelf.chatDisplayNode.historyNode.scrollToMessage(from: fromIndex, to: index, animated: animated, scrollPosition: scrollPosition) completion?() } else { (strongSelf.navigationController as? NavigationController)?.pushViewController(ChatController(account: strongSelf.account, chatLocation: .peer(messageLocation.messageId.peerId), messageId: messageLocation.messageId)) @@ -4605,7 +4639,7 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin if options.contains(.deleteGlobally) { let globalTitle: String if isChannel { - globalTitle = self.presentationData.strings.Common_Delete + globalTitle = self.presentationData.strings.Conversation_DeleteMessagesForEveryone } else if let personalPeerName = personalPeerName { globalTitle = self.presentationData.strings.Conversation_DeleteMessagesFor(personalPeerName).0 } else { @@ -4719,4 +4753,8 @@ public final class ChatController: TelegramController, UIViewControllerPreviewin })) } } + + private func commitPurposefulAction() { + self.purposefulAction?() + } } diff --git a/TelegramUI/ChatControllerInteraction.swift b/TelegramUI/ChatControllerInteraction.swift index 056558924b..28e931a8ea 100644 --- a/TelegramUI/ChatControllerInteraction.swift +++ b/TelegramUI/ChatControllerInteraction.swift @@ -67,6 +67,7 @@ public final class ChatControllerInteraction { let openSearch: () -> Void let setupReply: (MessageId) -> Void let canSetupReply: (Message) -> Bool + let navigateToFirstDateMessage:(Int32)->Void let requestMessageUpdate: (MessageId) -> Void let cancelInteractiveKeyboardGestures: () -> Void @@ -77,7 +78,7 @@ public final class ChatControllerInteraction { var contextHighlightedState: ChatInterfaceHighlightedState? var automaticMediaDownloadSettings: AutomaticMediaDownloadSettings - init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { + init(openMessage: @escaping (Message) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, ASDisplayNode, CGRect) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference) -> Void, sendGif: @escaping (FileMediaReference) -> Void, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32)->Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -108,6 +109,7 @@ public final class ChatControllerInteraction { self.openSearch = openSearch self.setupReply = setupReply self.canSetupReply = canSetupReply + self.navigateToFirstDateMessage = navigateToFirstDateMessage self.requestMessageUpdate = requestMessageUpdate self.cancelInteractiveKeyboardGestures = cancelInteractiveKeyboardGestures diff --git a/TelegramUI/ChatControllerNode.swift b/TelegramUI/ChatControllerNode.swift index c65976f92f..73b84f5a1e 100644 --- a/TelegramUI/ChatControllerNode.swift +++ b/TelegramUI/ChatControllerNode.swift @@ -1620,7 +1620,9 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { contextMenuController?.dismiss() }) self.messageActionSheetController = (controller, stableId) - self.controllerInteraction.presentGlobalOverlayController(controller, nil) + if let sheetActions = sheetActions, !sheetActions.isEmpty { + self.controllerInteraction.presentGlobalOverlayController(controller, nil) + } animateIn = true } } diff --git a/TelegramUI/ChatHistoryGridNode.swift b/TelegramUI/ChatHistoryGridNode.swift index 614409fcac..1f9f6b86cb 100644 --- a/TelegramUI/ChatHistoryGridNode.swift +++ b/TelegramUI/ChatHistoryGridNode.swift @@ -301,7 +301,7 @@ public final class ChatHistoryGridNode: GridNode, ChatHistoryNode { self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .upperBound, anchorIndex: .upperBound, sourceIndex: .lowerBound, scrollPosition: .top(0.0), animated: true)) } - public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex) { + public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, scrollPosition: ListViewScrollPosition = .center(.bottom)) { self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: .center(.bottom), animated: true)) } diff --git a/TelegramUI/ChatHistoryListNode.swift b/TelegramUI/ChatHistoryListNode.swift index 453aad28e1..44faf0b9bd 100644 --- a/TelegramUI/ChatHistoryListNode.swift +++ b/TelegramUI/ChatHistoryListNode.swift @@ -774,8 +774,8 @@ public final class ChatHistoryListNode: ListView, ChatHistoryNode { } } - public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true) { - self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: .center(.bottom), animated: animated)) + public func scrollToMessage(from fromIndex: MessageIndex, to toIndex: MessageIndex, animated: Bool, highlight: Bool = true, scrollPosition: ListViewScrollPosition = .center(.bottom)) { + self._chatHistoryLocation.set(ChatHistoryLocation.Scroll(index: .message(toIndex), anchorIndex: .message(toIndex), sourceIndex: .message(fromIndex), scrollPosition: scrollPosition, animated: animated)) } public func anchorMessageInCurrentHistoryView() -> Message? { diff --git a/TelegramUI/ChatInfoTitlePanelNode.swift b/TelegramUI/ChatInfoTitlePanelNode.swift index 93fde5a7e6..902b68d637 100644 --- a/TelegramUI/ChatInfoTitlePanelNode.swift +++ b/TelegramUI/ChatInfoTitlePanelNode.swift @@ -55,9 +55,9 @@ private enum ChatInfoTitleButton { } } -private func peerButtons(_ peer: Peer, isMuted: Bool) -> [ChatInfoTitleButton] { +private func peerButtons(_ peer: Peer, interfaceState: ChatPresentationInterfaceState) -> [ChatInfoTitleButton] { let muteAction: ChatInfoTitleButton - if isMuted { + if interfaceState.peerIsMuted { muteAction = .unmute } else { muteAction = .mute @@ -65,7 +65,7 @@ private func peerButtons(_ peer: Peer, isMuted: Bool) -> [ChatInfoTitleButton] { if let peer = peer as? TelegramUser { var buttons: [ChatInfoTitleButton] = [.search, muteAction] - if peer.botInfo == nil { + if peer.botInfo == nil && interfaceState.callsAvailable { buttons.append(.call) } @@ -73,7 +73,9 @@ private func peerButtons(_ peer: Peer, isMuted: Bool) -> [ChatInfoTitleButton] { return buttons } else if let _ = peer as? TelegramSecretChat { var buttons: [ChatInfoTitleButton] = [.search, muteAction] - buttons.append(.call) + if interfaceState.callsAvailable { + buttons.append(.call) + } buttons.append(.info) return buttons } else if let channel = peer as? TelegramChannel { @@ -151,7 +153,7 @@ final class ChatInfoTitlePanelNode: ChatTitleAccessoryPanelNode { switch interfaceState.chatLocation { case .peer: if let peer = interfaceState.renderedPeer?.peer { - updatedButtons = peerButtons(peer, isMuted: interfaceState.peerIsMuted) + updatedButtons = peerButtons(peer, interfaceState: interfaceState) } else { updatedButtons = [] } diff --git a/TelegramUI/ChatInterfaceStateContextMenus.swift b/TelegramUI/ChatInterfaceStateContextMenus.swift index cd27eceb2e..75a93bc21e 100644 --- a/TelegramUI/ChatInterfaceStateContextMenus.swift +++ b/TelegramUI/ChatInterfaceStateContextMenus.swift @@ -11,6 +11,8 @@ private struct MessageContextMenuData { let canReply: Bool let canPin: Bool let canEdit: Bool + let canSelect: Bool + let canContextDelete: Bool let resourceStatus: MediaResourceStatus? let messageActions: ChatAvailableMessageActions } @@ -32,7 +34,7 @@ func canReplyInChat(_ chatPresentationInterfaceState: ChatPresentationInterfaceS case .broadcast: canReply = channel.hasAdminRights([.canPostMessages]) case .group: - canReply = true + canReply = !channel.hasBannedRights(.banSendMessages) } } } else if let group = peer as? TelegramGroup { @@ -176,30 +178,49 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - var canReply = false + var canReply = canReplyInChat(chatPresentationInterfaceState) var canPin = false + let canSelect = !isAction + + var canDeleteMessage: Bool = false + + let message = messages[0] + if let channel = message.peers[message.id.peerId] as? TelegramChannel { + if case .broadcast = channel.info { + if !message.flags.contains(.Incoming) { + canDeleteMessage = channel.hasAdminRights(.canPostMessages) + } + canDeleteMessage = channel.hasAdminRights(.canDeleteMessages) + } + canDeleteMessage = channel.hasAdminRights(.canDeleteMessages) || !message.flags.contains(.Incoming) + } else if message.peers[message.id.peerId] is TelegramSecretChat { + canDeleteMessage = true + } else { + canDeleteMessage = account.peerId == message.author?.id + } + + let canContextDelete = isAction && canDeleteMessage if messages[0].flags.intersection([.Failed, .Unsent]).isEmpty { switch chatPresentationInterfaceState.chatLocation { case .peer: if let channel = messages[0].peers[messages[0].id.peerId] as? TelegramChannel { switch channel.info { case .broadcast: - canReply = channel.hasAdminRights([.canPostMessages]) if !isAction { canPin = channel.hasAdminRights([.canEditMessages]) } case .group: - canReply = true if !isAction { canPin = channel.hasAdminRights([.canPinMessages]) } } - } else { - canReply = true } case .group: break } + } else { + canReply = false + canPin = false } var loadStickerSaveStatusSignal: Signal = .single(nil) @@ -284,7 +305,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - return MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, resourceStatus: resourceStatus, messageActions: messageActions) + return MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, canContextDelete: canContextDelete, resourceStatus: resourceStatus, messageActions: messageActions) } return dataSignal |> deliverOnMainQueue |> map { data -> [ChatMessageContextMenuAction] in @@ -362,7 +383,7 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } - if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, let addressName = channel.addressName { + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, let addressName = channel.addressName, !(message.media.first is TelegramMediaAction) { actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuCopyLink, action: { UIPasteboard.general.string = "https://t.me/\(addressName)/\(message.id.id)" }))) @@ -398,9 +419,17 @@ func contextMenuForChatPresentationIntefaceState(chatPresentationInterfaceState: } } } - actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuMore), action: { - interfaceInteraction.beginMessageSelection(messages.map { $0.id }) - }))) + if data.canSelect { + actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuMore), action: { + interfaceInteraction.beginMessageSelection(messages.map { $0.id }) + }))) + } + if data.canContextDelete { + actions.append(.context(ContextMenuAction(content: .text(chatPresentationInterfaceState.strings.Conversation_ContextMenuDelete), action: { + interfaceInteraction.deleteMessages(messages) + }))) + } + if data.messageActions.options.contains(.forward) { actions.append(.sheet(ChatMessageContextMenuSheetAction(color: .accent, title: chatPresentationInterfaceState.strings.Conversation_ContextMenuForward, action: { @@ -534,9 +563,14 @@ func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messag } else { assertionFailure() } + if message.media.first is TelegramMediaAction { + optionsMap[id] = [] + } } else { optionsMap[id]!.insert(.deleteLocally) } + + } if !optionsMap.isEmpty { diff --git a/TelegramUI/ChatListController.swift b/TelegramUI/ChatListController.swift index 029c1ada61..f6e11206b6 100644 --- a/TelegramUI/ChatListController.swift +++ b/TelegramUI/ChatListController.swift @@ -115,7 +115,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD case .waitingForNetwork: strongSelf.titleView.title = NetworkStatusTitle(text: strongSelf.presentationData.strings.State_WaitingForNetwork, activity: true, hasProxy: hasProxy, connectsViaProxy: connectsViaProxy) case let .connecting(proxy): - var text = strongSelf.presentationData.strings.State_Connecting + let text = strongSelf.presentationData.strings.State_Connecting if let proxy = proxy, proxy.hasConnectionIssues { checkProxy = true } @@ -382,7 +382,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } } - self.chatListDisplayNode.requestOpenPeerFromSearch = { [weak self] peer in + self.chatListDisplayNode.requestOpenPeerFromSearch = { [weak self] peer, dismissSearch in if let strongSelf = self { let storedPeer = strongSelf.account.postbox.transaction { transaction -> Void in if transaction.getPeer(peer.id) == nil { @@ -393,9 +393,15 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } strongSelf.openMessageFromSearchDisposable.set((storedPeer |> deliverOnMainQueue).start(completed: { [weak strongSelf] in if let strongSelf = strongSelf { - strongSelf.dismissSearchOnDisappear = true + if dismissSearch { + strongSelf.dismissSearchOnDisappear = true + } if let navigationController = strongSelf.navigationController as? NavigationController { - navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peer.id)) + navigateToChatController(navigationController: navigationController, account: strongSelf.account, chatLocation: .peer(peer.id), purposefulAction: { [weak self] in + if let strongSelf = self { + strongSelf.deactivateSearch(animated: false) + } + }) strongSelf.chatListDisplayNode.chatListNode.clearHighlightAnimated(true) } } @@ -526,7 +532,7 @@ public class ChatListController: TelegramController, UIViewControllerPreviewingD } } - private func deactivateSearch(animated: Bool) { + func deactivateSearch(animated: Bool) { if !self.displayNavigationBar { self.setDisplayNavigationBar(true, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) self.chatListDisplayNode.deactivateSearch(animated: animated) diff --git a/TelegramUI/ChatListControllerNode.swift b/TelegramUI/ChatListControllerNode.swift index 5b0bb4346b..c9a85b538a 100644 --- a/TelegramUI/ChatListControllerNode.swift +++ b/TelegramUI/ChatListControllerNode.swift @@ -17,7 +17,7 @@ class ChatListControllerNode: ASDisplayNode { private var containerLayout: (ContainerViewLayout, CGFloat)? var requestDeactivateSearch: (() -> Void)? - var requestOpenPeerFromSearch: ((Peer) -> Void)? + var requestOpenPeerFromSearch: ((Peer, Bool) -> Void)? var requestOpenRecentPeerOptions: ((Peer) -> Void)? var requestOpenMessageFromSearch: ((Peer, MessageId) -> Void)? @@ -136,8 +136,8 @@ class ChatListControllerNode: ASDisplayNode { } if let placeholderNode = maybePlaceholderNode { - self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChatListSearchContainerNode(account: self.account, filter: [], groupId: self.groupId, openPeer: { [weak self] peer in - self?.requestOpenPeerFromSearch?(peer) + self.searchDisplayController = SearchDisplayController(theme: self.themeAndStrings.0, strings: self.themeAndStrings.1, contentNode: ChatListSearchContainerNode(account: self.account, filter: [], groupId: self.groupId, openPeer: { [weak self] peer, dismissSearch in + self?.requestOpenPeerFromSearch?(peer, dismissSearch) }, openRecentPeerOptions: { [weak self] peer in self?.requestOpenRecentPeerOptions?(peer) }, openMessage: { [weak self] peer, messageId in diff --git a/TelegramUI/ChatListItem.swift b/TelegramUI/ChatListItem.swift index db64d94b15..999da00eba 100644 --- a/TelegramUI/ChatListItem.swift +++ b/TelegramUI/ChatListItem.swift @@ -139,15 +139,15 @@ private let textFont = Font.regular(15.0) private let dateFont = Font.regular(14.0) private let badgeFont = Font.regular(14.0) -private let pinIcon = UIImage(bundleImageName: "Chat List/RevealActionPinIcon")?.precomposed() -private let unpinIcon = UIImage(bundleImageName: "Chat List/RevealActionUnpinIcon")?.precomposed() -private let muteIcon = UIImage(bundleImageName: "Chat List/RevealActionMuteIcon")?.precomposed() -private let unmuteIcon = UIImage(bundleImageName: "Chat List/RevealActionUnmuteIcon")?.precomposed() -private let deleteIcon = UIImage(bundleImageName: "Chat List/RevealActionDeleteIcon")?.precomposed() -private let groupIcon = UIImage(bundleImageName: "Chat List/RevealActionGroupIcon")?.precomposed() -private let ungroupIcon = UIImage(bundleImageName: "Chat List/RevealActionUngroupIcon")?.precomposed() -private let readIcon = UIImage(bundleImageName: "Chat List/RevealActionReadIcon")?.precomposed() -private let unreadIcon = UIImage(bundleImageName: "Chat List/RevealActionUnreadIcon")?.precomposed() +private let pinIcon = ItemListRevealOptionIcon.animation("anim_pin", keysToColor: nil) +private let unpinIcon = ItemListRevealOptionIcon.animation("anim_unpin", keysToColor: ["un Outlines.Group 1.Stroke 1"]) +private let muteIcon = ItemListRevealOptionIcon.animation("anim_mute", keysToColor: ["un Outlines.Group 1.Stroke 1"]) +private let unmuteIcon = ItemListRevealOptionIcon.animation("anim_unmute", keysToColor: nil) +private let deleteIcon = ItemListRevealOptionIcon.animation("anim_delete", keysToColor: nil) +private let groupIcon = ItemListRevealOptionIcon.animation("anim_group", keysToColor: nil) +private let ungroupIcon = ItemListRevealOptionIcon.animation("anim_ungroup", keysToColor: ["un Outlines.Group 1.Stroke 1"]) +private let readIcon = ItemListRevealOptionIcon.animation("anim_read", keysToColor: nil) +private let unreadIcon = ItemListRevealOptionIcon.animation("anim_unread", keysToColor: ["Oval.Oval.Stroke 1"]) private enum RevealOptionKey: Int32 { case pin @@ -1085,7 +1085,7 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let authorFrame = self.authorNode.frame transition.updateFrame(node: self.authorNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: authorFrame.origin.y), size: authorFrame.size)) - transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: authorFrame.minX + 1.0, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size)) + transition.updateFrame(node: self.inputActivitiesNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: self.inputActivitiesNode.frame.minY), size: self.inputActivitiesNode.bounds.size)) let textFrame = self.textNode.frame transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x - 1.0, y: textFrame.origin.y), size: textFrame.size)) @@ -1096,6 +1096,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { let statusFrame = self.statusNode.frame transition.updateFrame(node: self.statusNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + contentRect.size.width - dateFrame.size.width - 2.0 - statusFrame.size.width, y: statusFrame.minY), size: statusFrame.size)) + + var nextTitleIconOrigin: CGFloat = contentRect.origin.x + titleFrame.size.width + 3.0 + titleOffset if let verificationIconNode = self.verificationIconNode { diff --git a/TelegramUI/ChatListSearchContainerNode.swift b/TelegramUI/ChatListSearchContainerNode.swift index 93ead88518..a122e56f01 100644 --- a/TelegramUI/ChatListSearchContainerNode.swift +++ b/TelegramUI/ChatListSearchContainerNode.swift @@ -488,7 +488,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { return self._isSearching.get() } - init(account: Account, filter: ChatListNodePeersFilter, groupId: PeerGroupId?, openPeer: @escaping (Peer) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage: @escaping (Peer, MessageId) -> Void) { + init(account: Account, filter: ChatListNodePeersFilter, groupId: PeerGroupId?, openPeer: @escaping (Peer, Bool) -> Void, openRecentPeerOptions: @escaping (Peer) -> Void, openMessage: @escaping (Peer, MessageId) -> Void) { self.account = account self.openMessage = openMessage @@ -598,7 +598,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { let interaction = ChatListNodeInteraction(activateSearch: { }, peerSelected: { [weak self] peer in - openPeer(peer) + openPeer(peer, false) let _ = addRecentlySearchedPeer(postbox: account.postbox, peerId: peer.id).start() self?.listNode.clearHighlightAnimated(true) }, messageSelected: { [weak self] message in @@ -663,7 +663,7 @@ final class ChatListSearchContainerNode: SearchDisplayControllerContentNode { let transition = chatListSearchContainerPreparedRecentTransition(from: previousEntries ?? [], to: entries, account: account, filter: filter, peerSelected: { peer in self?.recentListNode.clearHighlightAnimated(true) - openPeer(peer) + openPeer(peer, true) }, peerLongTapped: { peer in openRecentPeerOptions(peer) }, clearRecentlySearchedPeers: { diff --git a/TelegramUI/ChatMediaInputNode.swift b/TelegramUI/ChatMediaInputNode.swift index 31ce75bdcb..35e1c18900 100644 --- a/TelegramUI/ChatMediaInputNode.swift +++ b/TelegramUI/ChatMediaInputNode.swift @@ -13,7 +13,7 @@ private struct PeerSpecificPackData { private enum CanInstallPeerSpecificPack { case none - case available(dismissed: Bool) + case available(peer: Peer, dismissed: Bool) } private struct ChatMediaInputPanelTransition { @@ -137,7 +137,7 @@ private func preparedChatMediaInputGridEntryTransition(account: Account, view: I return ChatMediaInputGridTransition(deletions: deletions, insertions: insertions, updates: updates, updateFirstIndexInSectionOffset: firstIndexInSectionOffset, stationaryItems: stationaryItems, scrollToItem: scrollToItem, updateOpaqueState: opaqueState, animated: animated) } -private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { +private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers: OrderedItemListView?, recentStickers: OrderedItemListView?, peerSpecificPack: PeerSpecificPackData?, canInstallPeerSpecificPack: CanInstallPeerSpecificPack, theme: PresentationTheme) -> [ChatMediaInputPanelEntry] { var entries: [ChatMediaInputPanelEntry] = [] entries.append(.recentGifs(theme)) if let savedStickers = savedStickers, !savedStickers.items.isEmpty { @@ -167,6 +167,8 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers } if let peerSpecificPack = peerSpecificPack { entries.append(.peerSpecific(theme: theme, peer: peerSpecificPack.peer)) + } else if case let .available(peer, false) = canInstallPeerSpecificPack { + entries.append(.peerSpecific(theme: theme, peer: peer)) } var index = 0 for (_, info, item) in view.collectionInfos { @@ -175,6 +177,11 @@ private func chatMediaInputPanelEntries(view: ItemCollectionsView, savedStickers index += 1 } } + + if peerSpecificPack == nil, case let .available(peer, true) = canInstallPeerSpecificPack { + entries.append(.peerSpecific(theme: theme, peer: peer)) + } + entries.append(.trending(false, theme)) entries.append(.settings(theme)) return entries @@ -226,7 +233,7 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: } } - if peerSpecificPack == nil, case .available(false) = canInstallPeerSpecificPack { + if peerSpecificPack == nil, case .available(_, false) = canInstallPeerSpecificPack { entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: false)) } @@ -250,7 +257,7 @@ private func chatMediaInputGridEntries(view: ItemCollectionsView, savedStickers: } if view.higher == nil { - if peerSpecificPack == nil, case .available(true) = canInstallPeerSpecificPack { + if peerSpecificPack == nil, case .available(_, true) = canInstallPeerSpecificPack { entries.append(.peerSpecificSetup(theme: theme, strings: strings, dismissed: true)) } } @@ -590,7 +597,7 @@ final class ChatMediaInputNode: ChatInputNode { if let peer = peersView.peers[peerId] { var canInstall: CanInstallPeerSpecificPack = .none if packData.canSetup { - canInstall = .available(dismissed: dismissedPeerSpecificPack) + canInstall = .available(peer: peer, dismissed: dismissedPeerSpecificPack) } if let (info, items) = packData.packInfo { return (PeerSpecificPackData(peer: peer, info: info, items: items), canInstall) @@ -624,7 +631,7 @@ final class ChatMediaInputNode: ChatInputNode { savedStickers = orderedView } } - let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, theme: theme) + let panelEntries = chatMediaInputPanelEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, theme: theme) let gridEntries = chatMediaInputGridEntries(view: view, savedStickers: savedStickers, recentStickers: recentStickers, peerSpecificPack: peerSpecificPack.0, canInstallPeerSpecificPack: peerSpecificPack.1, strings: strings, theme: theme) let (previousPanelEntries, previousGridEntries) = previousEntries.swap((panelEntries, gridEntries)) return (view, preparedChatMediaInputPanelEntryTransition(account: account, from: previousPanelEntries, to: panelEntries, inputNodeInteraction: inputNodeInteraction), previousPanelEntries.isEmpty, preparedChatMediaInputGridEntryTransition(account: account, view: view, from: previousGridEntries, to: gridEntries, update: update, interfaceInteraction: controllerInteraction, inputNodeInteraction: inputNodeInteraction), previousGridEntries.isEmpty) diff --git a/TelegramUI/ChatMessageActionItemNode.swift b/TelegramUI/ChatMessageActionItemNode.swift index 1c154fbee6..c64ddf2995 100644 --- a/TelegramUI/ChatMessageActionItemNode.swift +++ b/TelegramUI/ChatMessageActionItemNode.swift @@ -114,6 +114,7 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P case .pinnedMessageUpdated: enum PinnnedMediaType { case text(String) + case game case photo case video case round @@ -137,6 +138,10 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P if let pinnedMessage = pinnedMessage { type = .text(pinnedMessage.text) inner: for media in pinnedMessage.media { + if media is TelegramMediaGame { + type = .game + break inner + } if let _ = media as? TelegramMediaImage { type = .photo } else if let file = media as? TelegramMediaFile { @@ -157,19 +162,19 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P if isVoice { type = .audio } else { - let descriptionString: String - if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty { - descriptionString = title + " — " + performer - } else if let title = title, !title.isEmpty { - descriptionString = title - } else if let performer = performer, !performer.isEmpty { - descriptionString = performer - } else if let fileName = file.fileName { - descriptionString = fileName - } else { - descriptionString = strings.Message_Audio - } - type = .text(descriptionString) +// let descriptionString: String +// if let title = title, let performer = performer, !title.isEmpty, !performer.isEmpty { +// descriptionString = title + " — " + performer +// } else if let title = title, !title.isEmpty { +// descriptionString = title +// } else if let performer = performer, !performer.isEmpty { +// descriptionString = performer +// } else if let fileName = file.fileName { +// descriptionString = strings.Message_File +// } else { +// descriptionString = strings.Message_Audio +// } + type = .file } break inner case .Sticker: @@ -199,6 +204,8 @@ private func universalServiceMessageString(theme: PresentationTheme?, strings: P clippedText = "\(clippedText[...clippedText.index(clippedText.startIndex, offsetBy: 14)])..." } attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedTextMessage(authorName, clippedText), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) + case .game: + attributedString = addAttributesToStringWithRanges(strings.PINNED_GAME(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) case .photo: attributedString = addAttributesToStringWithRanges(strings.Notification_PinnedPhotoMessage(authorName), body: bodyAttributes, argumentAttributes: peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(0, message.author?.id)])) case .video: @@ -434,7 +441,7 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let labelNode: TextNode let filledBackgroundNode: LinkHighlightingNode var linkHighlightingNode: LinkHighlightingNode? - + fileprivate var imageNode: TransformImageNode? private let fetchDisposable = MetaDisposable() required init() { @@ -473,6 +480,22 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { return (contentProperties, nil, CGFloat.greatestFiniteMagnitude, { constrainedSize, position in let attributedString = attributedServiceMessageString(theme: item.presentationData.theme.theme, strings: item.presentationData.strings, message: item.message, accountPeerId: item.account.peerId) + var image: TelegramMediaImage? + for media in item.message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .photoUpdated(img): + image = img + default: + break + } + } + } + + + + let imageSize = CGSize(width: 70.0, height: 70.0) + let (labelLayout, apply) = makeLabelLayout(TextNodeLayoutArguments(attributedString: attributedString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: constrainedSize.width - 32.0, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets())) var labelRects = labelLayout.linesRects() @@ -497,18 +520,47 @@ class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let backgroundApply = backgroundLayout(item.presentationData.theme.theme.chat.serviceMessage.serviceMessageFillColor, labelRects, 10.0, 10.0, 0.0) - let backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) + var backgroundSize = CGSize(width: labelLayout.size.width + 8.0 + 8.0, height: labelLayout.size.height + 4.0) let layoutInsets = UIEdgeInsets(top: 4.0, left: 0.0, bottom: 4.0, right: 0.0) + + if let _ = image { + backgroundSize.height += imageSize.height + 10 + } + return (backgroundSize.width, { boundingWidth in return (backgroundSize, { [weak self] animation in if let strongSelf = self { strongSelf.item = item + if let image = image { + let imageNode: TransformImageNode + if let current = strongSelf.imageNode { + imageNode = current + } else { + imageNode = TransformImageNode() + strongSelf.imageNode = imageNode + strongSelf.insertSubnode(imageNode, at: 0) + let arguments = TransformImageArguments(corners: ImageCorners(radius: imageSize.width / 2), imageSize: imageSize, boundingSize: imageSize, intrinsicInsets: UIEdgeInsets()) + let apply = imageNode.asyncLayout()(arguments) + apply() + + strongSelf.fetchDisposable.set(chatMessagePhotoInteractiveFetched(account: item.account, photoReference: .message(message: MessageReference(item.message), media: image)).start()) + } + let updateImageSignal = chatMessagePhoto(postbox: item.account.postbox, photoReference: ImageMediaReference.message(message: MessageReference(item.message), media: image)) + + imageNode.setSignal(updateImageSignal) + + imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundSize.width - imageSize.width) / 2.0), y: labelLayout.size.height + 10 + 2), size: imageSize) + } else if let imageNode = strongSelf.imageNode { + imageNode.removeFromSupernode() + strongSelf.imageNode = nil + } + let _ = apply() let _ = backgroundApply() - let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) + let labelFrame = CGRect(origin: CGPoint(x: 8.0, y: image != nil ? 2 : floorToScreenPixels((backgroundSize.height - labelLayout.size.height) / 2.0) - 1.0), size: labelLayout.size) strongSelf.labelNode.frame = labelFrame strongSelf.filledBackgroundNode.frame = labelFrame.offsetBy(dx: 0.0, dy: -11.0) } diff --git a/TelegramUI/ChatMessageAttachedContentNode.swift b/TelegramUI/ChatMessageAttachedContentNode.swift index 33fa90947f..63d8cb8903 100644 --- a/TelegramUI/ChatMessageAttachedContentNode.swift +++ b/TelegramUI/ChatMessageAttachedContentNode.swift @@ -263,7 +263,7 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { self.addSubnode(self.statusNode) } - func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ account: Account, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: String?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { + func asyncLayout() -> (_ presentationData: ChatPresentationData, _ automaticDownloadSettings: AutomaticMediaDownloadSettings, _ associatedData: ChatMessageItemAssociatedData, _ account: Account, _ controllerInteraction: ChatControllerInteraction, _ message: Message, _ messageRead: Bool, _ title: String?, _ subtitle: NSAttributedString?, _ text: String?, _ entities: [MessageTextEntity]?, _ media: (Media, ChatMessageAttachedContentNodeMediaFlags)?, _ actionIcon: ChatMessageAttachedContentActionIcon?, _ actionTitle: String?, _ displayLine: Bool, _ layoutConstants: ChatMessageItemLayoutConstants, _ constrainedSize: CGSize) -> (CGFloat, (CGSize, ChatMessageBubbleContentPosition) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> Void))) { let textAsyncLayout = TextNode.asyncLayout(self.textNode) let currentImage = self.media as? TelegramMediaImage let imageLayout = self.inlineImageNode.asyncLayout() @@ -345,11 +345,14 @@ final class ChatMessageAttachedContentNode: ASDisplayNode { notEmpty = true } - if let subtitle = subtitle, !subtitle.isEmpty { + if let subtitle = subtitle, subtitle.length > 0 { if notEmpty { string.append(NSAttributedString(string: "\n", font: textFont, textColor: incoming ? bubbleTheme.incomingPrimaryTextColor : bubbleTheme.outgoingPrimaryTextColor)) } - string.append(NSAttributedString(string: subtitle, font: titleFont, textColor: incoming ? bubbleTheme.incomingPrimaryTextColor : bubbleTheme.outgoingPrimaryTextColor)) + let updatedSubtitle = NSMutableAttributedString() + updatedSubtitle.append(subtitle) + updatedSubtitle.addAttribute(.foregroundColor, value: incoming ? bubbleTheme.incomingPrimaryTextColor : bubbleTheme.outgoingPrimaryTextColor, range: NSMakeRange(0, subtitle.string.count)) + string.append(updatedSubtitle) notEmpty = true } diff --git a/TelegramUI/ChatMessageDateAndStatusNode.swift b/TelegramUI/ChatMessageDateAndStatusNode.swift index 36d6524145..9937ee8e22 100644 --- a/TelegramUI/ChatMessageDateAndStatusNode.swift +++ b/TelegramUI/ChatMessageDateAndStatusNode.swift @@ -204,24 +204,24 @@ class ChatMessageDateAndStatusNode: ASDisplayNode { dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor backgroundImage = graphics.dateAndStatusFreeBackground leftInset = 0.0 - loadedCheckFullImage = graphics.checkMediaFullImage - loadedCheckPartialImage = graphics.checkMediaPartialImage - clockFrameImage = graphics.clockMediaFrameImage - clockMinImage = graphics.clockMediaMinImage + loadedCheckFullImage = graphics.checkFreeFullImage + loadedCheckPartialImage = graphics.checkFreePartialImage + clockFrameImage = graphics.clockFreeFrameImage + clockMinImage = graphics.clockFreeMinImage if impressionCount != nil { - impressionImage = graphics.mediaImpressionIcon + impressionImage = graphics.freeImpressionIcon } case let .FreeOutgoing(status): dateColor = theme.theme.chat.serviceMessage.serviceMessagePrimaryTextColor outgoingStatus = status backgroundImage = graphics.dateAndStatusFreeBackground leftInset = 0.0 - loadedCheckFullImage = graphics.checkMediaFullImage - loadedCheckPartialImage = graphics.checkMediaPartialImage - clockFrameImage = graphics.clockMediaFrameImage - clockMinImage = graphics.clockMediaMinImage + loadedCheckFullImage = graphics.checkFreeFullImage + loadedCheckPartialImage = graphics.checkFreePartialImage + clockFrameImage = graphics.clockFreeFrameImage + clockMinImage = graphics.clockFreeMinImage if impressionCount != nil { - impressionImage = graphics.mediaImpressionIcon + impressionImage = graphics.freeImpressionIcon } } diff --git a/TelegramUI/ChatMessageDateHeader.swift b/TelegramUI/ChatMessageDateHeader.swift index e00e26f352..19f4315a9a 100644 --- a/TelegramUI/ChatMessageDateHeader.swift +++ b/TelegramUI/ChatMessageDateHeader.swift @@ -19,12 +19,12 @@ final class ChatMessageDateHeader: ListViewItemHeader { let id: Int64 let theme: ChatPresentationThemeData let strings: PresentationStrings - - init(timestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings) { + let action:((Int32)->Void)? + init(timestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings, action:((Int32)->Void)? = nil) { self.timestamp = timestamp self.theme = theme self.strings = strings - + self.action = action if timestamp == Int32.max { self.roundedTimestamp = timestamp / (granularity) * (granularity) } else { @@ -38,7 +38,7 @@ final class ChatMessageDateHeader: ListViewItemHeader { let height: CGFloat = 34.0 func node() -> ListViewItemHeaderNode { - return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, theme: self.theme, strings: self.strings) + return ChatMessageDateHeaderNode(localTimestamp: self.roundedTimestamp, theme: self.theme, strings: self.strings, action: self.action) } } @@ -86,11 +86,13 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { private var flashingOnScrolling = false private var stickDistanceFactor: CGFloat = 0.0 + private var action:((Int32)->Void)? = nil - init(localTimestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings) { + init(localTimestamp: Int32, theme: ChatPresentationThemeData, strings: PresentationStrings, action:((Int32)->Void)? = nil) { self.localTimestamp = localTimestamp self.theme = theme self.strings = strings + self.action = action self.labelNode = TextNode() self.labelNode.isLayerBacked = true @@ -158,7 +160,10 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { print("position \(position.x), \(position.y)") } }*/ + + } + override func didLoad() { super.didLoad() @@ -229,6 +234,8 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { } } } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { if !self.bounds.contains(point) { @@ -243,13 +250,15 @@ final class ChatMessageDateHeaderNode: ListViewItemHeaderNode { return nil } + + override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { super.touchesCancelled(touches, with: event) } @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - + action?(self.localTimestamp) } } } diff --git a/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift index 941f0e2ce0..9c27f2f1bf 100644 --- a/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousDescriptionContentNode.swift @@ -34,7 +34,6 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble } let title: String = item.presentationData.strings.Channel_AdminLog_MessagePreviousDescription - let subtitle: String? = nil let text: String if item.message.text.isEmpty { text = item.presentationData.strings.Channel_AdminLog_EmptyMessageText @@ -43,7 +42,7 @@ final class ChatMessageEventLogPreviousDescriptionContentNode: ChatMessageBubble } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift index 665bf53f56..807d52cb3f 100644 --- a/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousLinkContentNode.swift @@ -34,11 +34,10 @@ final class ChatMessageEventLogPreviousLinkContentNode: ChatMessageBubbleContent } let title: String = item.presentationData.strings.Channel_AdminLog_MessagePreviousLink - let subtitle: String? = nil let text: String = item.message.text let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift b/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift index 6f1fe46dc9..4a0f614f47 100644 --- a/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift +++ b/TelegramUI/ChatMessageEventLogPreviousMessageContentNode.swift @@ -34,7 +34,6 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont } let title: String = item.presentationData.strings.Channel_AdminLog_MessagePreviousMessage - let subtitle: String? = nil let text: String if item.message.text.isEmpty { text = item.presentationData.strings.Channel_AdminLog_EmptyMessageText @@ -43,7 +42,7 @@ final class ChatMessageEventLogPreviousMessageContentNode: ChatMessageBubbleCont } let mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? = nil - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, subtitle, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, true, title, nil, text, messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageGameBubbleContentNode.swift b/TelegramUI/ChatMessageGameBubbleContentNode.swift index 68ab749879..da39bb1a50 100644 --- a/TelegramUI/ChatMessageGameBubbleContentNode.swift +++ b/TelegramUI/ChatMessageGameBubbleContentNode.swift @@ -55,7 +55,6 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } var title: String? - let subtitle: String? = nil var text: String? var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? @@ -70,7 +69,7 @@ final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNode { } } - let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, item.read, title, subtitle, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) + let (initialWidth, continueLayout) = contentNodeLayout(item.presentationData, item.controllerInteraction.automaticMediaDownloadSettings, item.associatedData, item.account, item.controllerInteraction, item.message, item.read, title, nil, item.message.text.isEmpty ? text : item.message.text, item.message.text.isEmpty ? nil : messageEntities, mediaAndFlags, nil, nil, true, layoutConstants, constrainedSize) let contentProperties = ChatMessageBubbleContentProperties(hidesSimpleAuthorHeader: false, headerSpacing: 8.0, hidesBackground: .never, forceFullCorners: false, forceAlignment: .none) diff --git a/TelegramUI/ChatMessageInteractiveFileNode.swift b/TelegramUI/ChatMessageInteractiveFileNode.swift index c411fb8a25..943b7a5b82 100644 --- a/TelegramUI/ChatMessageInteractiveFileNode.swift +++ b/TelegramUI/ChatMessageInteractiveFileNode.swift @@ -248,7 +248,7 @@ final class ChatMessageInteractiveFileNode: ASTransformNode { } } } else { - candidateTitleString = NSAttributedString(string: title ?? "Unknown Track", font: titleFont, textColor: incoming ? bubbleTheme.incomingFileTitleColor : bubbleTheme.outgoingFileTitleColor) + candidateTitleString = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: titleFont, textColor: incoming ? bubbleTheme.incomingFileTitleColor : bubbleTheme.outgoingFileTitleColor) let descriptionText: String if let performer = performer { descriptionText = performer diff --git a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift index d1114ad363..f764a9afb2 100644 --- a/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift +++ b/TelegramUI/ChatMessageInteractiveInstantVideoNode.swift @@ -226,21 +226,21 @@ class ChatMessageInteractiveInstantVideoNode: ASDisplayNode { let edited = false let sentViaBot = false - let viewCount: Int? = nil - /*for attribute in item.message.attributes { + var viewCount: Int? = nil + for attribute in item.message.attributes { if let _ = attribute as? EditedMessageAttribute { - edited = true + // edited = true } else if let attribute = attribute as? ViewCountMessageAttribute { viewCount = attribute.count - } else if let _ = attribute as? InlineBotMessageAttribute { - sentViaBot = true - } + }// else if let _ = attribute as? InlineBotMessageAttribute { + // sentViaBot = true + // } } - if let author = item.message.author as? TelegramUser, author.botInfo != nil { - sentViaBot = true - }*/ + // if let author = item.message.author as? TelegramUser, author.botInfo != nil { + // sentViaBot = true + // } - let dateText = stringForMessageTimestampStatus(message: item.message, timeFormat: item.presentationData.timeFormat, strings: item.presentationData.strings, format: .minimal) + let dateText = stringForMessageTimestampStatus(message: item.message, timeFormat: item.presentationData.timeFormat, strings: item.presentationData.strings, format: .regular) let (dateAndStatusSize, dateAndStatusApply) = makeDateAndStatusLayout(item.presentationData.theme, item.presentationData.strings, edited && !sentViaBot, viewCount, dateText, statusType, CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)) diff --git a/TelegramUI/ChatMessageInteractiveMediaNode.swift b/TelegramUI/ChatMessageInteractiveMediaNode.swift index c6aadd7098..eaf9ced7c5 100644 --- a/TelegramUI/ChatMessageInteractiveMediaNode.swift +++ b/TelegramUI/ChatMessageInteractiveMediaNode.swift @@ -88,6 +88,12 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { let _ = account.postbox.transaction({ transaction -> Void in deleteMessages(transaction: transaction, mediaBox: account.postbox.mediaBox, ids: [message.id]) }).start() + } else if let media = media, let account = self.account, let message = message { + if let media = media as? TelegramMediaFile { + messageMediaFileCancelInteractiveFetch(account: account, messageId: message.id, file: media) + } else if let media = media as? TelegramMediaImage, let resource = largestImageRepresentation(media.representations)?.resource { + messageMediaImageCancelInteractiveFetch(account: account, messageId: message.id, image: media, resource: resource) + } } if let cancel = self.fetchControls.with({ return $0?.cancel }) { cancel() @@ -525,7 +531,11 @@ final class ChatMessageInteractiveMediaNode: ASTransformNode { if let invoice = invoice { let string = NSMutableAttributedString() if invoice.receiptMessageId != nil { - string.append(NSAttributedString(string: strings.Checkout_Receipt_Title.uppercased())) + var title = strings.Checkout_Receipt_Title.uppercased() + if invoice.flags.contains(.isTest) { + title += " (Test)" + } + string.append(NSAttributedString(string: title)) } else { string.append(NSAttributedString(string: "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) ", attributes: [ChatTextInputAttributes.bold: true as NSNumber])) diff --git a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift index 15acc0d8fe..c6951ca6a2 100644 --- a/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift +++ b/TelegramUI/ChatMessageInvoiceBubbleContentNode.swift @@ -5,6 +5,9 @@ import AsyncDisplayKit import SwiftSignalKit import TelegramCore +private let titleFont: UIFont = Font.semibold(15.0) +private let textFont: UIFont = Font.regular(15.0) + final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { private var invoice: TelegramMediaInvoice? @@ -41,7 +44,7 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { } var title: String? - let subtitle: String? = nil + var subtitle: NSAttributedString? = nil var text: String? var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? @@ -51,6 +54,17 @@ final class ChatMessageInvoiceBubbleContentNode: ChatMessageBubbleContentNode { if let image = invoice.photo { mediaAndFlags = (image, [.preferMediaBeforeText]) + } else { + let invoiceLabel = item.presentationData.strings.Message_InvoiceLabel + var invoiceText = "\(formatCurrencyAmount(invoice.totalAmount, currency: invoice.currency)) " + invoiceText += invoiceLabel + if invoice.flags.contains(.isTest) { + invoiceText += " (Test)" + } + + let string = NSMutableAttributedString(string: invoiceText, attributes: [.font: textFont]) + string.addAttribute(.font, value: titleFont, range: NSMakeRange(0, invoiceLabel.count)) + subtitle = string } } diff --git a/TelegramUI/ChatMessageItem.swift b/TelegramUI/ChatMessageItem.swift index 9dfb97b459..449a753bb3 100644 --- a/TelegramUI/ChatMessageItem.swift +++ b/TelegramUI/ChatMessageItem.swift @@ -278,7 +278,35 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { self.effectiveAuthorId = effectiveAuthor?.id - self.header = ChatMessageDateHeader(timestamp: content.index.timestamp, theme: presentationData.theme, strings: presentationData.strings) + + self.header = ChatMessageDateHeader(timestamp: content.index.timestamp, theme: presentationData.theme, strings: presentationData.strings, action: { timestamp in + +// + var calendar = NSCalendar.current + + calendar.timeZone = TimeZone(abbreviation: "UTC")! + let date = Date(timeIntervalSince1970: TimeInterval(timestamp)) + let components = calendar.dateComponents([.year, .month, .day], from: date) + + if let date = calendar.date(from: components) { + controllerInteraction.navigateToFirstDateMessage(Int32(date.timeIntervalSince1970)) + } + + + /* + unsigned unitFlags = NSCalendarUnitDay| NSCalendarUnitYear | NSCalendarUnitMonth; + NSDateComponents *components = [cal components:unitFlags fromDate:date]; + NSDateComponents *comps = [[NSDateComponents alloc] init]; + comps.day = day; + comps.year = components.year; + comps.month = components.month; + return [cal dateFromComponents:comps]; + */ + + // item.chatInteraction?.jumpToDate(CalendarUtils.monthDay(components.day!, date: date)) + + + }) if displayAuthorInfo { let message = content.firstMessage @@ -302,6 +330,7 @@ public final class ChatMessageItem: ListViewItem, CustomStringConvertible { } } self.accessoryItem = accessoryItem + } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) { diff --git a/TelegramUI/ChatMessageSelectionNode.swift b/TelegramUI/ChatMessageSelectionNode.swift index 3d25315934..11af9c2669 100644 --- a/TelegramUI/ChatMessageSelectionNode.swift +++ b/TelegramUI/ChatMessageSelectionNode.swift @@ -10,6 +10,7 @@ final class ChatMessageSelectionNode: ASDisplayNode { init(theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { self.toggle = toggle self.checkNode = CheckNode(strokeColor: theme.list.itemCheckColors.strokeColor, fillColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor, style: .overlay) + self.checkNode.isUserInteractionEnabled = false super.init() diff --git a/TelegramUI/ChatMessageStickerItemNode.swift b/TelegramUI/ChatMessageStickerItemNode.swift index b9f2975f8c..ca924cb2e3 100644 --- a/TelegramUI/ChatMessageStickerItemNode.swift +++ b/TelegramUI/ChatMessageStickerItemNode.swift @@ -78,14 +78,12 @@ class ChatMessageStickerItemNode: ChatMessageItemView { for media in item.message.media { if let telegramFile = media as? TelegramMediaFile { - if self.telegramFile != telegramFile { - self.telegramFile = telegramFile - + if self.telegramFile == nil || !telegramFile.isSemanticallyEqual(to: self.telegramFile!) { let signal = chatMessageSticker(account: item.account, file: telegramFile, small: false) self.imageNode.setSignal(signal) self.fetchDisposable.set(freeMediaFileInteractiveFetched(account: item.account, fileReference: .message(message: MessageReference(item.message), media: telegramFile)).start()) } - + self.telegramFile = telegramFile break } } diff --git a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index ea33946c40..8a22bc56ba 100644 --- a/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -101,6 +101,8 @@ private func instantPageBlockMedia(pageId: MediaId, block: InstantPageBlock, med return [] } +private let titleFont: UIFont = Font.semibold(15.0) + final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { private var webPage: TelegramMediaWebpage? @@ -178,7 +180,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } var title: String? - var subtitle: String? + var subtitle: NSAttributedString? var text: String? var entities: [MessageTextEntity]? var mediaAndFlags: (Media, ChatMessageAttachedContentNodeMediaFlags)? @@ -193,7 +195,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } if let title = webpage.title, !title.isEmpty { - subtitle = title + subtitle = NSAttributedString(string: title, font: titleFont) } if let textValue = webpage.text, !textValue.isEmpty { @@ -251,7 +253,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { switch type { case "telegram_channel": actionTitle = item.presentationData.strings.Conversation_ViewChannel - case "telegram_chat": + case "telegram_chat", "telegram_megagroup": actionTitle = item.presentationData.strings.Conversation_ViewGroup case "telegram_message": actionTitle = item.presentationData.strings.Conversation_ViewMessage diff --git a/TelegramUI/ChatPresentationInterfaceState.swift b/TelegramUI/ChatPresentationInterfaceState.swift index b73327070e..70279b2482 100644 --- a/TelegramUI/ChatPresentationInterfaceState.swift +++ b/TelegramUI/ChatPresentationInterfaceState.swift @@ -376,6 +376,8 @@ final class ChatPresentationInterfaceState: Equatable { let peerIsBlocked: Bool let peerIsMuted: Bool let canReportPeer: Bool + let callsAvailable: Bool + let callsPrivate: Bool let chatHistoryState: ChatHistoryNodeHistoryState? let botStartPayload: String? let urlPreview: (String, TelegramMediaWebpage)? @@ -406,6 +408,8 @@ final class ChatPresentationInterfaceState: Equatable { self.peerIsBlocked = false self.peerIsMuted = false self.canReportPeer = false + self.callsAvailable = false + self.callsPrivate = false self.chatHistoryState = nil self.botStartPayload = nil self.urlPreview = nil @@ -420,7 +424,7 @@ final class ChatPresentationInterfaceState: Equatable { self.mode = mode } - init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isContact: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { + init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isContact: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: Message?, peerIsBlocked: Bool, peerIsMuted: Bool, canReportPeer: Bool, callsAvailable: Bool, callsPrivate: Bool, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, fontSize: PresentationFontSize, accountPeerId: PeerId, mode: ChatControllerPresentationMode) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -437,6 +441,8 @@ final class ChatPresentationInterfaceState: Equatable { self.peerIsBlocked = peerIsBlocked self.peerIsMuted = peerIsMuted self.canReportPeer = canReportPeer + self.callsAvailable = callsAvailable + self.callsPrivate = callsPrivate self.chatHistoryState = chatHistoryState self.botStartPayload = botStartPayload self.urlPreview = urlPreview @@ -516,6 +522,14 @@ final class ChatPresentationInterfaceState: Equatable { return false } + if lhs.callsAvailable != rhs.callsAvailable { + return false + } + + if lhs.callsPrivate != rhs.callsPrivate { + return false + } + if lhs.peerIsBlocked != rhs.peerIsBlocked { return false } @@ -590,15 +604,15 @@ final class ChatPresentationInterfaceState: Equatable { } func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedIsContact(_ isContact: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -609,91 +623,99 @@ final class ChatPresentationInterfaceState: Equatable { } else { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPinnedMessage(_ pinnedMessage: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedCanReportPeer(_ canReportPeer: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + } + + func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + } + + func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: mode) } func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isContact: self.isContact, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, canReportPeer: self.canReportPeer, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, fontSize: self.fontSize, accountPeerId: self.accountPeerId, mode: self.mode) } } diff --git a/TelegramUI/ChatRecentActionsControllerNode.swift b/TelegramUI/ChatRecentActionsControllerNode.swift index ccf8783342..99ece81e7e 100644 --- a/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/TelegramUI/ChatRecentActionsControllerNode.swift @@ -366,6 +366,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in return false + }, navigateToFirstDateMessage: { _ in + }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: self.automaticMediaDownloadSettings) @@ -545,14 +547,17 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { strongSelf.emptyNode.alpha = displayEmptyNode ? 1.0 : 0.0 strongSelf.emptyNode.layer.animateAlpha(from: displayEmptyNode ? 0.0 : 1.0, to: displayEmptyNode ? 1.0 : 0.0, duration: 0.25) + + let hasFilter: Bool = strongSelf.filter.events != .all || strongSelf.filter.query != nil + if displayEmptyNode { var text: String = "" - if let query = strongSelf.filter.query { + if let query = strongSelf.filter.query, hasFilter { text = strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterQueryText(query).0 } else { - text = strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterText + text = strongSelf.presentationData.strings.Channel_AdminLog_EmptyText } - strongSelf.emptyNode.setup(title: strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterTitle, text: text) + strongSelf.emptyNode.setup(title: hasFilter ? strongSelf.presentationData.strings.Channel_AdminLog_EmptyFilterTitle : strongSelf.presentationData.strings.Channel_AdminLog_EmptyTitle, text: text) } } let isLoading = !displayingResults diff --git a/TelegramUI/ChatRecentActionsHistoryTransition.swift b/TelegramUI/ChatRecentActionsHistoryTransition.swift index c346f42bd2..0f1bb6a08a 100644 --- a/TelegramUI/ChatRecentActionsHistoryTransition.swift +++ b/TelegramUI/ChatRecentActionsHistoryTransition.swift @@ -475,6 +475,7 @@ struct ChatRecentActionsEntry: Comparable, Identifiable { for (_, peer) in participant.peers { peers[peer.id] = peer } + peers[participant.peer.id] = participant.peer let action: TelegramMediaActionType action = TelegramMediaActionType.addedMembers(peerIds: [participant.peer.id]) diff --git a/TelegramUI/ContactsPeerItem.swift b/TelegramUI/ContactsPeerItem.swift index 3f60ed8ad1..dbb38ca7f4 100644 --- a/TelegramUI/ContactsPeerItem.swift +++ b/TelegramUI/ContactsPeerItem.swift @@ -405,6 +405,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { updatedSelectionNode = selectionNode } else { selectionNode = CheckNode(strokeColor: item.theme.list.itemCheckColors.strokeColor, fillColor: item.theme.list.itemCheckColors.fillColor, foregroundColor: item.theme.list.itemCheckColors.foregroundColor, style: .plain) + selectionNode.isUserInteractionEnabled = false updatedSelectionNode = selectionNode } } @@ -705,7 +706,7 @@ class ContactsPeerItemNode: ItemListRevealOptionsItemNode { if item.editing.editable { - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) strongSelf.setRevealOptionsOpened(item.editing.revealed, animated: animated) } else { strongSelf.setRevealOptions((left: [], right: [])) diff --git a/TelegramUI/DefaultPresentationTheme.swift b/TelegramUI/DefaultPresentationTheme.swift index af39a49f17..bbe7851c16 100644 --- a/TelegramUI/DefaultPresentationTheme.swift +++ b/TelegramUI/DefaultPresentationTheme.swift @@ -81,8 +81,8 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr freeMonoIcon: UIColor(rgb: 0x7e7e87), itemSwitchColors: switchColors, itemDisclosureActions: PresentationThemeItemDisclosureActions( - neutral1: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xbcbcc3), foregroundColor: .white), - neutral2: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xaaaab3), foregroundColor: .white), + neutral1: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0x4892f2), foregroundColor: .white), + neutral2: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xf09a37), foregroundColor: .white), destructive: PresentationThemeItemDisclosureAction(fillColor: UIColor(rgb: 0xff3824), foregroundColor: .white), constructive: PresentationThemeItemDisclosureAction(fillColor: constructiveColor, foregroundColor: .white), accent: PresentationThemeItemDisclosureAction(fillColor: accentColor, foregroundColor: .white), @@ -274,13 +274,13 @@ private func makeDefaultPresentationTheme(accentColor: UIColor, day: Bool) -> Pr ) let serviceMessageDay = PresentationThemeServiceMessage( - serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.45), + serviceMessageFillColor: UIColor(rgb: 0xffffff, alpha: 0.8), serviceMessagePrimaryTextColor: UIColor(rgb: 0x8D8E93), serviceMessageLinkHighlightColor: UIColor(rgb: 0x748391, alpha: 0.25), unreadBarFillColor: UIColor(white: 1.0, alpha: 1.0), unreadBarStrokeColor: UIColor(white: 1.0, alpha: 1.0), unreadBarTextColor: UIColor(rgb: 0x8D8E93), - dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.45), + dateFillStaticColor: UIColor(rgb: 0xffffff, alpha: 0.8), dateFillFloatingColor: UIColor(rgb: 0xffffff, alpha: 0.8), dateTextColor: UIColor(rgb: 0x8D8E93) ) diff --git a/TelegramUI/DeviceContactInfoController.swift b/TelegramUI/DeviceContactInfoController.swift index 313e7d5fc7..560926b9b1 100644 --- a/TelegramUI/DeviceContactInfoController.swift +++ b/TelegramUI/DeviceContactInfoController.swift @@ -25,8 +25,9 @@ private final class DeviceContactInfoControllerArguments { let callPhone: (String) -> Void let openUrl: (String) -> Void let openAddress: (DeviceContactAddressData) -> Void + let displayCopyContextMenu: (DeviceContactInfoEntryTag, String) -> Void - init(account: Account, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void) { + init(account: Account, updateEditingName: @escaping (ItemListAvatarAndNameInfoItemName) -> Void, updatePhone: @escaping (Int64, String) -> Void, updatePhoneLabel: @escaping (Int64, String) -> Void, deletePhone: @escaping (Int64) -> Void, setPhoneIdWithRevealedOptions: @escaping (Int64?, Int64?) -> Void, addPhoneNumber: @escaping () -> Void, performAction: @escaping (DeviceContactInfoAction) -> Void, toggleSelection: @escaping (DeviceContactInfoDataId) -> Void, callPhone: @escaping (String) -> Void, openUrl: @escaping (String) -> Void, openAddress: @escaping (DeviceContactAddressData) -> Void, displayCopyContextMenu: @escaping (DeviceContactInfoEntryTag, String) -> Void) { self.account = account self.updateEditingName = updateEditingName self.updatePhone = updatePhone @@ -39,6 +40,7 @@ private final class DeviceContactInfoControllerArguments { self.callPhone = callPhone self.openUrl = openUrl self.openAddress = openAddress + self.displayCopyContextMenu = displayCopyContextMenu } } @@ -49,8 +51,9 @@ private enum DeviceContactInfoSection: ItemListSectionId { } private enum DeviceContactInfoEntryTag: Equatable, ItemListItemTag { + case info(Int) + case birthday case editingPhone(Int64) - case company func isEqual(to other: ItemListItemTag) -> Bool { return self == (other as? DeviceContactInfoEntryTag) @@ -335,14 +338,18 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { case let .company(_, theme, title, value, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: value, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { }, tag: nil) - case let .phoneNumber(_, _, theme, title, label, value, selected): + case let .phoneNumber(_, index, theme, title, label, value, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.phoneNumber(label, value)) } else { arguments.callPhone(value) } - }, tag: nil) + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), value) + } + }, tag: DeviceContactInfoEntryTag.info(index)) case let .editingPhoneNumber(_, theme, strings, id, title, label, value, hasActiveRevealControls): return UserInfoEditingPhoneItem(theme: theme, strings: strings, id: id, label: title, value: value, editing: UserInfoEditingPhoneItemEditing(editable: true, hasActiveRevealControls: hasActiveRevealControls), sectionId: self.section, setPhoneIdWithRevealedOptions: { lhs, rhs in arguments.setPhoneIdWithRevealedOptions(lhs, rhs) @@ -357,23 +364,31 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { return UserInfoEditingPhoneActionItem(theme: theme, title: title, sectionId: self.section, action: { arguments.addPhoneNumber() }) - case let .email(_, _, theme, title, label, value, selected): + case let .email(_, index, theme, title, label, value, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.email(label, value)) } else { arguments.openUrl("mailto:\(value)") } - }, tag: nil) - case let .url(_, _, theme, title, label, value, selected): + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), value) + } + }, tag: DeviceContactInfoEntryTag.info(index)) + case let .url(_, index, theme, title, label, value, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: value, textColor: .accent, enabledEntitiyTypes: [], multiline: false, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.url(label, value)) } else { arguments.openUrl(value) } - }, tag: nil) - case let .address(_, _, theme, title, value, selected): + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), value) + } + }, tag: DeviceContactInfoEntryTag.info(index)) + case let .address(_, index, theme, title, value, selected): var string = "" func combineComponent(string: inout String, component: String) { if !component.isEmpty { @@ -395,7 +410,11 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } else { arguments.openAddress(value) } - }, tag: nil) + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), string) + } + }, tag: DeviceContactInfoEntryTag.info(index)) case let .birthday(_, theme, title, value, text, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: text, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { @@ -420,21 +439,33 @@ private enum DeviceContactInfoEntry: ItemListNodeEntry { } } } - }, tag: nil) - case let .socialProfile(_, _, theme, title, value, text, selected): + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.birthday, text) + } + }, tag: DeviceContactInfoEntryTag.birthday) + case let .socialProfile(_, index, theme, title, value, text, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: text, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.socialProfile(value)) } else if value.url.count > 0 { arguments.openUrl(value.url) } - }, tag: nil) - case let .instantMessenger(_, _, theme, title, value, text, selected): + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), text) + } + }, tag: DeviceContactInfoEntryTag.info(index)) + case let .instantMessenger(_, index, theme, title, value, text, selected): return ItemListTextWithLabelItem(theme: theme, label: title, text: text, textColor: .accent, enabledEntitiyTypes: [], multiline: true, selected: selected, sectionId: self.section, action: { if selected != nil { arguments.toggleSelection(.instantMessenger(value)) } - }, tag: nil) + }, longTapAction: { + if selected == nil { + arguments.displayCopyContextMenu(.info(index), text) + } + }, tag: DeviceContactInfoEntryTag.info(index)) } } } @@ -850,12 +881,14 @@ func deviceContactInfoController(account: Account, subject: DeviceContactInfoSub } return state } - }, callPhone: { phoneNumer in - + }, callPhone: { phoneNumber in + requestCallImpl(phoneNumber) }, openUrl: { url in openUrlImpl?(url) }, openAddress: { address in openAddressImpl?(address) + }, displayCopyContextMenu: { tag, value in + displayCopyContextMenuImpl?(tag, value) }) let contactData: Signal<(Peer?, DeviceContactStableId?, DeviceContactExtendedData), NoError> @@ -1009,7 +1042,7 @@ func deviceContactInfoController(account: Account, subject: DeviceContactInfoSub let _ = strongController.frameForItemNode({ itemNode in if let itemNode = itemNode as? ItemListTextWithLabelItemNode { if let itemTag = itemNode.tag as? DeviceContactInfoEntryTag { - if itemTag == tag { + if itemTag == tag && itemNode.item?.text == value { resultItemNode = itemNode return true } diff --git a/TelegramUI/EditSettingsController.swift b/TelegramUI/EditSettingsController.swift index 46bf8c71a4..4b1ace32e2 100644 --- a/TelegramUI/EditSettingsController.swift +++ b/TelegramUI/EditSettingsController.swift @@ -151,7 +151,7 @@ private enum SettingsEntry: ItemListNodeEntry { func item(_ arguments: EditSettingsItemArguments) -> ListViewItem { switch self { case let .userInfo(theme, strings, peer, cachedData, state, updatingImage): - return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, mode: .generic, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max)), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false), editingNameUpdated: { editingName in + return ItemListAvatarAndNameInfoItem(account: arguments.account, theme: theme, strings: strings, mode: .editSettings, peer: peer, presence: TelegramUserPresence(status: .present(until: Int32.max)), cachedData: cachedData, state: state, sectionId: ItemListSectionId(self.section), style: .blocks(withTopInset: false), editingNameUpdated: { editingName in arguments.updateEditingName(editingName) }, avatarTapped: { arguments.avatarTapAction() diff --git a/TelegramUI/GalleryThumbnailContainerNode.swift b/TelegramUI/GalleryThumbnailContainerNode.swift index 1be8aad4e5..6003f76824 100644 --- a/TelegramUI/GalleryThumbnailContainerNode.swift +++ b/TelegramUI/GalleryThumbnailContainerNode.swift @@ -205,4 +205,16 @@ final class GalleryThumbnailContainerNode: ASDisplayNode, UIScrollViewDelegate { delay += 0.01 } } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + + } } diff --git a/TelegramUI/GridMessageSelectionNode.swift b/TelegramUI/GridMessageSelectionNode.swift index 84c8385843..0082ce09c3 100644 --- a/TelegramUI/GridMessageSelectionNode.swift +++ b/TelegramUI/GridMessageSelectionNode.swift @@ -11,6 +11,7 @@ final class GridMessageSelectionNode: ASDisplayNode { init(theme: PresentationTheme, toggle: @escaping (Bool) -> Void) { self.toggle = toggle self.checkNode = CheckNode(strokeColor: theme.list.itemCheckColors.strokeColor, fillColor: theme.list.itemCheckColors.fillColor, foregroundColor: theme.list.itemCheckColors.foregroundColor, style: .overlay) + self.checkNode.isUserInteractionEnabled = false super.init() diff --git a/TelegramUI/InstalledStickerPacksController.swift b/TelegramUI/InstalledStickerPacksController.swift index a5197825e4..e304bbd458 100644 --- a/TelegramUI/InstalledStickerPacksController.swift +++ b/TelegramUI/InstalledStickerPacksController.swift @@ -13,10 +13,10 @@ private final class InstalledStickerPacksControllerArguments { let openStickersBot: () -> Void let openMasks: () -> Void let openFeatured: () -> Void - let openArchived: () -> Void + let openArchived: ([ArchivedStickerPackItem]?) -> Void let openSuggestOptions: () -> Void - init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping () -> Void, openSuggestOptions: @escaping () -> Void) { + init(account: Account, openStickerPack: @escaping (StickerPackCollectionInfo) -> Void, setPackIdWithRevealedOptions: @escaping (ItemCollectionId?, ItemCollectionId?) -> Void, removePack: @escaping (ItemCollectionId) -> Void, openStickersBot: @escaping () -> Void, openMasks: @escaping () -> Void, openFeatured: @escaping () -> Void, openArchived: @escaping ([ArchivedStickerPackItem]?) -> Void, openSuggestOptions: @escaping () -> Void) { self.account = account self.openStickerPack = openStickerPack self.setPackIdWithRevealedOptions = setPackIdWithRevealedOptions @@ -68,7 +68,7 @@ private enum InstalledStickerPacksEntryId: Hashable { private enum InstalledStickerPacksEntry: ItemListNodeEntry { case suggestOptions(PresentationTheme, String, String) case trending(PresentationTheme, String, Int32) - case archived(PresentationTheme, String) + case archived(PresentationTheme, String, Int32, [ArchivedStickerPackItem]?) case masks(PresentationTheme, String) case packsTitle(PresentationTheme, String) case pack(Int32, PresentationTheme, PresentationStrings, StickerPackCollectionInfo, StickerPackItem?, String, Bool, ItemListStickerPackItemEditing) @@ -122,8 +122,8 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { } else { return false } - case let .archived(lhsTheme, lhsCount): - if case let .archived(rhsTheme, rhsCount) = rhs, lhsTheme === rhsTheme, lhsCount == rhsCount { + case let .archived(lhsTheme, lhsText, lhsCount, _): + if case let .archived(rhsTheme, rhsText, rhsCount, _) = rhs, lhsTheme === rhsTheme, lhsCount == rhsCount, lhsText == rhsText { return true } else { return false @@ -243,9 +243,9 @@ private enum InstalledStickerPacksEntry: ItemListNodeEntry { return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { arguments.openMasks() }) - case let .archived(theme, text): - return ItemListDisclosureItem(theme: theme, title: text, label: "", sectionId: self.section, style: .blocks, action: { - arguments.openArchived() + case let .archived(theme, text, count, archived): + return ItemListDisclosureItem(theme: theme, title: text, label: count == 0 ? "" : "\(count)", sectionId: self.section, style: .blocks, action: { + arguments.openArchived(archived) }) case let .packsTitle(theme, text): return ItemListSectionHeaderItem(theme: theme, text: text, sectionId: self.section) @@ -309,7 +309,7 @@ private func namespaceForMode(_ mode: InstalledStickerPacksControllerMode) -> It } } -private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, featured: [FeaturedStickerPackItem], stickerSettings: StickerSettings) -> [InstalledStickerPacksEntry] { +private func installedStickerPacksControllerEntries(presentationData: PresentationData, state: InstalledStickerPacksControllerState, mode: InstalledStickerPacksControllerMode, view: CombinedView, featured: [FeaturedStickerPackItem], archived: [ArchivedStickerPackItem]?, stickerSettings: StickerSettings) -> [InstalledStickerPacksEntry] { var entries: [InstalledStickerPacksEntry] = [] switch mode { @@ -334,7 +334,9 @@ private func installedStickerPacksControllerEntries(presentationData: Presentati } entries.append(.trending(presentationData.theme, presentationData.strings.StickerPacksSettings_FeaturedPacks, unreadCount)) } - entries.append(.archived(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks)) + if let archived = archived, !archived.isEmpty { + entries.append(.archived(presentationData.theme, presentationData.strings.StickerPacksSettings_ArchivedPacks, Int32(archived.count), archived)) + } entries.append(.masks(presentationData.theme, presentationData.strings.MaskStickerSettings_Title)) entries.append(.packsTitle(presentationData.theme, presentationData.strings.StickerPacksSettings_StickerPacksSection)) case .masks: @@ -376,7 +378,7 @@ public enum InstalledStickerPacksControllerMode { case masks } -public func installedStickerPacksController(account: Account, mode: InstalledStickerPacksControllerMode) -> ViewController { +public func installedStickerPacksController(account: Account, mode: InstalledStickerPacksControllerMode, archivedPacks:[ArchivedStickerPackItem]? = nil) -> ViewController { let initialState = InstalledStickerPacksControllerState().withUpdatedEditing(mode == .modal) let statePromise = ValuePromise(initialState, ignoreRepeated: true) let stateValue = Atomic(value: initialState) @@ -434,11 +436,11 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti } })) }, openMasks: { - pushControllerImpl?(installedStickerPacksController(account: account, mode: .masks)) + pushControllerImpl?(installedStickerPacksController(account: account, mode: .masks, archivedPacks: archivedPacks)) }, openFeatured: { pushControllerImpl?(featuredStickerPacksController(account: account)) - }, openArchived: { - pushControllerImpl?(archivedStickerPacksController(account: account)) + }, openArchived: { archived in + pushControllerImpl?(archivedStickerPacksController(account: account, archived: archived)) }, openSuggestOptions: { let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } let controller = ActionSheetController(presentationTheme: presentationData.theme) @@ -482,22 +484,30 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti stickerPacks.set(account.postbox.combinedView(keys: [.itemCollectionInfos(namespaces: [namespaceForMode(mode)])])) let featured = Promise<[FeaturedStickerPackItem]>() + let archivedPromise = Promise<[ArchivedStickerPackItem]?>() + switch mode { case .general, .modal: featured.set(account.viewTracker.featuredStickerPacks()) + archivedPromise.set(.single(archivedPacks) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) case .masks: featured.set(.single([])) + archivedPromise.set(.single(nil)) } + var previousPackCount: Int? let stickerSettingsKey = ApplicationSpecificPreferencesKeys.stickerSettings let preferencesKey: PostboxViewKey = .preferences(keys: Set([stickerSettingsKey])) let preferencesView = account.postbox.combinedView(keys: [preferencesKey]) - let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, featured.get() |> deliverOnMainQueue, preferencesView |> deliverOnMainQueue) + + + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get() |> deliverOnMainQueue, stickerPacks.get() |> deliverOnMainQueue, combineLatest(featured.get() |> deliverOnMainQueue, archivedPromise.get() |> deliverOnMainQueue), preferencesView |> deliverOnMainQueue) |> deliverOnMainQueue - |> map { presentationData, state, view, featured, preferencesView -> (ItemListControllerState, (ItemListNodeState, InstalledStickerPacksEntry.ItemGenerationArguments)) in + |> map { presentationData, state, view, featuredAndArchived, preferencesView -> (ItemListControllerState, (ItemListNodeState, InstalledStickerPacksEntry.ItemGenerationArguments)) in + var stickerSettings = StickerSettings.defaultSettings if let view = preferencesView.views[preferencesKey] as? PreferencesView { if let value = view.values[stickerSettingsKey] as? StickerSettings { @@ -554,7 +564,7 @@ public func installedStickerPacksController(account: Account, mode: InstalledSti let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, featured: featured, stickerSettings: stickerSettings), style: .blocks, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10)) + let listState = ItemListNodeState(entries: installedStickerPacksControllerEntries(presentationData: presentationData, state: state, mode: mode, view: view, featured: featuredAndArchived.0, archived: featuredAndArchived.1, stickerSettings: stickerSettings), style: .blocks, animateChanges: previous != nil && packCount != nil && (previous! != 0 && previous! >= packCount! - 10)) return (controllerState, (listState, arguments)) } |> afterDisposed { actionsDisposable.dispose() diff --git a/TelegramUI/ItemListActivityTextItem.swift b/TelegramUI/ItemListActivityTextItem.swift index 9f8f1cf91d..86738628a2 100644 --- a/TelegramUI/ItemListActivityTextItem.swift +++ b/TelegramUI/ItemListActivityTextItem.swift @@ -88,8 +88,11 @@ class ItemListActivityTextItemNode: ListViewItemNode { } let titleString = NSMutableAttributedString(attributedString: item.text) - titleString.removeAttribute(NSAttributedStringKey.font, range: NSMakeRange(0, titleString.length)) - titleString.addAttributes([NSAttributedStringKey.font: titleFont], range: NSMakeRange(0, titleString.length)) + let hasFont = titleString.attribute(.font, at: 0, effectiveRange: nil) != nil + if !hasFont { + titleString.removeAttribute(NSAttributedStringKey.font, range: NSMakeRange(0, titleString.length)) + titleString.addAttributes([NSAttributedStringKey.font: titleFont], range: NSMakeRange(0, titleString.length)) + } let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: titleString, backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.rightInset - 20.0 - 22.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: TextNodeCutout(position: .TopLeft, size: CGSize(width: activityWidth, height: 4.0)), insets: UIEdgeInsets())) diff --git a/TelegramUI/ItemListAvatarAndNameItem.swift b/TelegramUI/ItemListAvatarAndNameItem.swift index bbaaff7887..c7c95a4518 100644 --- a/TelegramUI/ItemListAvatarAndNameItem.swift +++ b/TelegramUI/ItemListAvatarAndNameItem.swift @@ -144,6 +144,7 @@ enum ItemListAvatarAndNameInfoItemUpdatingAvatar: Equatable { enum ItemListAvatarAndNameInfoItemMode { case generic case settings + case editSettings } class ItemListAvatarAndNameInfoItem: ListViewItem, ItemListItem { @@ -399,7 +400,7 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite statusText += "@\(username)" } statusColor = item.theme.list.itemSecondaryTextColor - case .generic: + case .generic, .editSettings: if let label = item.label { statusText = label statusColor = item.theme.list.itemSecondaryTextColor @@ -594,6 +595,8 @@ class ItemListAvatarAndNameInfoItemNode: ListViewItemNode, ItemListItemNode, Ite case let .image(representation): overrideImage = .image(representation) } + } else if case .editSettings = item.mode { + overrideImage = AvatarNodeImageOverride.editAvatarIcon } strongSelf.avatarNode.setPeer(account: item.account, peer: peer, overrideImage: overrideImage) } diff --git a/TelegramUI/ItemListMultilineInputItem.swift b/TelegramUI/ItemListMultilineInputItem.swift index b65c33525a..ba70c49ca9 100644 --- a/TelegramUI/ItemListMultilineInputItem.swift +++ b/TelegramUI/ItemListMultilineInputItem.swift @@ -214,7 +214,7 @@ class ItemListMultilineInputItemNode: ListViewItemNode, ASEditableTextNodeDelega } let bottomStripeInset: CGFloat switch neighbors.bottom { - case .sameSection(false): + case .sameSection(true): bottomStripeInset = leftInset default: bottomStripeInset = 0.0 diff --git a/TelegramUI/ItemListPeerItem.swift b/TelegramUI/ItemListPeerItem.swift index c50e000619..e3c527bcfc 100644 --- a/TelegramUI/ItemListPeerItem.swift +++ b/TelegramUI/ItemListPeerItem.swift @@ -268,12 +268,12 @@ class ItemListPeerItemNode: ItemListRevealOptionsItemNode { color = item.theme.list.itemDisclosureActions.destructive.fillColor textColor = item.theme.list.itemDisclosureActions.destructive.foregroundColor } - mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: nil, color: color, textColor: textColor)) + mappedOptions.append(ItemListRevealOption(key: index, title: option.title, icon: .none, color: color, textColor: textColor)) index += 1 } peerRevealOptions = mappedOptions } else { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] } } else { peerRevealOptions = [] diff --git a/TelegramUI/ItemListRecentSessionItem.swift b/TelegramUI/ItemListRecentSessionItem.swift index 1342804195..95c1b071dd 100644 --- a/TelegramUI/ItemListRecentSessionItem.swift +++ b/TelegramUI/ItemListRecentSessionItem.swift @@ -177,7 +177,7 @@ class ItemListRecentSessionItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] if item.editable && item.enabled { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_TerminateSession, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.AuthSessions_TerminateSession, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] } else { peerRevealOptions = [] } diff --git a/TelegramUI/ItemListRevealOptionsNode.swift b/TelegramUI/ItemListRevealOptionsNode.swift index 518590511b..ee99a18519 100644 --- a/TelegramUI/ItemListRevealOptionsNode.swift +++ b/TelegramUI/ItemListRevealOptionsNode.swift @@ -3,10 +3,39 @@ import AsyncDisplayKit import Display import Lottie +enum ItemListRevealOptionIcon: Equatable { + case none + case image(_ image: UIImage) + case animation(_ animation: String, keysToColor: [String]?) + + public static func ==(lhs: ItemListRevealOptionIcon, rhs: ItemListRevealOptionIcon) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false + } + case let .image(lhsImage): + if case let .image(rhsImage) = rhs, lhsImage == rhsImage { + return true + } else { + return false + } + case let .animation(lhsAnimation, lhsKeysToColor): + if case let .animation(rhsAnimation, rhsKeysToColor) = rhs, lhsAnimation == rhsAnimation, lhsKeysToColor == rhsKeysToColor { + return true + } else { + return false + } + } + } +} + struct ItemListRevealOption: Equatable { let key: Int32 let title: String - let icon: UIImage? + let icon: ItemListRevealOptionIcon let color: UIColor let textColor: UIColor @@ -23,14 +52,65 @@ struct ItemListRevealOption: Equatable { if !lhs.textColor.isEqual(rhs.textColor) { return false } - if lhs.icon !== rhs.icon { + if lhs.icon != rhs.icon { return false } return true } } -private let titleFontWithIcon = Font.regular(13.0) +private final class ItemListRevealAnimationNode : ASDisplayNode { + var played = false + + init(animation: String, keysToColor: [String]?, color: UIColor) { + super.init() + + self.setViewBlock({ + if let url = frameworkBundle.url(forResource: animation, withExtension: "json"), let composition = LOTComposition(filePath: url.path) { + let view = LOTAnimationView(model: composition, in: frameworkBundle) + + if let keysToColor = keysToColor { + for key in keysToColor { + let colorCallback = LOTColorValueCallback(color: color.cgColor) + view.setValueDelegate(colorCallback, for: LOTKeypath(string: "\(key).Color")) + } + } + + return view + } else { + return UIView() + } + }) + } + + func animationView() -> LOTAnimationView? { + return self.view as? LOTAnimationView + } + + func play() { + if let animationView = animationView(), !animationView.isAnimationPlaying, !self.played { + self.played = true + animationView.play() + } + } + + func reset() { + if self.played, let animationView = animationView() { + self.played = false + animationView.stop() + } + } + + func preferredSize() -> CGSize? { + if let animationView = animationView(), let sceneModel = animationView.sceneModel { + return CGSize(width: sceneModel.compBounds.width * 0.3333, height: sceneModel.compBounds.height * 0.3333) + } else { + return nil + } + } +} + +private let titleFontWithIcon = Font.medium(13.0) private let titleFontWithoutIcon = Font.regular(17.0) private enum ItemListRevealOptionAlignment { @@ -41,49 +121,42 @@ private enum ItemListRevealOptionAlignment { private final class ItemListRevealOptionNode: ASDisplayNode { private let titleNode: ASTextNode private let iconNode: ASImageNode? + private let animationNode: ItemListRevealAnimationNode? var alignment: ItemListRevealOptionAlignment? - //private var animView: LOTView? - - init(title: String, icon: UIImage?, color: UIColor, textColor: UIColor) { + init(title: String, icon: ItemListRevealOptionIcon, color: UIColor, textColor: UIColor) { self.titleNode = ASTextNode() - self.titleNode.attributedText = NSAttributedString(string: title, font: icon == nil ? titleFontWithoutIcon : titleFontWithIcon, textColor: textColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: icon == .none ? titleFontWithoutIcon : titleFontWithIcon, textColor: textColor) - if let icon = icon { - let iconNode = ASImageNode() - iconNode.image = generateTintedImage(image: icon, color: textColor) - self.iconNode = iconNode - } else { - self.iconNode = nil + switch icon { + case let .image(image): + let iconNode = ASImageNode() + iconNode.image = generateTintedImage(image: image, color: textColor) + self.iconNode = iconNode + self.animationNode = nil + + case let .animation(animation, keysToColor): + self.iconNode = nil + self.animationNode = ItemListRevealAnimationNode(animation: animation, keysToColor: keysToColor, color: color) + break + + case .none: + self.iconNode = nil + self.animationNode = nil } - + super.init() self.addSubnode(self.titleNode) if let iconNode = self.iconNode { self.addSubnode(iconNode) + } else if let animationNode = self.animationNode { + self.addSubnode(animationNode) } self.backgroundColor = color } - override func didLoad() { - super.didLoad() - -// if let url = frameworkBundle.url(forResource: "anim_mute", withExtension: "json") { -// let animView = LOTAnimationView(contentsOf: url) -// animView.frame = CGRect(origin: CGPoint(), size: CGSize(width: 50.0, height: 50.0)) -// self.animView = animView -// self.view.addSubview(animView) -// animView.loopAnimation = true -// animView.logHierarchyKeypaths() -// animView.setValue(UIColor.green, forKeypath: "Outlines.Group 1.Fill 1.Color", atFrame: 0) -// DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 2.0, execute: { -// animView.play() -// }) -// } - } - - func updateLayout(baseSize: CGSize, alignment: ItemListRevealOptionAlignment, extendedWidth: CGFloat, transition: ContainedViewLayoutTransition) { + func updateLayout(baseSize: CGSize, alignment: ItemListRevealOptionAlignment, extendedWidth: CGFloat, transition: ContainedViewLayoutTransition, revealFactor: CGFloat) { var animateAdditive = false if transition.isAnimated, self.alignment != alignment { animateAdditive = true @@ -97,19 +170,29 @@ private final class ItemListRevealOptionNode: ASDisplayNode { case .right: contentRect.origin.x = extendedWidth - contentRect.width } - if let iconNode = self.iconNode, let image = iconNode.image { - let titleIconSpacing: CGFloat = 3.0 - let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - image.size.width) / 2.0), y: contentRect.minY + floor((baseSize.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0)), size: image.size) + + if let animationNode = self.animationNode, let imageSize = animationNode.preferredSize() { + let iconOffset: CGFloat = -2.0 + let titleIconSpacing: CGFloat = 11.0 + let iconFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - imageSize.width) / 2.0), y: contentRect.midY - imageSize.height / 2.0 + iconOffset), size: imageSize) if animateAdditive { - transition.animatePositionAdditive(node: iconNode, offset: CGPoint(x: iconNode.frame.minX - iconFrame.minX, y: 0.0)) + transition.animatePositionAdditive(node: animationNode, offset: CGPoint(x: animationNode.frame.minX - iconFrame.minX, y: 0.0)) } - iconNode.frame = iconFrame - let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width) / 2.0), y: contentRect.minY + floor((baseSize.height - image.size.height - titleIconSpacing - titleSize.height) / 2.0) + image.size.height + titleIconSpacing), size: titleSize) + animationNode.frame = iconFrame + + let titleFrame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width) / 2.0), y: contentRect.midY + titleIconSpacing), size: titleSize) if animateAdditive { transition.animatePositionAdditive(node: self.titleNode, offset: CGPoint(x: self.titleNode.frame.minX - titleFrame.minX, y: 0.0)) } self.titleNode.frame = titleFrame - } else { + + if (fabs(revealFactor) >= 0.4) { + animationNode.play() + } else if fabs(revealFactor) < CGFloat.ulpOfOne { + animationNode.reset() + } + } + else { self.titleNode.frame = CGRect(origin: CGPoint(x: contentRect.minX + floor((baseSize.width - titleSize.width) / 2.0), y: contentRect.minY + floor((baseSize.height - titleSize.height) / 2.0)), size: titleSize) } } @@ -206,7 +289,7 @@ final class ItemListRevealOptionsNode: ASDisplayNode { } } transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: floorToScreenPixels(leftOffset * revealFactor), y: 0.0), size: CGSize(width: extendedWidth, height: size.height))) - node.updateLayout(baseSize: CGSize(width: nodeWidth, height: size.height), alignment: alignment, extendedWidth: extendedWidth, transition: nodeTransition) + node.updateLayout(baseSize: CGSize(width: nodeWidth, height: size.height), alignment: alignment, extendedWidth: extendedWidth, transition: nodeTransition, revealFactor: revealFactor) leftOffset += nodeWidth } } diff --git a/TelegramUI/ItemListSelectableControlNode.swift b/TelegramUI/ItemListSelectableControlNode.swift index 2dc4faf423..af40c06f2a 100644 --- a/TelegramUI/ItemListSelectableControlNode.swift +++ b/TelegramUI/ItemListSelectableControlNode.swift @@ -7,6 +7,7 @@ final class ItemListSelectableControlNode: ASDisplayNode { init(strokeColor: UIColor, fillColor: UIColor, foregroundColor: UIColor) { self.checkNode = CheckNode(strokeColor: strokeColor, fillColor: fillColor, foregroundColor: foregroundColor, style: .plain) + self.checkNode.isUserInteractionEnabled = false super.init() diff --git a/TelegramUI/ItemListSingleLineInputItem.swift b/TelegramUI/ItemListSingleLineInputItem.swift index 53f7b019af..8dbde90424 100644 --- a/TelegramUI/ItemListSingleLineInputItem.swift +++ b/TelegramUI/ItemListSingleLineInputItem.swift @@ -8,6 +8,7 @@ enum ItemListSingleLineInputItemType: Equatable { case password case email case number + case username } class ItemListSingleLineInputItem: ListViewItem, ItemListItem { @@ -224,6 +225,11 @@ class ItemListSingleLineInputItemNode: ListViewItemNode, UITextFieldDelegate, It capitalizationType = .none autocorrectionType = .no keyboardType = UIKeyboardType.numberPad + case .username: + secureEntry = false + capitalizationType = .none + autocorrectionType = .no + keyboardType = UIKeyboardType.asciiCapable } if strongSelf.textNode.textField.isSecureTextEntry != secureEntry { diff --git a/TelegramUI/ItemListStickerPackItem.swift b/TelegramUI/ItemListStickerPackItem.swift index f4c68dd973..6a09354609 100644 --- a/TelegramUI/ItemListStickerPackItem.swift +++ b/TelegramUI/ItemListStickerPackItem.swift @@ -243,7 +243,7 @@ class ItemListStickerPackItemNode: ItemListRevealOptionsItemNode { let packRevealOptions: [ItemListRevealOption] if item.editing.editable && item.enabled { - packRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] + packRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] } else { packRevealOptions = [] } diff --git a/TelegramUI/ItemListTextItem.swift b/TelegramUI/ItemListTextItem.swift index e3213b8b71..a40aa84099 100644 --- a/TelegramUI/ItemListTextItem.swift +++ b/TelegramUI/ItemListTextItem.swift @@ -17,14 +17,15 @@ class ItemListTextItem: ListViewItem, ItemListItem { let text: ItemListTextItemText let sectionId: ItemListSectionId let linkAction: ((ItemListTextItemLinkAction) -> Void)? - + let style: ItemListStyle let isAlwaysPlain: Bool = true - init(theme: PresentationTheme, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil) { + init(theme: PresentationTheme, text: ItemListTextItemText, sectionId: ItemListSectionId, linkAction: ((ItemListTextItemLinkAction) -> Void)? = nil, style: ItemListStyle = .blocks) { self.theme = theme self.text = text self.sectionId = sectionId self.linkAction = linkAction + self.style = style } func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) { @@ -95,7 +96,13 @@ class ItemListTextItemNode: ListViewItemNode { let makeTitleLayout = TextNode.asyncLayout(self.titleNode) return { item, params, neighbors in - let leftInset: CGFloat = 15.0 + params.leftInset + var leftInset: CGFloat = 15.0 + params.leftInset + switch item.style { + case .plain: + leftInset += 20 + case .blocks: + break + } let verticalInset: CGFloat = 7.0 let attributedText: NSAttributedString diff --git a/TelegramUI/LanguageSelectionController.swift b/TelegramUI/LanguageSelectionController.swift index 96ba54486f..6cdcfe7503 100644 --- a/TelegramUI/LanguageSelectionController.swift +++ b/TelegramUI/LanguageSelectionController.swift @@ -74,6 +74,7 @@ private final class LanguageAccessoryView: UIView { private final class InnerCoutrySearchResultsController: UIViewController, UITableViewDelegate, UITableViewDataSource { private let tableView: UITableView + private var presentationData: PresentationData var searchResults: [LocalizationInfo] = [] { didSet { @@ -83,8 +84,9 @@ private final class InnerCoutrySearchResultsController: UIViewController, UITabl var itemSelected: ((LocalizationInfo) -> Void)? - init() { + init(presentationData : PresentationData) { self.tableView = UITableView(frame: CGRect(), style: .plain) + self.presentationData = presentationData super.init(nibName: nil, bundle: nil) } @@ -99,10 +101,15 @@ private final class InnerCoutrySearchResultsController: UIViewController, UITabl self.view.backgroundColor = .white self.view.addSubview(self.tableView) + if #available(iOSApplicationExtension 11.0, *) { + self.tableView.contentInsetAdjustmentBehavior = .never + } self.tableView.frame = self.view.bounds self.tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.tableView.dataSource = self self.tableView.delegate = self + + updateThemeAndStrings() } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { @@ -115,16 +122,41 @@ private final class InnerCoutrySearchResultsController: UIViewController, UITabl cell = currentCell } else { cell = UITableViewCell(style: .subtitle, reuseIdentifier: "LanguageCell") + cell.selectedBackgroundView = UIView() } cell.textLabel?.text = self.searchResults[indexPath.row].title cell.detailTextLabel?.text = self.searchResults[indexPath.row].localizedTitle + cell.textLabel?.textColor = self.presentationData.theme.chatList.titleColor + cell.detailTextLabel?.textColor = self.presentationData.theme.chatList.titleColor + cell.backgroundColor = self.presentationData.theme.chatList.itemBackgroundColor + cell.selectedBackgroundView?.backgroundColor = self.presentationData.theme.chatList.itemHighlightedBackgroundColor + return cell } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { self.itemSelected?(self.searchResults[indexPath.row]) } + + func updatePresentationData(_ presentationData : PresentationData) { + let previousTheme = self.presentationData.theme + let previousStrings = self.presentationData.strings + + self.presentationData = presentationData + + if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { + self.updateThemeAndStrings() + } + } + + private func updateThemeAndStrings() { + self.view.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.tableView.backgroundColor = self.presentationData.theme.chatList.backgroundColor + self.tableView.separatorColor = self.presentationData.theme.chatList.itemSeparatorColor + + self.tableView.reloadData() + } } private final class InnerLanguageSelectionController: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchResultsUpdating, UISearchBarDelegate { @@ -168,6 +200,9 @@ private final class InnerLanguageSelectionController: UIViewController, UITableV let previousStrings = strongSelf.presentationData.strings strongSelf.presentationData = presentationData + if strongSelf.searchResultsController != nil { + strongSelf.searchResultsController.updatePresentationData(presentationData) + } if previousTheme !== presentationData.theme || previousStrings !== presentationData.strings { strongSelf.updateThemeAndStrings() @@ -211,7 +246,7 @@ private final class InnerLanguageSelectionController: UIViewController, UITableV self.view.backgroundColor = .white - self.searchResultsController = InnerCoutrySearchResultsController() + self.searchResultsController = InnerCoutrySearchResultsController(presentationData: self.presentationData) self.searchResultsController.itemSelected = { [weak self] language in if let strongSelf = self { strongSelf.searchController.searchBar.resignFirstResponder() @@ -223,6 +258,7 @@ private final class InnerLanguageSelectionController: UIViewController, UITableV self.searchController.searchResultsUpdater = self self.searchController.dimsBackgroundDuringPresentation = true self.searchController.searchBar.delegate = self + self.searchController.searchBar.searchTextPositionAdjustment = UIOffset(horizontal: 6.0, vertical: 0.0) self.tableView.autoresizingMask = [.flexibleWidth, .flexibleHeight] self.view.addSubview(self.tableView) diff --git a/TelegramUI/LegacyLocationController.swift b/TelegramUI/LegacyLocationController.swift index 2e7c49d7f0..695a6e43e8 100644 --- a/TelegramUI/LegacyLocationController.swift +++ b/TelegramUI/LegacyLocationController.swift @@ -104,7 +104,17 @@ private func legacyRemainingTime(message: TGMessage) -> SSignal { }) } -func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, account: Account, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, shareLocation: @escaping (TelegramMediaMap) -> Void) -> ViewController { +private func telegramMap(for location: TGLocationMediaAttachment) -> TelegramMediaMap { + let mapVenue: MapVenue? + if let venue = location.venue { + mapVenue = MapVenue(title: venue.title ?? "", address: venue.address ?? "", provider: venue.provider ?? "", id: venue.venueId ?? "", type: venue.type ?? "") + } else { + mapVenue = nil + } + return TelegramMediaMap(latitude: location.latitude, longitude: location.longitude, geoPlace: nil, venue: mapVenue, liveBroadcastingTimeout: nil) +} + +func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, account: Account, openPeer: @escaping (Peer) -> Void, sendLiveLocation: @escaping (CLLocationCoordinate2D, Int32) -> Void, stopLiveLocation: @escaping () -> Void, showActions: @escaping (TelegramMediaMap, Bool) -> Void) -> ViewController { let legacyAuthor: AnyObject? = message.author.flatMap(makeLegacyPeer) let legacyLocation = TGLocationMediaAttachment() @@ -215,37 +225,12 @@ func legacyLocationController(message: Message, mapMedia: TelegramMediaMap, acco controller.navigation_setDismiss({ [weak legacyController] in legacyController?.dismiss() }, rootController: nil) - controller.presentShareMenu = { menuController, coordinate in - menuController?.dismiss(animated: true) - if coordinate.latitude.isEqual(to: mapMedia.latitude) && coordinate.longitude.isEqual(to: mapMedia.longitude) { - shareLocation(mapMedia) - } else { - shareLocation(TelegramMediaMap(latitude: coordinate.latitude, longitude: coordinate.longitude, geoPlace: nil, venue: nil, liveBroadcastingTimeout: nil)) + controller.presentActionsMenu = { legacyLocation, directions in + if let location = legacyLocation { + let map = telegramMap(for: location) + showActions(map, directions) } - return true } - /*controller.shareAction = { [weak legacyController] in - if let legacyController = legacyController { - var shareAction: (([PeerId]) -> Void)? - let shareController = ShareController(account: account, shareAction: { peerIds in - shareAction?(peerIds) - }, defaultAction: nil) - legacyController.present(shareController, in: .window(.root)) - shareAction = { [weak shareController] peerIds in - shareController?.dismiss() - - for peerId in peerIds { - let _ = enqueueMessages(account: account, peerId: peerId, messages: , grouping: .auto)]).start() - } - } - } - }*/ - /*controller.calloutPressed = { [weak legacyController] in - legacyController?.dismiss() - - if let author = message.author { - openPeer(author) - } - }*/ + return legacyController } diff --git a/TelegramUI/ListMessageFileItemNode.swift b/TelegramUI/ListMessageFileItemNode.swift index c1cec79dbc..5f3ab77bb6 100644 --- a/TelegramUI/ListMessageFileItemNode.swift +++ b/TelegramUI/ListMessageFileItemNode.swift @@ -307,7 +307,7 @@ final class ListMessageFileItemNode: ListMessageNode { if case let .Audio(voice, _, title, performer, _) = attribute { isAudio = true - titleText = NSAttributedString(string: title ?? "Unknown Track", font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) + titleText = NSAttributedString(string: title ?? (file.fileName ?? "Unknown Track"), font: audioTitleFont, textColor: item.theme.list.itemPrimaryTextColor) let descriptionString: String if let performer = performer { diff --git a/TelegramUI/NavigateToChatController.swift b/TelegramUI/NavigateToChatController.swift index 16b13552e0..5ba4e726c6 100644 --- a/TelegramUI/NavigateToChatController.swift +++ b/TelegramUI/NavigateToChatController.swift @@ -9,12 +9,13 @@ public enum NavigateToChatKeepStack { case never } -public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, animated: Bool = true) { +public func navigateToChatController(navigationController: NavigationController, chatController: ChatController? = nil, account: Account, chatLocation: ChatLocation, messageId: MessageId? = nil, botStart: ChatControllerInitialBotStart? = nil, keepStack: NavigateToChatKeepStack = .default, purposefulAction: (()-> Void)? = nil, animated: Bool = true) { var found = false var isFirst = true for controller in navigationController.viewControllers.reversed() { if let controller = controller as? ChatController, controller.chatLocation == chatLocation { if let messageId = messageId { + controller.purposefulAction = purposefulAction controller.navigateToMessage(messageLocation: .id(messageId), animated: isFirst, completion: { [weak navigationController, weak controller] in if let navigationController = navigationController, let controller = controller { let _ = navigationController.popToViewController(controller, animated: animated) @@ -36,6 +37,7 @@ public func navigateToChatController(navigationController: NavigationController, } else { controller = ChatController(account: account, chatLocation: chatLocation, messageId: messageId, botStart: botStart) } + controller.purposefulAction = purposefulAction let resolvedKeepStack: Bool switch keepStack { case .default: diff --git a/TelegramUI/OpenChatMessage.swift b/TelegramUI/OpenChatMessage.swift index ef4bb15a80..5087132b8e 100644 --- a/TelegramUI/OpenChatMessage.swift +++ b/TelegramUI/OpenChatMessage.swift @@ -137,7 +137,7 @@ func chatMessagePreviewControllerData(account: Account, message: Message, standa return nil } -func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void) -> Bool { +func openChatMessage(account: Account, message: Message, standalone: Bool, reverseMessageGalleryOrder: Bool, navigationController: NavigationController?, dismissInput: @escaping () -> Void, present: @escaping (ViewController, Any?) -> Void, transitionNode: @escaping (MessageId, Media) -> (ASDisplayNode, () -> UIView?)?, addToTransitionSurface: @escaping (UIView) -> Void, openUrl: @escaping (String) -> Void, openPeer: @escaping (Peer, ChatControllerInteractionNavigateToPeer) -> Void, callPeer: @escaping (PeerId) -> Void, enqueueMessage: @escaping (EnqueueMessage) -> Void, sendSticker: ((FileMediaReference) -> Void)?, setupTemporaryHiddenMedia: @escaping (Signal, Int, Media) -> Void) -> Bool { if let mediaData = chatMessageGalleryControllerData(account: account, message: message, navigationController: navigationController, standalone: standalone, reverseMessageGalleryOrder: reverseMessageGalleryOrder, synchronousLoad: false) { switch mediaData { case let .url(url): @@ -188,8 +188,13 @@ func openChatMessage(account: Account, message: Message, standalone: Bool, rever enqueueMessage(outMessage) }, stopLiveLocation: { account.telegramApplicationContext.liveLocationManager?.cancelLiveLocation(peerId: message.id.peerId) - }, shareLocation: { media in - present(ShareController(account: account, subject: .mapMedia(media), externalShare: true), nil) + }, showActions: { media, directions in + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + let shareAction = OpenInControllerAction(title: presentationData.strings.Conversation_ContextMenuShare, action: { + present(ShareController(account: account, subject: .mapMedia(media), externalShare: true), nil) + }) + + present(OpenInActionSheetController(postbox: account.postbox, applicationContext: account.telegramApplicationContext, theme: presentationData.theme, strings: presentationData.strings, item: .location(media, withDirections: directions), additionalAction: shareAction, openUrl: openUrl), nil) }), nil) return true case let .stickerPack(reference): diff --git a/TelegramUI/OpenInActionSheetController.swift b/TelegramUI/OpenInActionSheetController.swift index 20f88ce5fe..48f4de6b50 100644 --- a/TelegramUI/OpenInActionSheetController.swift +++ b/TelegramUI/OpenInActionSheetController.swift @@ -4,36 +4,67 @@ import AsyncDisplayKit import SwiftSignalKit import Postbox import TelegramCore +import MapKit + +public struct OpenInControllerAction { + let title: String + let action: () -> Void +} final class OpenInActionSheetController: ActionSheetController { private let theme: PresentationTheme private let strings: PresentationStrings - private let openUrl: (String) -> Void private let _ready = Promise() override var ready: Promise { return self._ready } - init(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: PresentationTheme, strings: PresentationStrings, item: OpenInItem, openUrl: @escaping (String) -> Void) { + init(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: PresentationTheme, strings: PresentationStrings, item: OpenInItem, additionalAction: OpenInControllerAction? = nil, openUrl: @escaping (String) -> Void) { self.theme = theme self.strings = strings - self.openUrl = openUrl super.init(theme: ActionSheetControllerTheme(presentationTheme: theme)) self._ready.set(.single(true)) + let invokeActionImpl: (OpenInAction) -> Void = { action in + switch action { + case let .openUrl(url): + openUrl(url) + case let .openLocation(latitude, longitude, withDirections): + let placemark = MKPlacemark(coordinate: CLLocationCoordinate2DMake(latitude, longitude), addressDictionary: [:]) + let mapItem = MKMapItem(placemark: placemark) + + if withDirections { + let options = [ MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving ] + MKMapItem.openMaps(with: [MKMapItem.forCurrentLocation(), mapItem], launchOptions: options) + } else { + mapItem.openInMaps(launchOptions: nil) + } + default: + break + } + } + var items: [ActionSheetItem] = [] - items.append(OpenInActionSheetItem(postbox: postbox, applicationContext: applicationContext, strings: strings, options: availableOpenInOptions(applicationContext: applicationContext, item: item))) + items.append(OpenInActionSheetItem(postbox: postbox, applicationContext: applicationContext, strings: strings, options: availableOpenInOptions(applicationContext: applicationContext, item: item), invokeAction: invokeActionImpl)) + + if let action = additionalAction { + items.append(ActionSheetButtonItem(title: action.title, action: { [weak self] in + action.action() + self?.dismissAnimated() + })) + } + self.setItemGroups([ ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ ActionSheetButtonItem(title: strings.Common_Cancel, action: { [weak self] in self?.dismissAnimated() - }), - ]) + }) ]) + ]) } required init(coder aDecoder: NSCoder) { @@ -46,16 +77,18 @@ private final class OpenInActionSheetItem: ActionSheetItem { let applicationContext: TelegramApplicationContext let strings: PresentationStrings let options: [OpenInOption] + let invokeAction: (OpenInAction) -> Void - init(postbox: Postbox, applicationContext: TelegramApplicationContext, strings: PresentationStrings, options: [OpenInOption]) { + init(postbox: Postbox, applicationContext: TelegramApplicationContext, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) { self.postbox = postbox self.applicationContext = applicationContext self.strings = strings self.options = options + self.invokeAction = invokeAction } func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { - return OpenInActionSheetItemNode(postbox: self.postbox, applicationContext: self.applicationContext, theme: theme, strings: self.strings, options: self.options) + return OpenInActionSheetItemNode(postbox: self.postbox, applicationContext: self.applicationContext, theme: theme, strings: self.strings, options: self.options, invokeAction: self.invokeAction) } func updateNode(_ node: ActionSheetItemNode) { @@ -66,15 +99,15 @@ private let titleFont = Font.medium(20.0) private let textFont = Font.regular(11.0) private final class OpenInActionSheetItemNode: ActionSheetItemNode { - private let theme: ActionSheetControllerTheme - private let strings: PresentationStrings + let theme: ActionSheetControllerTheme + let strings: PresentationStrings - private let titleNode: ASTextNode - private let scrollNode: ASScrollNode + let titleNode: ASTextNode + let scrollNode: ASScrollNode - private let openInNodes: [OpenInAppNode] + let openInNodes: [OpenInAppNode] - init(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: ActionSheetControllerTheme, strings: PresentationStrings, options: [OpenInOption]) { + init(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: ActionSheetControllerTheme, strings: PresentationStrings, options: [OpenInOption], invokeAction: @escaping (OpenInAction) -> Void) { self.theme = theme self.strings = strings @@ -93,7 +126,7 @@ private final class OpenInActionSheetItemNode: ActionSheetItemNode { self.openInNodes = options.map { option in let node = OpenInAppNode() - node.setup(postbox: postbox, applicationContext: applicationContext, theme: theme, option: option) + node.setup(postbox: postbox, applicationContext: applicationContext, theme: theme, option: option, invokeAction: invokeAction) return node } @@ -154,7 +187,7 @@ private final class OpenInAppNode : ASDisplayNode { self.addSubnode(self.textNode) } - func setup(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: ActionSheetControllerTheme, option: OpenInOption) { + func setup(postbox: Postbox, applicationContext: TelegramApplicationContext, theme: ActionSheetControllerTheme, option: OpenInOption, invokeAction: @escaping (OpenInAction) -> Void) { self.textNode.attributedText = NSAttributedString(string: option.title, font: textFont, textColor: theme.primaryTextColor, paragraphAlignment: .center) let iconSize = CGSize(width: 60.0, height: 60.0) @@ -162,27 +195,22 @@ private final class OpenInAppNode : ASDisplayNode { let applyLayout = makeLayout(TransformImageArguments(corners: ImageCorners(radius: 16.0), imageSize: iconSize, boundingSize: iconSize, intrinsicInsets: UIEdgeInsets())) applyLayout() - //option.a + switch option.application { + case .safari: + if let image = UIImage(bundleImageName: "Open In/Safari") { + self.iconNode.setSignal(openInAppIcon(postbox: postbox, appIcon: .image(image))) + } + case .maps: + if let image = UIImage(bundleImageName: "Open In/Maps") { + self.iconNode.setSignal(openInAppIcon(postbox: postbox, appIcon: .image(image))) + } + case let .other(_, identifier, _): + self.iconNode.setSignal(openInAppIcon(postbox: postbox, appIcon: .resource(OpenInAppIconResource(appStoreId: identifier)))) + } -// switch option.action { -// case .o -//// case .safari: -//// self.iconNode.setSignal(openInAppIcon(postbox: postbox, appIcon: nil)) -//// self.action = { -//// applicationContext.applicationBindings.openUrl("https://telegram.org") -//// } -//// // //self.iconNode.setSignal( -//// // case .maps: -//// // nil -//// case let .external(identifier, _, _): -//// self.iconNode.setSignal(openInAppIcon(postbox: postbox, appIcon: OpenInAppIconResource(appStoreId: identifier))) -//// self.action = { -//// applicationContext.applicationBindings.openUrl("googlechromes://telegram.org") -//// } -//// default: -//// break -//// } -// } + self.action = { + invokeAction(option.action()) + } } override func didLoad() { diff --git a/TelegramUI/OverlayPlayerControllerNode.swift b/TelegramUI/OverlayPlayerControllerNode.swift index aeb326b818..7716494906 100644 --- a/TelegramUI/OverlayPlayerControllerNode.swift +++ b/TelegramUI/OverlayPlayerControllerNode.swift @@ -65,6 +65,7 @@ final class OverlayPlayerControllerNode: ViewControllerTracingNode, UIGestureRec }, setupReply: { _ in }, canSetupReply: { _ in return false + }, navigateToFirstDateMessage: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/PeerMediaCollectionController.swift b/TelegramUI/PeerMediaCollectionController.swift index 4783fc45d5..785a420fa5 100644 --- a/TelegramUI/PeerMediaCollectionController.swift +++ b/TelegramUI/PeerMediaCollectionController.swift @@ -215,6 +215,7 @@ public class PeerMediaCollectionController: TelegramController { }, setupReply: { _ in }, canSetupReply: { _ in return false + }, navigateToFirstDateMessage: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/PeerSelectionControllerNode.swift b/TelegramUI/PeerSelectionControllerNode.swift index cb2eb8d825..7aaffa00e8 100644 --- a/TelegramUI/PeerSelectionControllerNode.swift +++ b/TelegramUI/PeerSelectionControllerNode.swift @@ -191,7 +191,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { } if let placeholderNode = maybePlaceholderNode { - self.searchDisplayController = SearchDisplayController(theme: self.presentationData.theme, strings: self.presentationData.strings, contentNode: ChatListSearchContainerNode(account: self.account, filter: self.filter, groupId: nil, openPeer: { [weak self] peer in + self.searchDisplayController = SearchDisplayController(theme: self.presentationData.theme, strings: self.presentationData.strings, contentNode: ChatListSearchContainerNode(account: self.account, filter: self.filter, groupId: nil, openPeer: { [weak self] peer, _ in if let requestOpenPeerFromSearch = self?.requestOpenPeerFromSearch { requestOpenPeerFromSearch(peer) } diff --git a/TelegramUI/PhotoResources.swift b/TelegramUI/PhotoResources.swift index 2c4b11545f..7cda707f48 100644 --- a/TelegramUI/PhotoResources.swift +++ b/TelegramUI/PhotoResources.swift @@ -2490,50 +2490,52 @@ private func drawOpenInAppIconBorder(into c: CGContext, arguments: TransformImag c.strokePath() } -func openInAppIcon(postbox: Postbox, appIcon: TelegramMediaResource?) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - if let appIcon = appIcon { - return openInAppIconData(postbox: postbox, appIcon: appIcon) |> map { data in - return { arguments in +enum OpenInAppIcon { + case resource(_ resource: TelegramMediaResource) + case image(_ image: UIImage) +} + +func openInAppIcon(postbox: Postbox, appIcon: OpenInAppIcon) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + switch appIcon { + case let .resource(resource): + return openInAppIconData(postbox: postbox, appIcon: resource) |> map { data in + return { arguments in + let context = DrawingContext(size: arguments.drawingSize, clear: true) + + var sourceImage: UIImage? + if let data = data, let image = UIImage(data: data) { + sourceImage = image + } + + if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage { + let imageSize = sourceImage.size.aspectFilled(arguments.drawingRect.size) + context.withFlippedContext { c in + c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize)) + drawOpenInAppIconBorder(into: c, arguments: arguments) + } + } else { + context.withFlippedContext { c in + drawOpenInAppIconBorder(into: c, arguments: arguments) + } + } + + addCorners(context, arguments: arguments) + + return context + } + } + case let .image(image): + return .single({ arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) - var sourceImage: UIImage? - if let data = data, let image = UIImage(data: data) { - sourceImage = image - } - - if let sourceImage = sourceImage, let cgImage = sourceImage.cgImage { - let imageSize = sourceImage.size.aspectFilled(arguments.drawingRect.size) - context.withFlippedContext { c in - c.draw(cgImage, in: CGRect(origin: CGPoint(x: floor((arguments.drawingRect.size.width - imageSize.width) / 2.0), y: floor((arguments.drawingRect.size.height - imageSize.height) / 2.0)), size: imageSize)) - drawOpenInAppIconBorder(into: c, arguments: arguments) - } - } else { - context.withFlippedContext { c in - drawOpenInAppIconBorder(into: c, arguments: arguments) - } + context.withFlippedContext { c in + c.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: arguments.drawingSize)) + drawOpenInAppIconBorder(into: c, arguments: arguments) } addCorners(context, arguments: arguments) return context - } - } - } else { - return .single({ arguments in - let context = DrawingContext(size: arguments.drawingSize, clear: true) - - let img = UIImage(bundleImageName: "Open In/Safari") - - context.withFlippedContext { c in - if let image = img { - c.draw(image.cgImage!, in: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: arguments.drawingSize)) - } - drawOpenInAppIconBorder(into: c, arguments: arguments) - } - - addCorners(context, arguments: arguments) - - return context - }) + }) } } diff --git a/TelegramUI/PresentationStrings.swift b/TelegramUI/PresentationStrings.swift index 8fef993e4f..3969cbe54c 100644 --- a/TelegramUI/PresentationStrings.swift +++ b/TelegramUI/PresentationStrings.swift @@ -222,6 +222,11 @@ public final class PresentationStrings { public func PrivacyPolicy_AgeVerificationMessage(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_PrivacyPolicy_AgeVerificationMessage, self._PrivacyPolicy_AgeVerificationMessage_r, [_0]) } + private let _Login_TermsOfService_ProceedBot: String + private let _Login_TermsOfService_ProceedBot_r: [(Int, NSRange)] + public func Login_TermsOfService_ProceedBot(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_Login_TermsOfService_ProceedBot, self._Login_TermsOfService_ProceedBot_r, [_0]) + } public let NotificationsSound_None: String public let Channel_AdminLog_CanEditMessages: String private let _MESSAGE_CONTACT: String @@ -3273,6 +3278,11 @@ public final class PresentationStrings { public func ChannelInfo_ChannelForbidden(_ _0: String) -> (String, [(Int, NSRange)]) { return formatWithArgumentRanges(_ChannelInfo_ChannelForbidden, self._ChannelInfo_ChannelForbidden_r, [_0]) } + private let _ChannelInfo_AddParticipantConfirmation: String + private let _ChannelInfo_AddParticipantConfirmation_r: [(Int, NSRange)] + public func ChannelInfo_AddParticipantConfirmation(_ _0: String) -> (String, [(Int, NSRange)]) { + return formatWithArgumentRanges(_ChannelInfo_AddParticipantConfirmation, self._ChannelInfo_AddParticipantConfirmation_r, [_0]) + } public let Conversation_ShareMyContactInfo: String public let SocksProxySetup_UsernamePlaceholder: String private let _CHANNEL_MESSAGE_GEO: String @@ -5536,6 +5546,8 @@ public final class PresentationStrings { self.SocksProxySetup_Hostname = getValue(dict, "SocksProxySetup.Hostname") self._PrivacyPolicy_AgeVerificationMessage = getValue(dict, "PrivacyPolicy.AgeVerificationMessage") self._PrivacyPolicy_AgeVerificationMessage_r = extractArgumentRanges(self._PrivacyPolicy_AgeVerificationMessage) + self._Login_TermsOfService_ProceedBot = getValue(dict, "Login.TermsOfService.ProceedBot") + self._Login_TermsOfService_ProceedBot_r = extractArgumentRanges(self._Login_TermsOfService_ProceedBot) self.NotificationsSound_None = getValue(dict, "NotificationsSound.None") self.Channel_AdminLog_CanEditMessages = getValue(dict, "Channel.AdminLog.CanEditMessages") self._MESSAGE_CONTACT = getValue(dict, "MESSAGE_CONTACT") @@ -7732,6 +7744,8 @@ public final class PresentationStrings { self._PINNED_ROUND_r = extractArgumentRanges(self._PINNED_ROUND) self._ChannelInfo_ChannelForbidden = getValue(dict, "ChannelInfo.ChannelForbidden") self._ChannelInfo_ChannelForbidden_r = extractArgumentRanges(self._ChannelInfo_ChannelForbidden) + self._ChannelInfo_AddParticipantConfirmation = getValue(dict, "ChannelInfo.AddParticipantConfirmation") + self._ChannelInfo_AddParticipantConfirmation_r = extractArgumentRanges(self._ChannelInfo_AddParticipantConfirmation) self.Conversation_ShareMyContactInfo = getValue(dict, "Conversation.ShareMyContactInfo") self.SocksProxySetup_UsernamePlaceholder = getValue(dict, "SocksProxySetup.UsernamePlaceholder") self._CHANNEL_MESSAGE_GEO = getValue(dict, "CHANNEL_MESSAGE_GEO") diff --git a/TelegramUI/PresentationThemeEssentialGraphics.swift b/TelegramUI/PresentationThemeEssentialGraphics.swift index a3594e80ef..7eac1858eb 100644 --- a/TelegramUI/PresentationThemeEssentialGraphics.swift +++ b/TelegramUI/PresentationThemeEssentialGraphics.swift @@ -73,18 +73,24 @@ public final class PrincipalThemeEssentialGraphics { public let checkMediaFullImage: UIImage public let checkMediaPartialImage: UIImage + public let checkFreeFullImage: UIImage + public let checkFreePartialImage: UIImage + public let clockBubbleIncomingFrameImage: UIImage public let clockBubbleIncomingMinImage: UIImage public let clockBubbleOutgoingFrameImage: UIImage public let clockBubbleOutgoingMinImage: UIImage public let clockMediaFrameImage: UIImage public let clockMediaMinImage: UIImage + public let clockFreeFrameImage: UIImage + public let clockFreeMinImage: UIImage public let dateAndStatusMediaBackground: UIImage public let dateAndStatusFreeBackground: UIImage public let incomingDateAndStatusImpressionIcon: UIImage public let outgoingDateAndStatusImpressionIcon: UIImage public let mediaImpressionIcon: UIImage + public let freeImpressionIcon: UIImage public let dateStaticBackground: UIImage public let dateFloatingBackground: UIImage @@ -129,6 +135,9 @@ public final class PrincipalThemeEssentialGraphics { self.checkMediaFullImage = generateCheckImage(partial: false, color: .white)! self.checkMediaPartialImage = generateCheckImage(partial: true, color: .white)! + self.checkFreeFullImage = generateCheckImage(partial: false, color: theme.serviceMessage.serviceMessagePrimaryTextColor)! + self.checkFreePartialImage = generateCheckImage(partial: true, color: theme.serviceMessage.serviceMessagePrimaryTextColor)! + self.clockBubbleIncomingFrameImage = generateClockFrameImage(color: theme.bubble.incomingPendingActivityColor)! self.clockBubbleIncomingMinImage = generateClockMinImage(color: theme.bubble.incomingPendingActivityColor)! self.clockBubbleOutgoingFrameImage = generateClockFrameImage(color: theme.bubble.outgoingPendingActivityColor)! @@ -137,6 +146,9 @@ public final class PrincipalThemeEssentialGraphics { self.clockMediaFrameImage = generateClockFrameImage(color: .white)! self.clockMediaMinImage = generateClockMinImage(color: .white)! + self.clockFreeFrameImage = generateClockFrameImage(color: theme.serviceMessage.serviceMessagePrimaryTextColor)! + self.clockFreeMinImage = generateClockMinImage(color: theme.serviceMessage.serviceMessagePrimaryTextColor)! + self.dateAndStatusMediaBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.bubble.mediaDateAndStatusFillColor)! self.dateAndStatusFreeBackground = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.serviceMessage.serviceMessageFillColor)! @@ -144,6 +156,7 @@ public final class PrincipalThemeEssentialGraphics { self.incomingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.bubble.incomingSecondaryTextColor)! self.outgoingDateAndStatusImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.bubble.outgoingSecondaryTextColor)! self.mediaImpressionIcon = generateTintedImage(image: impressionCountImage, color: .white)! + self.freeImpressionIcon = generateTintedImage(image: impressionCountImage, color: theme.serviceMessage.serviceMessagePrimaryTextColor)! self.dateStaticBackground = generateImage(CGSize(width: 26.0, height: 26.0), contextGenerator: { size, context -> Void in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/TelegramUI/ProxySettingsServerItem.swift b/TelegramUI/ProxySettingsServerItem.swift index 5cf5deb005..50a4c56963 100644 --- a/TelegramUI/ProxySettingsServerItem.swift +++ b/TelegramUI/ProxySettingsServerItem.swift @@ -209,7 +209,7 @@ class ProxySettingsServerItemNode: ItemListRevealOptionsItemNode { let peerRevealOptions: [ItemListRevealOption] if item.editing.editable { - peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] + peerRevealOptions = [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)] } else { peerRevealOptions = [] } diff --git a/TelegramUI/SearchBarNode.swift b/TelegramUI/SearchBarNode.swift index 3fd515be13..98dad39350 100644 --- a/TelegramUI/SearchBarNode.swift +++ b/TelegramUI/SearchBarNode.swift @@ -13,7 +13,7 @@ private func generateClearIcon(color: UIColor) -> UIImage? { } private func generateBackground(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { - let diameter: CGFloat = 10.0 + let diameter: CGFloat = 14.0 return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.setFillColor(backgroundColor.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) diff --git a/TelegramUI/SecretMediaPreviewController.swift b/TelegramUI/SecretMediaPreviewController.swift index 8948aceb03..6aec559048 100644 --- a/TelegramUI/SecretMediaPreviewController.swift +++ b/TelegramUI/SecretMediaPreviewController.swift @@ -59,7 +59,7 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode { self.timeoutNode = timeoutNode var iconImage: UIImage? if let image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Message/SecretMediaIcon"), color: .white) { - let factor: CGFloat = 0.4 + let factor: CGFloat = 0.48 iconImage = generateImage(CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) @@ -106,8 +106,8 @@ private final class SecretMediaPreviewControllerNode: GalleryControllerNode { private func layoutTimeoutNode(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { if let timeoutNode = self.timeoutNode { - let diameter: CGFloat = 24.0 - transition.updateFrame(node: timeoutNode, frame: CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - diameter - 8.0, y: navigationBarHeight - 8.0 - diameter), size: CGSize(width: diameter, height: diameter))) + let diameter: CGFloat = 28.0 + transition.updateFrame(node: timeoutNode, frame: CGRect(origin: CGPoint(x: layout.size.width - layout.safeInsets.right - diameter - 9.0, y: navigationBarHeight - 9.0 - diameter), size: CGSize(width: diameter, height: diameter))) } } } diff --git a/TelegramUI/SettingsController.swift b/TelegramUI/SettingsController.swift index 382ee388b6..e201219378 100644 --- a/TelegramUI/SettingsController.swift +++ b/TelegramUI/SettingsController.swift @@ -65,7 +65,7 @@ private enum SettingsEntry: ItemListNodeEntry { case savedMessages(PresentationTheme, UIImage?, String) case recentCalls(PresentationTheme, UIImage?, String) - case stickers(PresentationTheme, UIImage?, String, String) + case stickers(PresentationTheme, UIImage?, String, String, [ArchivedStickerPackItem]?) case notificationsAndSounds(PresentationTheme, UIImage?, String) case privacyAndSecurity(PresentationTheme, UIImage?, String) @@ -193,8 +193,8 @@ private enum SettingsEntry: ItemListNodeEntry { } else { return false } - case let .stickers(lhsTheme, lhsImage, lhsText, lhsValue): - if case let .stickers(rhsTheme, rhsImage, rhsText, rhsValue) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue { + case let .stickers(lhsTheme, lhsImage, lhsText, lhsValue, _): + if case let .stickers(rhsTheme, rhsImage, rhsText, rhsValue, _) = rhs, lhsTheme === rhsTheme, lhsImage === rhsImage, lhsText == rhsText, lhsValue == rhsValue { return true } else { return false @@ -283,9 +283,9 @@ private enum SettingsEntry: ItemListNodeEntry { return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { arguments.openRecentCalls() }) - case let .stickers(theme, image, text, value): + case let .stickers(theme, image, text, value, archivedPacks): return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: value, labelStyle: .badge, sectionId: ItemListSectionId(self.section), style: .blocks, action: { - arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general)) + arguments.pushController(installedStickerPacksController(account: arguments.account, mode: .general, archivedPacks: archivedPacks)) }) case let .notificationsAndSounds(theme, image, text): return ItemListDisclosureItem(theme: theme, icon: image, title: text, label: "", sectionId: ItemListSectionId(self.section), style: .blocks, action: { @@ -342,7 +342,7 @@ private struct SettingsState: Equatable { } } -private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, unreadTrendingStickerPacks: Int) -> [SettingsEntry] { +private func settingsEntries(presentationData: PresentationData, state: SettingsState, view: PeerView, proxySettings: ProxySettings, unreadTrendingStickerPacks: Int, archivedPacks: [ArchivedStickerPackItem]?) -> [SettingsEntry] { var entries: [SettingsEntry] = [] if let peer = peerViewMainPeer(view) as? TelegramUser { @@ -372,7 +372,7 @@ private func settingsEntries(presentationData: PresentationData, state: Settings entries.append(.savedMessages(presentationData.theme, SettingsItemIcons.savedMessages, presentationData.strings.Settings_SavedMessages)) entries.append(.recentCalls(presentationData.theme, SettingsItemIcons.recentCalls, presentationData.strings.CallSettings_RecentCalls)) - entries.append(.stickers(presentationData.theme, SettingsItemIcons.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)")) + entries.append(.stickers(presentationData.theme, SettingsItemIcons.stickers, presentationData.strings.ChatSettings_Stickers, unreadTrendingStickerPacks == 0 ? "" : "\(unreadTrendingStickerPacks)", archivedPacks)) entries.append(.notificationsAndSounds(presentationData.theme, SettingsItemIcons.notifications, presentationData.strings.Settings_NotificationsAndSounds)) entries.append(.privacyAndSecurity(presentationData.theme, SettingsItemIcons.security, presentationData.strings.Settings_PrivacySettings)) @@ -601,8 +601,11 @@ public func settingsController(account: Account, accountManager: AccountManager) let peerView = account.viewTracker.peerView(account.peerId) - let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), account.viewTracker.featuredStickerPacks()) - |> map { presentationData, state, view, preferences, featuredStickerPacks -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in + let archivedPacks = Promise<[ArchivedStickerPackItem]?>() + archivedPacks.set(.single(nil) |> then(archivedStickerPacks(account: account) |> map(Optional.init))) + + let signal = combineLatest((account.applicationContext as! TelegramApplicationContext).presentationData, statePromise.get(), peerView, account.postbox.preferencesView(keys: [PreferencesKeys.proxySettings]), combineLatest(account.viewTracker.featuredStickerPacks(), archivedPacks.get())) + |> map { presentationData, state, view, preferences, featuredAndArchived -> (ItemListControllerState, (ItemListNodeState, SettingsEntry.ItemGenerationArguments)) in let proxySettings: ProxySettings if let value = preferences.values[PreferencesKeys.proxySettings] as? ProxySettings { proxySettings = value @@ -620,13 +623,13 @@ public func settingsController(account: Account, accountManager: AccountManager) let controllerState = ItemListControllerState(theme: presentationData.theme, title: .text(presentationData.strings.Settings_Title), leftNavigationButton: nil, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back)) var unreadTrendingStickerPacks = 0 - for item in featuredStickerPacks { + for item in featuredAndArchived.0 { if item.unread { unreadTrendingStickerPacks += 1 } } - let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks), style: .blocks) + let listState = ItemListNodeState(entries: settingsEntries(presentationData: presentationData, state: state, view: view, proxySettings: proxySettings, unreadTrendingStickerPacks: unreadTrendingStickerPacks, archivedPacks: featuredAndArchived.1), style: .blocks) return (controllerState, (listState, arguments)) } |> afterDisposed { diff --git a/TelegramUI/ShareController.swift b/TelegramUI/ShareController.swift index 5cdfc955a3..b4913a5761 100644 --- a/TelegramUI/ShareController.swift +++ b/TelegramUI/ShareController.swift @@ -164,7 +164,7 @@ public final class ShareController: ViewController { private let immediateExternalShare: Bool private let subject: ShareControllerSubject - private let peers = Promise<[Peer]>() + private let peers = Promise<([Peer], Peer)>() private let peersDisposable = MetaDisposable() private var defaultAction: ShareControllerAction? @@ -189,7 +189,13 @@ public final class ShareController: ViewController { }) case .text: break - case .mapMedia: + case let .mapMedia(media): + self.defaultAction = ShareControllerAction(title: self.presentationData.strings.ShareMenu_CopyShareLink, action: { [weak self] in + let coordinates = "\(media.latitude),\(media.longitude)" + let url = "https://maps.apple.com/maps?ll=\(coordinates)&q=\(coordinates)&t=m" + UIPasteboard.general.string = url + self?.controllerNode.cancel?() + }) break case .quote: break @@ -235,12 +241,11 @@ public final class ShareController: ViewController { break } - self.peers.set(combineLatest(account.postbox.loadedPeerWithId(account.peerId) |> take(1), account.viewTracker.tailChatListView(groupId: nil, count: 150) |> take(1)) |> map { accountPeer, view -> [Peer] in + self.peers.set(combineLatest(account.postbox.loadedPeerWithId(account.peerId) |> take(1), account.viewTracker.tailChatListView(groupId: nil, count: 150) |> take(1)) |> map { accountPeer, view -> ([Peer], Peer) in var peers: [Peer] = [] - peers.append(accountPeer) for entry in view.0.entries.reversed() { switch entry { - case let .MessageEntry(_, message, _, _, _, renderedPeer, _): + case let .MessageEntry(_, _, _, _, _, renderedPeer, _): if let peer = renderedPeer.chatMainPeer, peer.id != accountPeer.id { if canSendMessagesToPeer(peer) { peers.append(peer) @@ -250,7 +255,7 @@ public final class ShareController: ViewController { break } } - return peers + return (peers, accountPeer) }) } @@ -454,7 +459,7 @@ public final class ShareController: ViewController { self.displayNodeDidLoad() self.peersDisposable.set((self.peers.get() |> deliverOnMainQueue).start(next: { [weak self] next in if let strongSelf = self { - strongSelf.controllerNode.updatePeers(peers: next, defaultAction: strongSelf.defaultAction) + strongSelf.controllerNode.updatePeers(peers: next.0, accountPeer: next.1, defaultAction: strongSelf.defaultAction) } })) self.ready.set(self.controllerNode.ready.get()) diff --git a/TelegramUI/ShareControllerNode.swift b/TelegramUI/ShareControllerNode.swift index fda7f710a7..085a10e6f5 100644 --- a/TelegramUI/ShareControllerNode.swift +++ b/TelegramUI/ShareControllerNode.swift @@ -133,7 +133,7 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate super.init() - self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer in + self.controllerInteraction = ShareControllerInteraction(togglePeer: { [weak self] peer, search in if let strongSelf = self { var added = false if strongSelf.controllerInteraction!.selectedPeerIds.contains(peer.id) { @@ -147,6 +147,14 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate added = true } + if search && added { + strongSelf.controllerInteraction!.foundPeers.removeAll(where: { otherPeer in + return peer.id == otherPeer.id + }) + strongSelf.controllerInteraction!.foundPeers.append(peer) + strongSelf.peersContentNode?.updateFoundPeers() + } + let inputNodeAlpha: CGFloat = strongSelf.controllerInteraction!.selectedPeers.isEmpty ? 0.0 : 1.0 if !strongSelf.inputFieldNode.alpha.isEqual(to: inputNodeAlpha) { let previousAlpha = strongSelf.inputFieldNode.alpha @@ -494,8 +502,8 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate } } - func updatePeers(peers: [Peer], defaultAction: ShareControllerAction?) { - let peersContentNode = SharePeersContainerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peers: peers, controllerInteraction: self.controllerInteraction!, externalShare: self.externalShare) + func updatePeers(peers: [Peer], accountPeer: Peer, defaultAction: ShareControllerAction?) { + let peersContentNode = SharePeersContainerNode(account: self.account, theme: self.presentationData.theme, strings: self.presentationData.strings, peers: peers, accountPeer: accountPeer, controllerInteraction: self.controllerInteraction!, externalShare: self.externalShare) self.peersContentNode = peersContentNode peersContentNode.openSearch = { [weak self] in if let strongSelf = self { diff --git a/TelegramUI/ShareControllerPeerGridItem.swift b/TelegramUI/ShareControllerPeerGridItem.swift index 43dd4701c6..fdbe69db58 100644 --- a/TelegramUI/ShareControllerPeerGridItem.swift +++ b/TelegramUI/ShareControllerPeerGridItem.swift @@ -6,11 +6,12 @@ import AsyncDisplayKit import Postbox final class ShareControllerInteraction { + var foundPeers: [Peer] = [] var selectedPeerIds = Set() var selectedPeers: [Peer] = [] - let togglePeer: (Peer) -> Void + let togglePeer: (Peer, Bool) -> Void - init(togglePeer: @escaping (Peer) -> Void) { + init(togglePeer: @escaping (Peer, Bool) -> Void) { self.togglePeer = togglePeer } } @@ -85,16 +86,18 @@ final class ShareControllerPeerGridItem: GridItem { let peer: Peer let chatPeer: Peer? let controllerInteraction: ShareControllerInteraction + let search: Bool let section: GridSection? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peer: Peer, chatPeer: Peer?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peer: Peer, chatPeer: Peer?, controllerInteraction: ShareControllerInteraction, sectionTitle: String? = nil, search: Bool = false) { self.account = account self.theme = theme self.strings = strings self.peer = peer self.chatPeer = chatPeer self.controllerInteraction = controllerInteraction + self.search = search if let sectionTitle = sectionTitle { self.section = ShareControllerGridSection(title: sectionTitle, theme: self.theme) @@ -106,7 +109,7 @@ final class ShareControllerPeerGridItem: GridItem { func node(layout: GridNodeLayout) -> GridItemNode { let node = ShareControllerPeerGridItemNode() node.controllerInteraction = self.controllerInteraction - node.setup(account: self.account, theme: self.theme, strings: self.strings, peer: self.peer, chatPeer: self.chatPeer) + node.setup(account: self.account, theme: self.theme, strings: self.strings, peer: self.peer, chatPeer: self.chatPeer, search: self.search) return node } @@ -116,12 +119,12 @@ final class ShareControllerPeerGridItem: GridItem { return } node.controllerInteraction = self.controllerInteraction - node.setup(account: self.account, theme: self.theme, strings: self.strings, peer: self.peer, chatPeer: self.chatPeer) + node.setup(account: self.account, theme: self.theme, strings: self.strings, peer: self.peer, chatPeer: self.chatPeer, search: self.search) } } final class ShareControllerPeerGridItemNode: GridItemNode { - private var currentState: (Account, Peer, Peer?)? + private var currentState: (Account, Peer, Peer?, Bool)? private let peerNode: SelectablePeerNode var controllerInteraction: ShareControllerInteraction? @@ -133,21 +136,21 @@ final class ShareControllerPeerGridItemNode: GridItemNode { self.peerNode.toggleSelection = { [weak self] in if let strongSelf = self { - if let (_, peer, chatPeer) = strongSelf.currentState { + if let (_, peer, chatPeer, search) = strongSelf.currentState { let mainPeer = chatPeer ?? peer - strongSelf.controllerInteraction?.togglePeer(mainPeer) + strongSelf.controllerInteraction?.togglePeer(mainPeer, search) } } } self.addSubnode(self.peerNode) } - func setup(account: Account, theme: PresentationTheme, strings: PresentationStrings, peer: Peer, chatPeer: Peer?) { + func setup(account: Account, theme: PresentationTheme, strings: PresentationStrings, peer: Peer, chatPeer: Peer?, search: Bool) { if self.currentState == nil || self.currentState!.0 !== account || !arePeersEqual(self.currentState!.1, peer) { let itemTheme = SelectablePeerNodeTheme(textColor: theme.actionSheet.primaryTextColor, secretTextColor: theme.chatList.secretTitleColor, selectedTextColor: theme.actionSheet.controlAccentColor, checkBackgroundColor: theme.actionSheet.opaqueItemBackgroundColor, checkFillColor: theme.actionSheet.controlAccentColor, checkColor: theme.actionSheet.checkContentColor) self.peerNode.theme = itemTheme self.peerNode.setup(account: account, strings: strings, peer: peer, chatPeer: chatPeer) - self.currentState = (account, peer, chatPeer) + self.currentState = (account, peer, chatPeer, search) self.setNeedsLayout() } self.updateSelection(animated: false) @@ -155,7 +158,7 @@ final class ShareControllerPeerGridItemNode: GridItemNode { func updateSelection(animated: Bool) { var selected = false - if let controllerInteraction = self.controllerInteraction, let (_, peer, chatPeer) = self.currentState { + if let controllerInteraction = self.controllerInteraction, let (_, peer, chatPeer, _) = self.currentState { let mainPeer = chatPeer ?? peer selected = controllerInteraction.selectedPeerIds.contains(mainPeer.id) } @@ -169,8 +172,4 @@ final class ShareControllerPeerGridItemNode: GridItemNode { let bounds = self.bounds self.peerNode.frame = bounds } - - func animateIn() { - self.peerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 60.0), to: CGPoint(), duration: 0.42, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } } diff --git a/TelegramUI/ShareControllerRecentPeersGridItem.swift b/TelegramUI/ShareControllerRecentPeersGridItem.swift index f0374ba392..e772f5603f 100644 --- a/TelegramUI/ShareControllerRecentPeersGridItem.swift +++ b/TelegramUI/ShareControllerRecentPeersGridItem.swift @@ -57,7 +57,7 @@ final class ShareControllerRecentPeersGridItemNode: GridItemNode { peersNode.updateThemeAndStrings(theme: theme, strings: strings) } else { peersNode = ChatListSearchRecentPeersNode(account: account, theme: theme, mode: .actionSheet, strings: strings, peerSelected: { [weak self] peer in - self?.controllerInteraction?.togglePeer(peer) + self?.controllerInteraction?.togglePeer(peer, true) }, peerLongTapped: {_ in }, isPeerSelected: { [weak self] peerId in return self?.controllerInteraction?.selectedPeerIds.contains(peerId) ?? false }, share: true) diff --git a/TelegramUI/ShareInputFieldNode.swift b/TelegramUI/ShareInputFieldNode.swift index f92d3c62f4..ef2bef6e9f 100644 --- a/TelegramUI/ShareInputFieldNode.swift +++ b/TelegramUI/ShareInputFieldNode.swift @@ -30,7 +30,7 @@ final class ShareInputFieldNode: ASDisplayNode, ASEditableTextNodeDelegate { self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 6.0, color: theme.actionSheet.inputBackgroundColor) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputBackgroundColor) self.textInputNode = ASEditableTextNode() let textColor: UIColor = theme.actionSheet.inputTextColor diff --git a/TelegramUI/SharePeersContainerNode.swift b/TelegramUI/SharePeersContainerNode.swift index 1d7aaa0fcf..0a61e1bda5 100644 --- a/TelegramUI/SharePeersContainerNode.swift +++ b/TelegramUI/SharePeersContainerNode.swift @@ -7,12 +7,64 @@ import Display private let subtitleFont = Font.regular(12.0) +private struct SharePeerEntry: Comparable, Identifiable { + let index: Int32 + let peer: Peer + let theme: PresentationTheme + let strings: PresentationStrings + + var stableId: Int64 { + return self.peer.id.toInt64() + } + + static func ==(lhs: SharePeerEntry, rhs: SharePeerEntry) -> Bool { + if lhs.index != rhs.index { + return false + } + if !arePeersEqual(lhs.peer, rhs.peer) { + return false + } + return true + } + + static func <(lhs: SharePeerEntry, rhs: SharePeerEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(account: Account, interfaceInteraction: ShareControllerInteraction) -> GridItem { + return ShareControllerPeerGridItem(account: account, theme: self.theme, strings: self.strings, peer: self.peer, chatPeer: nil, controllerInteraction: interfaceInteraction, search: false) + } +} + +private struct ShareGridTransaction { + let deletions: [Int] + let insertions: [GridNodeInsertItem] + let updates: [GridNodeUpdateItem] + let animated: Bool +} + +private func preparedGridEntryTransition(account: Account, from fromEntries: [SharePeerEntry], to toEntries: [SharePeerEntry], interfaceInteraction: ShareControllerInteraction) -> ShareGridTransaction { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) + + let deletions = deleteIndices + let insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction), previousIndex: $0.2) } + let updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interfaceInteraction: interfaceInteraction)) } + + return ShareGridTransaction(deletions: deletions, insertions: insertions, updates: updates, animated: false) +} + final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { private let account: Account private let theme: PresentationTheme private let strings: PresentationStrings private let controllerInteraction: ShareControllerInteraction - var peers: [Peer]? + + private let accountPeer: Peer + private let foundPeers = Promise<[Peer]>([]) + + private let disposable = MetaDisposable() + private var entries: [SharePeerEntry] = [] + private var enqueuedTransitions: [(ShareGridTransaction, Bool)] = [] private let contentGridNode: GridNode private let contentTitleNode: ASTextNode @@ -30,12 +82,39 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { private var validLayout: (CGSize, CGFloat)? private var overrideGridOffsetTransition: ContainedViewLayoutTransition? - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peers: [Peer], controllerInteraction: ShareControllerInteraction, externalShare: Bool) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, peers: [Peer], accountPeer: Peer, controllerInteraction: ShareControllerInteraction, externalShare: Bool) { self.account = account self.theme = theme self.strings = strings self.controllerInteraction = controllerInteraction - self.peers = peers + self.accountPeer = accountPeer + + let items: Signal<[SharePeerEntry], NoError> = combineLatest(.single(peers), foundPeers.get()) + |> map { initialPeers, foundPeers -> [SharePeerEntry] in + var entries: [SharePeerEntry] = [] + var index: Int32 = 0 + + var existingPeerIds: Set = Set() + + entries.append(SharePeerEntry(index: index, peer: accountPeer, theme: theme, strings: strings)) + index += 1 + + for peer in foundPeers.reversed() { + entries.append(SharePeerEntry(index: index, peer: peer, theme: theme, strings: strings)) + existingPeerIds.insert(peer.id) + index += 1 + } + + for peer in initialPeers { + if !existingPeerIds.contains(peer.id) { + entries.append(SharePeerEntry(index: index, peer: peer, theme: theme, strings: strings)) + existingPeerIds.insert(peer.id) + index += 1 + } + } + return entries + } + self.contentGridNode = GridNode() @@ -71,13 +150,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.addSubnode(self.shareButtonNode) self.addSubnode(self.contentSeparatorNode) - var insertItems: [GridNodeInsertItem] = [] - for i in 0 ..< peers.count { - insertItems.append(GridNodeInsertItem(index: i, item: ShareControllerPeerGridItem(account: self.account, theme: self.theme, strings: self.strings, peer: peers[i], chatPeer: nil, controllerInteraction: self.controllerInteraction), previousIndex: nil)) - } - - self.contentGridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: insertItems, updateItems: [], scrollToItem: nil, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) - + let previousItems = Atomic<[SharePeerEntry]?>(value: []) + self.disposable.set((items + |> deliverOnMainQueue).start(next: { [weak self] entries in + if let strongSelf = self { + let previousEntries = previousItems.swap(entries) + strongSelf.entries = entries + + let firstTime = previousEntries == nil + let transition = preparedGridEntryTransition(account: account, from: previousEntries ?? [], to: entries, interfaceInteraction: controllerInteraction) + strongSelf.enqueueTransition(transition, firstTime: firstTime) + } + })) + self.contentGridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition) } @@ -86,6 +171,28 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.shareButtonNode.addTarget(self, action: #selector(self.sharePressed), forControlEvents: .touchUpInside) } + private func enqueueTransition(_ transition: ShareGridTransaction, firstTime: Bool) { + self.enqueuedTransitions.append((transition, firstTime)) + + if self.validLayout != nil { + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } + } + } + + private func dequeueTransition() { + if let (transition, _) = self.enqueuedTransitions.first { + self.enqueuedTransitions.remove(at: 0) + + var itemTransition: ContainedViewLayoutTransition = .immediate + if transition.animated { + itemTransition = .animated(duration: 0.3, curve: .spring) + } + self.contentGridNode.transaction(GridNodeTransaction(deleteItems: transition.deletions, insertItems: transition.insertions, updateItems: transition.updates, scrollToItem: nil, updateLayout: nil, itemTransition: itemTransition, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + } + } + func setEnsurePeerVisibleOnLayout(_ peerId: PeerId?) { self.ensurePeerVisibleOnLayout = peerId } @@ -95,7 +202,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } private func calculateMetrics(size: CGSize) -> (topInset: CGFloat, itemWidth: CGFloat) { - let itemCount = self.peers?.count ?? 1 + let itemCount = self.entries.count let itemInsets = UIEdgeInsets(top: 0.0, left: 12.0, bottom: 0.0, right: 12.0) let minimalItemWidth: CGFloat = size.width > 301.0 ? 70.0 : 60.0 @@ -138,7 +245,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { var scrollToItem: GridNodeScrollToItem? if let ensurePeerVisibleOnLayout = self.ensurePeerVisibleOnLayout { self.ensurePeerVisibleOnLayout = nil - if let index = self.peers?.index(where: { $0.id == ensurePeerVisibleOnLayout }) { + if let index = self.entries.index(where: { $0.peer.id == ensurePeerVisibleOnLayout }) { scrollToItem = GridNodeScrollToItem(index: index, position: .visible, transition: transition, directionHint: .up, adjustForSection: false) } } @@ -149,7 +256,9 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { gridLayoutTransition.updateFrame(node: self.contentGridNode, frame: CGRect(origin: CGPoint(x: floor((size.width - gridSize.width) / 2.0), y: 0.0), size: gridSize)) if firstLayout { - self.animateIn() + while !self.enqueuedTransitions.isEmpty { + self.dequeueTransition() + } } } @@ -204,32 +313,25 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } } - func animateIn() { - var durationOffset = 0.0 - self.contentGridNode.forEachRow { itemNodes in - for itemNode in itemNodes { - itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 4.0), to: CGPoint(), duration: 0.4 + durationOffset, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let itemNode = itemNode as? StickerPackPreviewGridItemNode { - itemNode.animateIn() - } - } - durationOffset += 0.04 - } - - if let (size, _) = self.validLayout { - let (topInset, _) = self.calculateMetrics(size: size) - self.contentGridNode.layer.animateBoundsOriginYAdditive(from: -(topInset - 64.0), to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) - } + func updateFoundPeers() { + self.foundPeers.set(.single(self.controllerInteraction.foundPeers)) } func updateSelectedPeers() { var subtitleText = self.strings.ShareMenu_SelectChats if !self.controllerInteraction.selectedPeers.isEmpty { subtitleText = self.controllerInteraction.selectedPeers.reduce("", { string, peer in - if !string.isEmpty { - return string + ", " + peer.displayTitle + let text: String + if peer.id == self.accountPeer.id { + text = self.strings.DialogList_SavedMessages } else { - return string + peer.displayTitle + text = peer.displayTitle + } + + if !string.isEmpty { + return string + ", " + text + } else { + return string + text } }) } diff --git a/TelegramUI/ShareSearchBarNode.swift b/TelegramUI/ShareSearchBarNode.swift index fa232fd06b..e2d7d32978 100644 --- a/TelegramUI/ShareSearchBarNode.swift +++ b/TelegramUI/ShareSearchBarNode.swift @@ -21,7 +21,7 @@ final class ShareSearchBarNode: ASDisplayNode, UITextFieldDelegate { self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 6.0, color: theme.actionSheet.inputBackgroundColor) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 16.0, color: theme.actionSheet.inputBackgroundColor) self.searchIconNode = ASImageNode() self.searchIconNode.isLayerBacked = true diff --git a/TelegramUI/ShareSearchContainerNode.swift b/TelegramUI/ShareSearchContainerNode.swift index 42d5fba821..27e011f513 100644 --- a/TelegramUI/ShareSearchContainerNode.swift +++ b/TelegramUI/ShareSearchContainerNode.swift @@ -103,7 +103,7 @@ private enum ShareSearchRecentEntry: Comparable, Identifiable { primaryPeer = peer chatPeer = associatedPeer } - return ShareControllerPeerGridItem(account: account, theme: theme, strings: strings, peer: primaryPeer, chatPeer: chatPeer, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent) + return ShareControllerPeerGridItem(account: account, theme: theme, strings: strings, peer: primaryPeer, chatPeer: chatPeer, controllerInteraction: interfaceInteraction, sectionTitle: strings.DialogList_SearchSectionRecent, search: true) } } } @@ -559,7 +559,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } private func dequeueTransition() { - if let (transition, firstTime) = self.enqueuedTransitions.first { + if let (transition, _) = self.enqueuedTransitions.first { self.enqueuedTransitions.remove(at: 0) var itemTransition: ContainedViewLayoutTransition = .immediate @@ -581,7 +581,7 @@ final class ShareSearchContainerNode: ASDisplayNode, ShareContentContainerNode { } private func dequeueRecentTransition() { - if let (transition, firstTime) = self.enqueuedRecentTransitions.first { + if let (transition, _) = self.enqueuedRecentTransitions.first { self.enqueuedRecentTransitions.remove(at: 0) var itemTransition: ContainedViewLayoutTransition = .immediate diff --git a/TelegramUI/StickerPackPreviewControllerNode.swift b/TelegramUI/StickerPackPreviewControllerNode.swift index 62b29035db..5cb2877343 100644 --- a/TelegramUI/StickerPackPreviewControllerNode.swift +++ b/TelegramUI/StickerPackPreviewControllerNode.swift @@ -336,22 +336,9 @@ final class StickerPackPreviewControllerNode: ViewControllerTracingNode, UIScrol self.contentShareButtonNode.isHidden = false self.contentShareButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - var durationOffset = 0.0 - self.contentGridNode.forEachRow { itemNodes in - for itemNode in itemNodes { - itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 4.0), to: CGPoint(), duration: 0.4 + durationOffset, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - if let itemNode = itemNode as? StickerPackPreviewGridItemNode { - itemNode.animateIn() - } - } - durationOffset += 0.04 - } - self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.installActionButtonNode.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.installActionSeparatorNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - - self.contentGridNode.layer.animateBoundsOriginYAdditive(from: -(topInset - buttonHeight), to: 0.0, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) } if let _ = self.stickerPack, self.stickerPackUpdated { diff --git a/TelegramUI/StickerPackPreviewGridItem.swift b/TelegramUI/StickerPackPreviewGridItem.swift index 8bf1efddc6..d691cf1887 100644 --- a/TelegramUI/StickerPackPreviewGridItem.swift +++ b/TelegramUI/StickerPackPreviewGridItem.swift @@ -139,10 +139,6 @@ final class StickerPackPreviewGridItemNode: GridItemNode { } } - func animateIn() { - self.textNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 60.0), to: CGPoint(), duration: 0.42, timingFunction: kCAMediaTimingFunctionSpring, additive: true) - } - func updatePreviewing(animated: Bool) { var isPreviewing = false if let (_, item, _) = self.currentState, let interaction = self.interaction { diff --git a/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift b/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift index 6dbccbf7e7..1e87ce7f5b 100644 --- a/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift +++ b/TelegramUI/StickerPanePeerSpecificSetupGridItem.swift @@ -25,7 +25,7 @@ final class StickerPanePeerSpecificSetupGridItem: GridItem { let leftInset: CGFloat = 12.0 let rightInset: CGFloat = 16.0 let (descriptionLayout, _) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: strings.Stickers_GroupStickersHelp, font: statusFont, textColor: .black), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - return 67.0 + descriptionLayout.size.height + return 71.0 + descriptionLayout.size.height } } @@ -45,7 +45,7 @@ final class StickerPanePeerSpecificSetupGridItem: GridItem { } private let titleFont = Font.medium(12.0) -private let statusFont = Font.regular(15.0) +private let statusFont = Font.regular(14.0) private let buttonFont = Font.medium(13.0) class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { @@ -142,8 +142,8 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { let leftInset: CGFloat = 12.0 let rightInset: CGFloat = 16.0 let topOffset: CGFloat = 9.0 - let textSpacing: CGFloat = 2.0 - let buttonSpacing: CGFloat = 3.0 + let textSpacing: CGFloat = 3.0 + let buttonSpacing: CGFloat = 6.0 let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_GroupChooseStickerPack, font: buttonFont, textColor: item.theme.list.itemAccentColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) @@ -162,7 +162,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { } let installWidth: CGFloat = installLayout.size.width + 20.0 - let buttonFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing + descriptionLayout.size.height + buttonSpacing), size: CGSize(width: installWidth, height: 26.0)) + let buttonFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing + descriptionLayout.size.height + buttonSpacing), size: CGSize(width: installWidth, height: 32.0)) self.installBackgroundNode.frame = buttonFrame self.installTextNode.frame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - installLayout.size.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - installLayout.size.height) / 2.0)), size: installLayout.size) self.installButtonNode.frame = buttonFrame @@ -172,7 +172,7 @@ class StickerPanePeerSpecificSetupGridItemNode: GridItemNode { self.dismissButtonNode.frame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - dismissButtonSize.width, y: topOffset - 1.0), size: dismissButtonSize) self.dismissButtonNode.isHidden = item.dismiss == nil self.titleNode.frame = titleFrame - self.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + 1.0), size: descriptionLayout.size) + self.descriptionNode.frame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: topOffset + titleLayout.size.height + textSpacing), size: descriptionLayout.size) } @objc private func installPressed() { diff --git a/TelegramUI/TermsOfServiceController.swift b/TelegramUI/TermsOfServiceController.swift index 9e6ba80589..a5fdc0016d 100644 --- a/TelegramUI/TermsOfServiceController.swift +++ b/TelegramUI/TermsOfServiceController.swift @@ -48,9 +48,10 @@ public class TermsOfServiceController: ViewController { private let entities: [MessageTextEntity] private let ageConfirmation: Int32? private let signingUp: Bool - private let accept: () -> Void + private let accept: (String?) -> Void private let decline: () -> Void private let openUrl: (String) -> Void + private var proccessBotNameAfterAccept: String? = nil private var didPlayPresentationAnimation = false @@ -66,7 +67,7 @@ public class TermsOfServiceController: ViewController { } } - public init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping () -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { + public init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, signingUp: Bool, accept: @escaping (String?) -> Void, decline: @escaping () -> Void, openUrl: @escaping (String) -> Void) { self.theme = theme self.strings = strings self.text = text @@ -135,18 +136,21 @@ public class TermsOfServiceController: ViewController { theme = defaultDarkPresentationTheme } strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: theme), title: strongSelf.strings.PrivacyPolicy_AgeVerificationTitle, text: strongSelf.strings.PrivacyPolicy_AgeVerificationMessage("\(ageConfirmation)").0, actions: [TextAlertAction(type: .genericAction, title: strongSelf.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: strongSelf.strings.PrivacyPolicy_AgeVerificationAgree, action: { - self?.accept() + self?.accept(self?.proccessBotNameAfterAccept) })]), in: .window(.root)) } else { - strongSelf.accept() + strongSelf.accept(self?.proccessBotNameAfterAccept) } }, openUrl: self.openUrl, present: { [weak self] c, a in self?.present(c, in: .window(.root), with: a) + }, setToProcceedBot: { [weak self] botName in + self?.proccessBotNameAfterAccept = botName }) self.displayNodeDidLoad() } + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) diff --git a/TelegramUI/TermsOfServiceControllerNode.swift b/TelegramUI/TermsOfServiceControllerNode.swift index 53c164d712..425c3cdfbb 100644 --- a/TelegramUI/TermsOfServiceControllerNode.swift +++ b/TelegramUI/TermsOfServiceControllerNode.swift @@ -39,7 +39,7 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } } - init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, leftAction: @escaping () -> Void, rightAction: @escaping () -> Void, openUrl: @escaping (String) -> Void, present: @escaping (ViewController, Any?) -> Void) { + init(theme: TermsOfServiceControllerTheme, strings: PresentationStrings, text: String, entities: [MessageTextEntity], ageConfirmation: Int32?, leftAction: @escaping () -> Void, rightAction: @escaping () -> Void, openUrl: @escaping (String) -> Void, present: @escaping (ViewController, Any?) -> Void, setToProcceedBot:@escaping(String)->Void) { self.theme = theme self.strings = strings self.text = text @@ -142,14 +142,10 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: mention.mention), - ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = mention.mention - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + ActionSheetTextItem(title: strongSelf.strings.Login_TermsOfService_ProceedBot(mention.mention).0), + ActionSheetButtonItem(title: strongSelf.strings.Common_OK, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() + setToProcceedBot(mention.mention) }) ])]) strongSelf.present(actionSheet, nil) @@ -162,14 +158,10 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: mention), - ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = mention - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + ActionSheetTextItem(title: strongSelf.strings.Login_TermsOfService_ProceedBot(mention).0), + ActionSheetButtonItem(title: strongSelf.strings.Common_OK, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() + setToProcceedBot(mention) }) ])]) strongSelf.present(actionSheet, nil) @@ -212,14 +204,10 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: mention.mention), - ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = mention.mention - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + ActionSheetTextItem(title: strongSelf.strings.Login_TermsOfService_ProceedBot(mention.mention).0), + ActionSheetButtonItem(title: strongSelf.strings.Common_OK, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() + setToProcceedBot(mention.mention) }) ])]) strongSelf.present(actionSheet, nil) @@ -232,14 +220,10 @@ final class TermsOfServiceControllerNode: ViewControllerTracingNode { } let actionSheet = ActionSheetController(presentationTheme: theme) actionSheet.setItemGroups([ActionSheetItemGroup(items: [ - ActionSheetTextItem(title: mention), - ActionSheetButtonItem(title: strongSelf.strings.Conversation_LinkDialogCopy, color: .accent, action: { [weak actionSheet] in - actionSheet?.dismissAnimated() - UIPasteboard.general.string = mention - }) - ]), ActionSheetItemGroup(items: [ - ActionSheetButtonItem(title: strongSelf.strings.Common_Cancel, color: .accent, action: { [weak actionSheet] in + ActionSheetTextItem(title: strongSelf.strings.Login_TermsOfService_ProceedBot(mention).0), + ActionSheetButtonItem(title: strongSelf.strings.Common_OK, color: .accent, action: { [weak actionSheet] in actionSheet?.dismissAnimated() + setToProcceedBot(mention) }) ])]) strongSelf.present(actionSheet, nil) diff --git a/TelegramUI/ThemeSettingsChatPreviewItem.swift b/TelegramUI/ThemeSettingsChatPreviewItem.swift index 8373d032eb..6e4832b7fb 100644 --- a/TelegramUI/ThemeSettingsChatPreviewItem.swift +++ b/TelegramUI/ThemeSettingsChatPreviewItem.swift @@ -95,6 +95,7 @@ class ThemeSettingsChatPreviewItemNode: ListViewItemNode { }, presentGlobalOverlayController: { _, _ in }, callPeer: { _ in }, longTap: { _ in }, openCheckoutOrReceipt: { _ in }, openSearch: { }, setupReply: { _ in }, canSetupReply: { _ in return false + }, navigateToFirstDateMessage: { _ in }, requestMessageUpdate: { _ in }, cancelInteractiveKeyboardGestures: { }, automaticMediaDownloadSettings: AutomaticMediaDownloadSettings.defaultSettings) diff --git a/TelegramUI/UserInfoController.swift b/TelegramUI/UserInfoController.swift index 493be9fa36..40a9dee7d7 100644 --- a/TelegramUI/UserInfoController.swift +++ b/TelegramUI/UserInfoController.swift @@ -498,7 +498,12 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat } } - entries.append(UserInfoEntry.info(presentationData.theme, presentationData.strings, peer: user, presence: view.peerPresences[user.id], cachedData: view.cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), displayCall: user.botInfo == nil)) + var callsAvailable = true + if let cachedUserData = view.cachedData as? CachedUserData { + callsAvailable = cachedUserData.callsAvailable + } + + entries.append(UserInfoEntry.info(presentationData.theme, presentationData.strings, peer: user, presence: view.peerPresences[user.id], cachedData: view.cachedData, state: ItemListAvatarAndNameInfoItemState(editingName: editingName, updatingName: nil), displayCall: user.botInfo == nil && callsAvailable)) if let phoneNumber = user.phone, !phoneNumber.isEmpty { let formattedNumber = formatPhoneNumber(phoneNumber) @@ -537,10 +542,8 @@ private func userInfoEntries(account: Account, presentationData: PresentationDat } } - if let cachedUserData = view.cachedData as? CachedUserData { - if let about = cachedUserData.about, !about.isEmpty { - entries.append(UserInfoEntry.about(presentationData.theme, presentationData.strings.Profile_About, about)) - } + if let cachedUserData = view.cachedData as? CachedUserData, let about = cachedUserData.about, !about.isEmpty { + entries.append(UserInfoEntry.about(presentationData.theme, presentationData.strings.Profile_About, about)) } if !isEditing { @@ -675,13 +678,21 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll let cachedAvatarEntries = Atomic?>(value: nil) + let peerView = Promise() + peerView.set(account.viewTracker.peerView(peerId)) + let requestCallImpl: () -> Void = { - let _ = (getUserPeer(postbox: account.postbox, peerId: peerId) - |> deliverOnMainQueue).start(next: { peer in - guard let peer = peer else { + let _ = (peerView.get() |> deliverOnMainQueue).start(next: { view in + guard let peer = peerViewMainPeer(view) else { return } - + + if let cachedUserData = view.cachedData as? CachedUserData, cachedUserData.callsPrivate { + let presentationData = account.telegramApplicationContext.currentPresentationData.with { $0 } + presentControllerImpl?(standardTextAlertController(theme: AlertControllerTheme(presentationTheme: presentationData.theme), title: presentationData.strings.Call_ConnectionErrorTitle, text: presentationData.strings.Call_PrivacyErrorMessage(peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + return + } + let callResult = account.telegramApplicationContext.callManager?.requestCall(peerId: peer.id, endCurrentIfAny: false) if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { if currentPeerId == peer.id { @@ -929,9 +940,6 @@ public func userInfoController(account: Account, peerId: PeerId) -> ViewControll }), nil) }) - let peerView = Promise() - peerView.set(account.viewTracker.peerView(peerId)) - let deviceContacts: Signal<[DeviceContact], NoError> = peerView.get() |> map { peerView -> String in if let peer = peerView.peers[peerId] as? TelegramUser { diff --git a/TelegramUI/UserInfoEditingPhoneItem.swift b/TelegramUI/UserInfoEditingPhoneItem.swift index ae0fb1e415..72e847780d 100644 --- a/TelegramUI/UserInfoEditingPhoneItem.swift +++ b/TelegramUI/UserInfoEditingPhoneItem.swift @@ -243,7 +243,7 @@ class UserInfoEditingPhoneItemNode: ItemListRevealOptionsItemNode, ItemListItemN strongSelf.updateLayout(size: layout.contentSize, leftInset: params.leftInset, rightInset: params.rightInset) - strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: nil, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) + strongSelf.setRevealOptions((left: [], right: [ItemListRevealOption(key: 0, title: item.strings.Common_Delete, icon: .none, color: item.theme.list.itemDisclosureActions.destructive.fillColor, textColor: item.theme.list.itemDisclosureActions.destructive.foregroundColor)])) } }) } diff --git a/TelegramUI/UsernameSetupController.swift b/TelegramUI/UsernameSetupController.swift index a6b41e78d8..4d987d0ffa 100644 --- a/TelegramUI/UsernameSetupController.swift +++ b/TelegramUI/UsernameSetupController.swift @@ -72,7 +72,7 @@ private enum UsernameSetupEntry: ItemListNodeEntry { func item(_ arguments: UsernameSetupControllerArguments) -> ListViewItem { switch self { case let .editablePublicLink(theme, currentText, text): - return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", sectionId: self.section, textUpdated: { updatedText in + return ItemListSingleLineInputItem(theme: theme, title: NSAttributedString(string: "t.me/", textColor: theme.list.itemPrimaryTextColor), text: text, placeholder: "", type: .username, sectionId: self.section, textUpdated: { updatedText in arguments.updatePublicLinkText(currentText, updatedText) }, action: {