From 4d51f5fcb3008c5f019161f030ba9c87e2149a36 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 13 Apr 2023 20:40:29 +0400 Subject: [PATCH 1/4] Various fixes --- .../Telegram-iOS/en.lproj/Localizable.strings | 8 ++- .../AnimationUI/Sources/AnimationNode.swift | 6 ++ .../Display/Source/CAAnimationUtils.swift | 10 +++ .../ContainedViewLayoutTransition.swift | 23 +++++++ .../Sources/UsernameSetupController.swift | 30 ++++++-- .../TelegramUI/Sources/ChatController.swift | 13 ++-- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 69 +++++++++++++++---- .../Sources/PeerInfo/PeerInfoScreen.swift | 2 +- 8 files changed, 135 insertions(+), 26 deletions(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 4a7d0d41da..1ecd06f426 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9100,7 +9100,7 @@ Sorry for the inconvenience."; "TextFormat.EditLinkTitle" = "Edit Link"; -"PeerInfo.Username" = "Username"; +"PeerInfo.BotLinks" = "Public Links"; "Username.BotLinksOrderInfo" = "Drag and drop links to change the order in which they will be displayed on the bot info page."; "Wallpaper.ApplyForAll" = "Apply For All Chats"; @@ -9140,6 +9140,10 @@ Sorry for the inconvenience."; "Username.BotLinkHint" = "This username cannot be edited."; "Username.BotLinkHintExtended" = "This username cannot be edited. You can acquire additional usernames on [Fragment]()."; +"Username.BotActivateAlertText" = "Do you want to show this link on the bot's info page?"; +"Username.BotDeactivateAlertText" = "Do you want to hide this link from the bot's info page?"; +"Username.BotActiveLimitReachedError" = "Sorry, you have too many active public links already. Please hide one of the bot's active public links first."; + "PeerInfo.Bot.EditIntro" = "Edit Intro"; "PeerInfo.Bot.EditCommands" = "Edit Commands"; "PeerInfo.Bot.ChangeSettings" = "Change Bot Settings"; @@ -9327,3 +9331,5 @@ Sorry for the inconvenience."; "ChatList.EmptyListContactsHeader" = "YOUR CONTACTS ON TELEGRAM"; "ChatList.EmptyListContactsHeaderHide" = "hide"; "ChatList.EmptyListTooltip" = "Send a message or\nstart a group here."; + +"Username.BotTitle" = "Public Links"; diff --git a/submodules/AnimationUI/Sources/AnimationNode.swift b/submodules/AnimationUI/Sources/AnimationNode.swift index c7dfb88f43..4c42e29c82 100644 --- a/submodules/AnimationUI/Sources/AnimationNode.swift +++ b/submodules/AnimationUI/Sources/AnimationNode.swift @@ -117,6 +117,12 @@ public final class AnimationNode: ASDisplayNode { self.animationView()?.currentProgress = progress } + public func animate(from: CGFloat, to: CGFloat, completion: @escaping () -> Void) { + self.animationView()?.play(fromProgress: from, toProgress: to, completion: { _ in + completion() + }) + } + public func setAnimation(name: String, colors: [String: UIColor]? = nil) { self.currentParams = (name, colors) if let url = getAppBundle().url(forResource: name, withExtension: "json"), let animation = Animation.filepath(url.path) { diff --git a/submodules/Display/Source/CAAnimationUtils.swift b/submodules/Display/Source/CAAnimationUtils.swift index ac53fb66ca..585317db34 100644 --- a/submodules/Display/Source/CAAnimationUtils.swift +++ b/submodules/Display/Source/CAAnimationUtils.swift @@ -392,6 +392,16 @@ public extension CALayer { self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } + func animateAnchorPoint(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = CAMediaTimingFunctionName.easeInEaseOut.rawValue, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { + if let completion = completion { + completion(true) + } + return + } + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "anchorPoint", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + } + func animateBounds(from: CGRect, to: CGRect, duration: Double, delay: Double = 0.0, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { diff --git a/submodules/Display/Source/ContainedViewLayoutTransition.swift b/submodules/Display/Source/ContainedViewLayoutTransition.swift index 5686e645fb..6aaea8af80 100644 --- a/submodules/Display/Source/ContainedViewLayoutTransition.swift +++ b/submodules/Display/Source/ContainedViewLayoutTransition.swift @@ -423,6 +423,29 @@ public extension ContainedViewLayoutTransition { } } + func updateAnchorPoint(layer: CALayer, anchorPoint: CGPoint, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if layer.anchorPoint.equalTo(anchorPoint) && !force { + completion?(true) + } else { + switch self { + case .immediate: + layer.removeAnimation(forKey: "anchorPoint") + layer.anchorPoint = anchorPoint + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAnchorPoint = layer.anchorPoint + layer.anchorPoint = anchorPoint + layer.animateAnchorPoint(from: previousAnchorPoint, to: anchorPoint, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + func animatePosition(layer: CALayer, from fromValue: CGPoint, to toValue: CGPoint, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { switch self { case .immediate: diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index 4959dbfba0..c3eff1a4ba 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -488,13 +488,23 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup }, activateLink: { name in dismissInputImpl?() let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_ActivateAlertTitle, text: presentationData.strings.Username_ActivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_ActivateAlertShow, action: { + let alertText: String + if case .bot = mode { + alertText = presentationData.strings.Username_BotActivateAlertText + } else { + alertText = presentationData.strings.Username_ActivateAlertText + } + presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_ActivateAlertTitle, text: alertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_ActivateAlertShow, action: { let _ = (context.engine.peers.toggleAddressNameActive(domain: domain, name: name, active: true) |> deliverOnMainQueue).start(error: { error in let errorText: String switch error { case .activeLimitReached: - errorText = presentationData.strings.Username_ActiveLimitReachedError + if case .bot = mode { + errorText = presentationData.strings.Username_BotActiveLimitReachedError + } else { + errorText = presentationData.strings.Username_ActiveLimitReachedError + } default: errorText = presentationData.strings.Login_UnknownError } @@ -504,7 +514,13 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup }, deactivateLink: { name in dismissInputImpl?() let presentationData = context.sharedContext.currentPresentationData.with { $0 } - presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: presentationData.strings.Username_DeactivateAlertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: { + let alertText: String + if case .bot = mode { + alertText = presentationData.strings.Username_BotDeactivateAlertText + } else { + alertText = presentationData.strings.Username_DeactivateAlertText + } + presentControllerImpl?(textAlertController(context: context, title: presentationData.strings.Username_DeactivateAlertTitle, text: alertText, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: presentationData.strings.Username_DeactivateAlertHide, action: { let _ = context.engine.peers.toggleAddressNameActive(domain: domain, name: name, active: false).start() })]), nil) }, openAuction: { username in @@ -577,7 +593,13 @@ public func usernameSetupController(context: AccountContext, mode: UsernameSetup dismissImpl?() }) - let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(presentationData.strings.Username_Title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) + let title: String + if case .bot = mode { + title = presentationData.strings.Username_BotTitle + } else { + title = presentationData.strings.Username_Title + } + let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: .text(title), leftNavigationButton: leftNavigationButton, rightNavigationButton: rightNavigationButton, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: false) let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: usernameSetupControllerEntries(presentationData: presentationData, view: view, state: state, temporaryOrder: temporaryOrder, mode: mode), style: .blocks, focusItemTag: mode == .account ? UsernameEntryTag.username : nil, animateChanges: true) return (controllerState, (listState, arguments)) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 1c6a9576ec..c399cfad06 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -860,12 +860,15 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, brightness in var settings: WallpaperSettings? - if case let .wallpaper(wallpaper, _) = entry, case let .file(file) = wallpaper, !file.isPattern { - var intensity: Int32? - if let brightness { - intensity = max(0, min(100, Int32(brightness * 100.0))) + if case let .wallpaper(wallpaper, _) = entry { + let baseSettings = wallpaper.settings + var intensity: Int32? = baseSettings?.intensity + if case let .file(file) = wallpaper, !file.isPattern { + if let brightness { + intensity = max(0, min(100, Int32(brightness * 100.0))) + } } - settings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), intensity: intensity) + settings = WallpaperSettings(blur: options.contains(.blur), motion: options.contains(.motion), colors: baseSettings?.colors ?? [], intensity: intensity, rotation: baseSettings?.rotation) } let _ = (strongSelf.context.engine.themes.setExistingChatWallpaper(messageId: message.id, settings: settings) |> deliverOnMainQueue).start() diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index ed8fbd51da..1edb30a8f1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -1022,6 +1022,7 @@ final class PeerInfoEditingAvatarNode: ASDisplayNode { final class PeerInfoAvatarListNode: ASDisplayNode { private let isSettings: Bool + let containerNode: ASDisplayNode let pinchSourceNode: PinchSourceContainerNode let bottomCoverNode: ASDisplayNode fileprivate let maskNode: DynamicIslandMaskNode @@ -1040,6 +1041,8 @@ final class PeerInfoAvatarListNode: ASDisplayNode { init(context: AccountContext, readyWhenGalleryLoads: Bool, isSettings: Bool) { self.isSettings = isSettings + + self.containerNode = ASDisplayNode() self.bottomCoverNode = ASDisplayNode() self.bottomCoverNode.backgroundColor = .black @@ -1057,12 +1060,13 @@ final class PeerInfoAvatarListNode: ASDisplayNode { super.init() - self.addSubnode(self.bottomCoverNode) - self.addSubnode(self.pinchSourceNode) + self.addSubnode(self.containerNode) + self.containerNode.addSubnode(self.bottomCoverNode) + self.containerNode.addSubnode(self.pinchSourceNode) self.pinchSourceNode.contentNode.addSubnode(self.avatarContainerNode) self.listContainerTransformNode.addSubnode(self.listContainerNode) self.pinchSourceNode.contentNode.addSubnode(self.listContainerTransformNode) - self.addSubnode(self.topCoverNode) + self.containerNode.addSubnode(self.topCoverNode) let avatarReady = (self.avatarContainerNode.avatarNode.ready |> mapToSignal { _ -> Signal in @@ -1129,6 +1133,7 @@ final class PeerInfoAvatarListNode: ASDisplayNode { self.arguments = (peer, threadId, threadInfo, theme, avatarSize, isExpanded) self.maskNode.isForum = isForum self.pinchSourceNode.update(size: size, transition: transition) + self.containerNode.frame = CGRect(origin: CGPoint(), size: size) self.pinchSourceNode.frame = CGRect(origin: CGPoint(), size: size) self.avatarContainerNode.update(peer: peer, threadId: threadId, threadInfo: threadInfo, item: self.item, theme: theme, avatarSize: avatarSize, isExpanded: isExpanded, isSettings: self.isSettings) } @@ -2244,6 +2249,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var skipCollapseCompletion = false var ignoreCollapse = false + let avatarClippingNode: SparseNode let avatarListNode: PeerInfoAvatarListNode let buttonsContainerNode: SparseNode @@ -2305,6 +2311,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { var emojiStatusPackDisposable = MetaDisposable() var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() + private var validWidth: CGFloat? + init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) { self.context = context self.isAvatarExpanded = avatarInitiallyExpanded @@ -2314,6 +2322,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.forumTopicThreadId = forumTopicThreadId self.chatLocation = chatLocation + self.avatarClippingNode = SparseNode() + self.avatarClippingNode.clipsToBounds = true self.avatarListNode = PeerInfoAvatarListNode(context: context, readyWhenGalleryLoads: avatarInitiallyExpanded, isSettings: isSettings) self.titleNodeContainer = ASDisplayNode() @@ -2388,7 +2398,6 @@ final class PeerInfoHeaderNode: ASDisplayNode { self?.requestUpdateLayout?(false) } - if !isMediaOnly { self.addSubnode(self.buttonsContainerNode) } @@ -2400,7 +2409,8 @@ final class PeerInfoHeaderNode: ASDisplayNode { // self.subtitleNodeContainer.addSubnode(self.nextPanelSubtitleNode) self.usernameNodeContainer.addSubnode(self.usernameNode) - self.regularContentNode.addSubnode(self.avatarListNode) + self.regularContentNode.addSubnode(self.avatarClippingNode) + self.avatarClippingNode.addSubnode(self.avatarListNode) self.regularContentNode.addSubnode(self.avatarListNode.listContainerNode.controlsClippingOffsetNode) self.regularContentNode.addSubnode(self.titleNodeContainer) self.regularContentNode.addSubnode(self.subtitleNodeContainer) @@ -2567,6 +2577,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.peer = peer self.threadData = threadData self.avatarListNode.listContainerNode.peer = peer + self.validWidth = width let previousPanelStatusData = self.currentPanelStatusData self.currentPanelStatusData = panelStatusData.0 @@ -3277,6 +3288,14 @@ final class PeerInfoHeaderNode: ASDisplayNode { apparentAvatarFrame = CGRect(origin: CGPoint(x: avatarCenter.x - avatarFrame.width / 2.0, y: -contentOffset + avatarOffset + avatarCenter.y - avatarFrame.height / 2.0), size: avatarFrame.size) controlsClippingFrame = apparentAvatarFrame } + + let avatarClipOffset: CGFloat = !self.isAvatarExpanded && deviceMetrics.hasDynamicIsland ? 48.0 : 0.0 + let clippingNodeTransition = ContainedViewLayoutTransition.immediate + clippingNodeTransition.updateFrame(layer: self.avatarClippingNode.layer, frame: CGRect(origin: CGPoint(x: 0.0, y: avatarClipOffset), size: CGSize(width: width, height: 1000.0))) + clippingNodeTransition.updateSublayerTransformOffset(layer: self.avatarClippingNode.layer, offset: CGPoint(x: 0.0, y: -avatarClipOffset)) + let clippingNodeRadiusTransition = ContainedViewLayoutTransition.animated(duration: 0.15, curve: .easeInOut) + clippingNodeRadiusTransition.updateCornerRadius(node: self.avatarClippingNode, cornerRadius: avatarClipOffset > 0.0 ? width / 2.5 : 0.0) + transition.updateFrameAdditive(node: self.avatarListNode, frame: CGRect(origin: apparentAvatarFrame.center, size: CGSize())) transition.updateFrameAdditive(node: self.avatarOverlayNode, frame: CGRect(origin: apparentAvatarFrame.center, size: CGSize())) @@ -3318,26 +3337,34 @@ final class PeerInfoHeaderNode: ASDisplayNode { } if deviceMetrics.hasDynamicIsland && self.forumTopicThreadId == nil { - self.avatarListNode.maskNode.frame = CGRect(origin: CGPoint(x: -85.5, y: -self.avatarListNode.frame.minY + 48.0), size: CGSize(width: 171.0, height: 171.0)) - self.avatarListNode.bottomCoverNode.frame = self.avatarListNode.maskNode.frame - self.avatarListNode.topCoverNode.frame = self.avatarListNode.maskNode.frame - let maskValue = max(0.0, min(1.0, contentOffset / 120.0)) + self.avatarListNode.containerNode.view.mask = self.avatarListNode.maskNode.view if maskValue > 0.03 { self.avatarListNode.bottomCoverNode.isHidden = false self.avatarListNode.topCoverNode.isHidden = false - self.avatarListNode.view.mask = self.avatarListNode.maskNode.view + self.avatarListNode.maskNode.backgroundColor = .clear } else { self.avatarListNode.bottomCoverNode.isHidden = true self.avatarListNode.topCoverNode.isHidden = true - self.avatarListNode.view.mask = nil + self.avatarListNode.maskNode.backgroundColor = .white } - self.avatarListNode.maskNode.update(maskValue) self.avatarListNode.topCoverNode.update(maskValue) + self.avatarListNode.maskNode.update(maskValue) + + self.avatarListNode.listContainerNode.topShadowNode.isHidden = !self.isAvatarExpanded + + self.avatarListNode.maskNode.position = CGPoint(x: 0.0, y: -self.avatarListNode.frame.minY + 48.0 + 85.5) + self.avatarListNode.maskNode.bounds = CGRect(origin: .zero, size: CGSize(width: 171.0, height: 171.0)) + + self.avatarListNode.bottomCoverNode.position = self.avatarListNode.maskNode.position + self.avatarListNode.bottomCoverNode.bounds = self.avatarListNode.maskNode.bounds + + self.avatarListNode.topCoverNode.position = self.avatarListNode.maskNode.position + self.avatarListNode.topCoverNode.bounds = self.avatarListNode.maskNode.bounds } else { self.avatarListNode.bottomCoverNode.isHidden = true self.avatarListNode.topCoverNode.isHidden = true - self.avatarListNode.view.mask = nil + self.avatarListNode.containerNode.view.mask = nil } self.avatarListNode.listContainerNode.update(size: expandedAvatarListSize, peer: peer, isExpanded: self.isAvatarExpanded, transition: transition) @@ -3662,12 +3689,22 @@ final class PeerInfoHeaderNode: ASDisplayNode { if case .animated = transition, !isAvatarExpanded { self.avatarListNode.animateAvatarCollapse(transition: transition) } + + if let width = self.validWidth { + let maskScale: CGFloat = isAvatarExpanded ? width / 100.0 : 1.0 + transition.updateTransformScale(layer: self.avatarListNode.maskNode.layer, scale: maskScale) + transition.updateTransformScale(layer: self.avatarListNode.bottomCoverNode.layer, scale: maskScale) + transition.updateTransformScale(layer: self.avatarListNode.topCoverNode.layer, scale: maskScale) + + let maskAnchorPoint = CGPoint(x: 0.5, y: isAvatarExpanded ? 0.37 : 0.5) + transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint) + } } } } private class DynamicIslandMaskNode: ASDisplayNode { - private var animationNode: AnimationNode? + var animationNode: AnimationNode? var isForum = false { didSet { @@ -3693,6 +3730,8 @@ private class DynamicIslandMaskNode: ASDisplayNode { self.animationNode?.setProgress(value) } + var animating = false + override func layout() { self.animationNode?.frame = self.bounds } @@ -3701,7 +3740,7 @@ private class DynamicIslandMaskNode: ASDisplayNode { private class DynamicIslandBlurNode: ASDisplayNode { private var effectView: UIVisualEffectView? private let fadeNode = ASDisplayNode() - private let gradientNode = ASImageNode() + let gradientNode = ASImageNode() private var hierarchyTrackingNode: HierarchyTrackingNode? diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 27f6840b40..8c99cffcda 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1401,7 +1401,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL let ItemBotInfo = 9 if let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) { - items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_Username, icon: nil, action: { + items[.peerDataSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemUsername, label: .text("@\(user.addressName ?? "")"), text: presentationData.strings.PeerInfo_BotLinks, icon: nil, action: { interaction.editingOpenPublicLinkSetup() })) From 790343efa815556bca3c2d09609fbe3eb562faf4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 13 Apr 2023 21:06:27 +0400 Subject: [PATCH 2/4] Chat wallpaper improvements --- Telegram/Telegram-iOS/en.lproj/Localizable.strings | 1 + .../Sources/Themes/WallpaperOptionButtonNode.swift | 5 +++++ submodules/TelegramUI/Sources/ChatThemeScreen.swift | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 1ecd06f426..ac84368a67 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -9154,6 +9154,7 @@ Sorry for the inconvenience."; "WallpaperPreview.ChatBottomText" = "Enjoy the view."; "Conversation.Theme.SetPhotoWallpaper" = "Choose Wallpaper from Photos"; +"Conversation.Theme.SetNewPhotoWallpaper" = "Choose a New Wallpaper"; "Conversation.Theme.SetColorWallpaper" = "Set a Color as Wallpaper"; "Conversation.Theme.ChooseWallpaperTitle" = "Choose Wallpaper"; diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift index 444e2382b1..65455e1c73 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperOptionButtonNode.swift @@ -219,6 +219,11 @@ final class WallpaperNavigationButtonNode: HighlightTrackingButtonNode { self.animationNode?.setAnimation(name: !isNight ? "anim_sun_reverse" : "anim_sun", colors: [:]) self.animationNode?.speed = 1.66 self.animationNode?.playOnce() + + self.isUserInteractionEnabled = false + Queue.mainQueue().after(0.4) { + self.isUserInteractionEnabled = true + } } var buttonColor: UIColor = UIColor(rgb: 0x000000, alpha: 0.3) { diff --git a/submodules/TelegramUI/Sources/ChatThemeScreen.swift b/submodules/TelegramUI/Sources/ChatThemeScreen.swift index 8c8c1ba3bd..93ff7acf41 100644 --- a/submodules/TelegramUI/Sources/ChatThemeScreen.swift +++ b/submodules/TelegramUI/Sources/ChatThemeScreen.swift @@ -1015,8 +1015,8 @@ private class ChatThemeScreenNode: ViewControllerTracingNode, UIScrollViewDelega var accentButtonTheme = true var otherIsEnabled = false if self.selectedEmoticon?.strippedEmoji == self.initiallySelectedEmoticon?.strippedEmoji { - doneButtonTitle = self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper otherIsEnabled = self.controller?.canResetWallpaper == true + doneButtonTitle = otherIsEnabled ? self.presentationData.strings.Conversation_Theme_SetNewPhotoWallpaper : self.presentationData.strings.Conversation_Theme_SetPhotoWallpaper accentButtonTheme = false } else if self.selectedEmoticon == nil && self.initiallySelectedEmoticon != nil { doneButtonTitle = self.presentationData.strings.Conversation_Theme_Reset From 724a303c9c19265d1fe8d95b8506aae476aa5ce1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 13 Apr 2023 22:29:58 +0400 Subject: [PATCH 3/4] Chat wallpaper fixes --- .../Themes/CustomWallpaperPicker.swift | 6 +--- .../Sources/Themes/ThemeGridController.swift | 22 ++++++++++---- .../Sources/Themes/WallpaperGalleryItem.swift | 14 +++++++++ .../PendingMessageUploadedContent.swift | 4 +-- .../PendingPeerMediaUploadManager.swift | 29 +++++++++++++++++-- .../TelegramEngine/Themes/ChatThemes.swift | 24 +++++++++++---- .../Themes/TelegramEngineThemes.swift | 2 +- .../TelegramUI/Sources/ChatController.swift | 15 +++++++++- ...hatMessageWallpaperBubbleContentNode.swift | 2 +- 9 files changed, 93 insertions(+), 25 deletions(-) diff --git a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift index d938dd0358..47ef6d0b8f 100644 --- a/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift +++ b/submodules/SettingsUI/Sources/Themes/CustomWallpaperPicker.swift @@ -284,11 +284,7 @@ public func uploadCustomPeerWallpaper(context: AccountContext, wallpaper: Wallpa finalCropRect = CGRect(x: (image.size.width - fittedSize.width) / 2.0, y: (image.size.height - fittedSize.height) / 2.0, width: fittedSize.width, height: fittedSize.height) } croppedImage = TGPhotoEditorCrop(image, nil, .up, 0.0, finalCropRect, false, CGSize(width: 1440.0, height: 2960.0), image.size, true) - - if mode.contains(.blur) { - croppedImage = blurredImage(croppedImage, radius: 30.0)! - } - + let thumbnailDimensions = finalCropRect.size.fitted(CGSize(width: 320.0, height: 320.0)) let thumbnailImage = generateScaledImage(image: croppedImage, size: thumbnailDimensions, scale: 1.0) diff --git a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift index 048a0af79e..8bf500a949 100644 --- a/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift +++ b/submodules/SettingsUI/Sources/Themes/ThemeGridController.swift @@ -142,18 +142,28 @@ public final class ThemeGridController: ViewController { } }, presentGallery: { [weak self] in if let strongSelf = self { + let dismissControllers = { [weak self] in + if let self, let navigationController = self.navigationController as? NavigationController { + let controllers = navigationController.viewControllers.filter({ controller in + if controller is WallpaperGalleryController || controller is MediaPickerScreen { + return false + } + return true + }) + navigationController.setViewControllers(controllers, animated: true) + } + } + let controller = MediaPickerScreen(context: strongSelf.context, peer: nil, threadTitle: nil, chatLocation: nil, bannedSendPhotos: nil, bannedSendVideos: nil, subject: .assets(nil, .wallpaper)) controller.customSelection = { [weak self] asset in guard let strongSelf = self else { return } let controller = WallpaperGalleryController(context: strongSelf.context, source: .asset(asset)) - controller.apply = { [weak self, weak controller] wallpaper, options, editedImage, cropRect, brightness in - if let strongSelf = self, let controller = controller { - uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { [weak controller] in - if let controller = controller { - controller.dismiss(forceAway: true) - } + controller.apply = { [weak self] wallpaper, options, editedImage, cropRect, brightness in + if let strongSelf = self { + uploadCustomWallpaper(context: strongSelf.context, wallpaper: wallpaper, mode: options, editedImage: editedImage, cropRect: cropRect, brightness: brightness, completion: { + dismissControllers() }) } } diff --git a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift index edd7fec83b..51ccf0e2d1 100644 --- a/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift +++ b/submodules/SettingsUI/Sources/Themes/WallpaperGalleryItem.swift @@ -668,6 +668,20 @@ final class WallpaperGalleryItemNode: GalleryItemNode { self.patternButtonNode.isSelected = false self.playButtonNode.setIcon(self.playButtonRotateImage) } + + if let settings = wallpaper.settings { + if settings.blur { + self.blurButtonNode.setSelected(true, animated: false) + self.setBlurEnabled(true, animated: false) + } + if settings.motion { + self.motionButtonNode.setSelected(true, animated: false) + self.setMotionEnabled(true, animated: false) + } + if case let .file(file) = wallpaper, !file.isPattern, let intensity = file.settings.intensity { + self.sliderNode.value = (1.0 - CGFloat(intensity) / 100.0) + } + } case .asset: self.nativeNode._internalUpdateIsSettingUpWallpaper() self.nativeNode.isHidden = true diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift index 29d6a27614..595b020200 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingMessageUploadedContent.swift @@ -212,8 +212,8 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: let inputPoll = Api.InputMedia.inputMediaPoll(flags: pollMediaFlags, poll: Api.Poll.poll(id: 0, flags: pollFlags, question: poll.text, answers: poll.options.map({ $0.apiOption }), closePeriod: poll.deadlineTimeout, closeDate: nil), correctAnswers: correctAnswers, solution: mappedSolution, solutionEntities: mappedSolutionEntities) return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputPoll, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else if let media = media as? TelegramMediaDice { - let input = Api.InputMedia.inputMediaDice(emoticon: media.emoji) - return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil, cacheReferenceKey: nil))) + let inputDice = Api.InputMedia.inputMediaDice(emoticon: media.emoji) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(inputDice, text), reuploadInfo: nil, cacheReferenceKey: nil))) } else { return nil } diff --git a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift index 2e6468064e..a1938e5310 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/PendingPeerMediaUploadManager.swift @@ -11,6 +11,7 @@ public final class PeerMediaUploadingItem: Equatable { public enum Error { case generic + case flood } public enum Content: Equatable { @@ -69,7 +70,14 @@ private func uploadPeerMedia(postbox: Postbox, network: Network, stateManager: A } } return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: result, applyUpdates: false) - |> castError(PeerMediaUploadingItem.Error.self) + |> mapError { error -> PeerMediaUploadingItem.Error in + switch error { + case .generic: + return .generic + case .flood: + return .flood + } + } |> map { updates -> PeerMediaUploadingItem.ProgressValue in return .done(updates) } @@ -78,7 +86,14 @@ private func uploadPeerMedia(postbox: Postbox, network: Network, stateManager: A } } else { return _internal_setChatWallpaper(postbox: postbox, network: network, stateManager: stateManager, peerId: peerId, wallpaper: wallpaper, applyUpdates: false) - |> castError(PeerMediaUploadingItem.Error.self) + |> mapError { error -> PeerMediaUploadingItem.Error in + switch error { + case .generic: + return .generic + case .flood: + return .flood + } + } |> map { updates -> PeerMediaUploadingItem.ProgressValue in return .done(updates) } @@ -265,7 +280,15 @@ private final class PendingPeerMediaUploadManagerImpl { } if let context = strongSelf.contexts[peerId], context === initialContext { strongSelf.contexts.removeValue(forKey: peerId) - context.disposable.dispose() + + if let messageId = context.value.messageId { + context.disposable.set(strongSelf.postbox.transaction({ transaction in + transaction.deleteMessages([messageId], forEachMedia: nil) + }).start()) + } else { + context.disposable.dispose() + } + strongSelf.updateValues() } })) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift index 8e97221e71..437097df2d 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/ChatThemes.swift @@ -118,13 +118,19 @@ func managedChatThemesUpdates(accountManager: AccountManager then(.complete() |> suspendAwareDelay(1.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart } -func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, wallpaper: TelegramWallpaper?, applyUpdates: Bool = true) -> Signal { +public enum SetChatWallpaperError { + case generic + case flood +} + +func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager: AccountStateManager, peerId: PeerId, wallpaper: TelegramWallpaper?, applyUpdates: Bool = true) -> Signal { return postbox.loadedPeerWithId(peerId) + |> castError(SetChatWallpaperError.self) |> mapToSignal { peer in guard let inputPeer = apiInputPeer(peer) else { return .complete() } - return postbox.transaction { transaction -> Signal in + return postbox.transaction { transaction -> Signal in transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, current in if let current = current as? CachedUserData { return current.withUpdatedWallpaper(wallpaper) @@ -143,16 +149,22 @@ func _internal_setChatWallpaper(postbox: Postbox, network: Network, stateManager inputSettings = inputWallpaperAndInputSettings.1 } return network.request(Api.functions.messages.setChatWallPaper(flags: flags, peer: inputPeer, wallpaper: inputWallpaper, settings: inputSettings, id: nil), automaticFloodWait: false) - |> `catch` { error in - return .complete() + |> mapError { error -> SetChatWallpaperError in + if error.errorDescription.hasPrefix("FLOOD_WAIT") { + return .flood + } else { + return .generic + } } - |> mapToSignal { updates -> Signal in + |> mapToSignal { updates -> Signal in if applyUpdates { stateManager.addUpdates(updates) } return .single(updates) } - } |> switchToLatest + } + |> castError(SetChatWallpaperError.self) + |> switchToLatest } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift index 623efabd58..e189e3cbaa 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Themes/TelegramEngineThemes.swift @@ -17,7 +17,7 @@ public extension TelegramEngine { return _internal_setChatTheme(account: self.account, peerId: peerId, emoticon: emoticon) } - public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { + public func setChatWallpaper(peerId: PeerId, wallpaper: TelegramWallpaper?) -> Signal { return _internal_setChatWallpaper(postbox: self.account.postbox, network: self.account.network, stateManager: self.account.stateManager, peerId: peerId, wallpaper: wallpaper) |> ignoreValues } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index c399cfad06..3733d0799d 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -857,7 +857,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return true } strongSelf.chatDisplayNode.dismissInput() - let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, nil, [], nil, nil, nil), mode: .peer(EnginePeer(peer), true)) + var options = WallpaperPresentationOptions() + var intensity: Int32? + if let settings = wallpaper.settings { + if settings.blur { + options.insert(.blur) + } + if settings.motion { + options.insert(.motion) + } + if case let .file(file) = wallpaper, !file.isPattern { + intensity = settings.intensity + } + } + let wallpaperPreviewController = WallpaperGalleryController(context: strongSelf.context, source: .wallpaper(wallpaper, options, [], intensity, nil, nil), mode: .peer(EnginePeer(peer), true)) wallpaperPreviewController.apply = { [weak wallpaperPreviewController] entry, options, _, _, brightness in var settings: WallpaperSettings? if case let .wallpaper(wallpaper, _) = entry { diff --git a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift index 853caa832f..6624a49de8 100644 --- a/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageWallpaperBubbleContentNode.swift @@ -328,7 +328,7 @@ class ChatMessageWallpaperBubbleContentNode: ChatMessageBubbleContentNode { if let dimensions = file.dimensions?.cgSize { imageSize = dimensions.aspectFilled(boundingSize) } - updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), representations: representations, alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true) + updateImageSignal = wallpaperImage(account: item.context.account, accountManager: item.context.sharedContext.accountManager, fileReference: FileMediaReference.message(message: MessageReference(item.message), media: file), representations: representations, alwaysShowThumbnailFirst: true, thumbnail: true, autoFetchFullSize: true, blurred: wallpaper?.settings?.blur == true) } case let .image(representations): if let dimensions = representations.last?.dimensions.cgSize { From 8d9068d87d8adf90ee732904376facdc08b6fab9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 13 Apr 2023 23:02:05 +0400 Subject: [PATCH 4/4] Various fixes --- .../Sources/PeerInfo/PeerInfoHeaderNode.swift | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift index 1edb30a8f1..6205c1f7fd 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoHeaderNode.swift @@ -2311,7 +2311,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { var emojiStatusPackDisposable = MetaDisposable() var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() - private var validWidth: CGFloat? + private var validLayout: (width: CGFloat, deviceMetrics: DeviceMetrics)? init(context: AccountContext, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, isMediaOnly: Bool, isSettings: Bool, forumTopicThreadId: Int64?, chatLocation: ChatLocation) { self.context = context @@ -2577,7 +2577,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.peer = peer self.threadData = threadData self.avatarListNode.listContainerNode.peer = peer - self.validWidth = width + + let isFirstTime = self.validLayout == nil + self.validLayout = (width, deviceMetrics) let previousPanelStatusData = self.currentPanelStatusData self.currentPanelStatusData = panelStatusData.0 @@ -3628,6 +3630,10 @@ final class PeerInfoHeaderNode: ASDisplayNode { transition.updateFrame(node: self.separatorNode, frame: separatorFrame) } + if isFirstTime { + self.updateAvatarMask(transition: .immediate) + } + return resolvedHeight } @@ -3690,17 +3696,22 @@ final class PeerInfoHeaderNode: ASDisplayNode { self.avatarListNode.animateAvatarCollapse(transition: transition) } - if let width = self.validWidth { - let maskScale: CGFloat = isAvatarExpanded ? width / 100.0 : 1.0 - transition.updateTransformScale(layer: self.avatarListNode.maskNode.layer, scale: maskScale) - transition.updateTransformScale(layer: self.avatarListNode.bottomCoverNode.layer, scale: maskScale) - transition.updateTransformScale(layer: self.avatarListNode.topCoverNode.layer, scale: maskScale) - - let maskAnchorPoint = CGPoint(x: 0.5, y: isAvatarExpanded ? 0.37 : 0.5) - transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint) - } + self.updateAvatarMask(transition: transition) } } + + private func updateAvatarMask(transition: ContainedViewLayoutTransition) { + guard let (width, deviceMetrics) = self.validLayout, deviceMetrics.hasDynamicIsland else { + return + } + let maskScale: CGFloat = isAvatarExpanded ? width / 100.0 : 1.0 + transition.updateTransformScale(layer: self.avatarListNode.maskNode.layer, scale: maskScale) + transition.updateTransformScale(layer: self.avatarListNode.bottomCoverNode.layer, scale: maskScale) + transition.updateTransformScale(layer: self.avatarListNode.topCoverNode.layer, scale: maskScale) + + let maskAnchorPoint = CGPoint(x: 0.5, y: isAvatarExpanded ? 0.37 : 0.5) + transition.updateAnchorPoint(layer: self.avatarListNode.maskNode.layer, anchorPoint: maskAnchorPoint) + } } private class DynamicIslandMaskNode: ASDisplayNode {