diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 53d72796a8..7dd672027b 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -8093,6 +8093,8 @@ Sorry for the inconvenience."; "Group.Setup.ActivateAlertText" = "Do you want to show this link on the group info page?"; "Group.Setup.ActivateAlertShow" = "Show"; +"Group.Setup.ActiveLimitReachedError" = "Sorry, you have too many active public links already. Please hide one of your active public links first."; + "Group.Setup.DeactivateAlertTitle" = "Deactivate Link"; "Group.Setup.DeactivateAlertText" = "Do you want to hide this link from the group info page?"; "Group.Setup.DeactivateAlertHide" = "Hide"; @@ -8105,6 +8107,8 @@ Sorry for the inconvenience."; "Channel.Setup.ActivateAlertText" = "Do you want to show this link on the channel info page?"; "Channel.Setup.ActivateAlertShow" = "Show"; +"Channel.Setup.ActiveLimitReachedError" = "Sorry, you have too many active public links already. Please hide one of your active public links first."; + "Channel.Setup.DeactivateAlertTitle" = "Deactivate Link"; "Channel.Setup.DeactivateAlertText" = "Do you want to hide this link from the channel info page?"; "Channel.Setup.DeactivateAlertHide" = "Hide"; @@ -8117,6 +8121,8 @@ Sorry for the inconvenience."; "Username.ActivateAlertText" = "Do you want to show this link on your info page?"; "Username.ActivateAlertShow" = "Show"; +"Username.ActiveLimitReachedError" = "Sorry, you have too many active public links already. Please hide one of your active public links first."; + "Username.DeactivateAlertTitle" = "Deactivate Username"; "Username.DeactivateAlertText" = "Do you want to hide this link from your info page?"; "Username.DeactivateAlertHide" = "Hide"; diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 84dd97455d..0537a33e06 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1591,9 +1591,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController controller.navigationPresentation = .modal controller.completion = { title, fileId in - let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] - - let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.randomElement()!, iconFileId: fileId) + let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() }) @@ -2634,9 +2632,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController let controller = ForumCreateTopicScreen(context: context, peerId: peerId, mode: .create) controller.navigationPresentation = .modal controller.completion = { title, fileId in - let availableColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] - - let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: availableColors.randomElement()!, iconFileId: fileId) + let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: ForumCreateTopicScreen.iconColors.randomElement()!, iconFileId: fileId) |> deliverOnMainQueue).start(next: { topicId in if let navigationController = (sourceController.navigationController as? NavigationController) { let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: topicId, messageId: nil, navigationController: navigationController, activateInput: .text).start() diff --git a/submodules/ChatListUI/Sources/ChatListControllerNode.swift b/submodules/ChatListUI/Sources/ChatListControllerNode.swift index 39fd4a3efb..ddf696a788 100644 --- a/submodules/ChatListUI/Sources/ChatListControllerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListControllerNode.swift @@ -337,8 +337,14 @@ private final class ChatListContainerItemNode: ASDisplayNode { currentNode.updateIsLoading(isLoading) } else { let subject: ChatListEmptyNode.Subject - if filter != nil { - subject = .filter + if let filter = filter { + var showEdit = true + if case let .filter(_, _, _, data) = filter { + if data.excludeRead && data.includePeers.peers.isEmpty && data.includePeers.pinnedPeers.isEmpty { + showEdit = false + } + } + subject = .filter(showEdit: showEdit) } else { if case .forum = location { subject = .forum @@ -1309,7 +1315,10 @@ final class ChatListControllerNode: ASDisplayNode { return nil } - let filter: ChatListNodePeersFilter = [] + var filter: ChatListNodePeersFilter = [] + if case .forum = self.location { + filter.insert(.excludeRecent) + } let contentNode = ChatListSearchContainerNode(context: self.context, animationCache: self.animationCache, animationRenderer: self.animationRenderer, filter: filter, location: location, displaySearchFilters: displaySearchFilters, hasDownloads: hasDownloads, initialFilter: initialFilter, openPeer: { [weak self] peer, _, threadId, dismissSearch in self?.requestOpenPeerFromSearch?(peer, threadId, dismissSearch) diff --git a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift index 2ca8fab6e6..c83e113635 100644 --- a/submodules/ChatListUI/Sources/ChatListEmptyNode.swift +++ b/submodules/ChatListUI/Sources/ChatListEmptyNode.swift @@ -13,7 +13,7 @@ import AccountContext final class ChatListEmptyNode: ASDisplayNode { enum Subject { case chats - case filter + case filter(showEdit: Bool) case forum } private let action: () -> Void @@ -54,7 +54,12 @@ final class ChatListEmptyNode: ASDisplayNode { self.descriptionNode.textAlignment = .center self.descriptionNode.lineSpacing = 0.1 - self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), cornerRadius: 11.0, gloss: true) + var gloss = true + if case .filter = subject { + gloss = false + } + + self.buttonNode = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: theme), cornerRadius: 11.0, gloss: gloss) self.secondaryButtonNode = HighlightableButtonNode() @@ -70,8 +75,9 @@ final class ChatListEmptyNode: ASDisplayNode { self.addSubnode(self.activityIndicator) let animationName: String - if case .filter = subject { + if case let .filter(showEdit) = subject { animationName = "ChatListFilterEmpty" + self.buttonNode.isHidden = !showEdit } else { animationName = "ChatListEmpty" } diff --git a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift index 5eb58e080f..fa5c562064 100644 --- a/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift +++ b/submodules/ChatListUI/Sources/ChatListSearchContainerNode.swift @@ -483,6 +483,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo self.copyProtectionTooltipController?.dismiss() } + public override var hasDim: Bool { + return self.peersFilter.contains(.excludeRecent) + } + private func updateState(_ f: (ChatListSearchContainerNodeSearchState) -> ChatListSearchContainerNodeSearchState) { let state = f(self.stateValue) if state != self.stateValue { @@ -606,6 +610,7 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo override public func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: transition) + let isFirstTime = self.validLayout == nil self.validLayout = (layout, navigationBarHeight) let topInset = navigationBarHeight @@ -626,6 +631,10 @@ public final class ChatListSearchContainerNode: SearchDisplayControllerContentNo let overflowInset: CGFloat = 20.0 self.filterContainerNode.update(size: CGSize(width: layout.size.width - overflowInset * 2.0, height: 38.0), sideInset: layout.safeInsets.left - overflowInset, filters: filters.map { .filter($0) }, selectedFilter: self.selectedFilter?.id, transitionFraction: self.transitionFraction, presentationData: self.presentationData, transition: .animated(duration: 0.4, curve: .spring)) + if isFirstTime { + self.filterContainerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + var bottomIntrinsicInset = layout.intrinsicInsets.bottom if case .chatList(.root) = self.location { if layout.safeInsets.left > overflowInset { diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index d8628f0ecd..7cfe272b56 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -330,10 +330,16 @@ private func groupReferenceRevealOptions(strings: PresentationStrings, theme: Pr return options } -private func forumRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isEditing: Bool, canManage: Bool) -> [ItemListRevealOption] { +private func forumRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isMuted: Bool?, isClosed: Bool, isPinned: Bool, isEditing: Bool, canManage: Bool) -> [ItemListRevealOption] { var options: [ItemListRevealOption] = [] if !isEditing { - if let isMuted = isMuted { + if canManage { + if isPinned { + options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + } else { + options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) + } + } else if let isMuted = isMuted { if isMuted { options.append(ItemListRevealOption(key: RevealOptionKey.unmute.rawValue, title: strings.ChatList_Unmute, icon: unmuteIcon, color: theme.list.itemDisclosureActions.neutral2.fillColor, textColor: theme.list.itemDisclosureActions.neutral2.foregroundColor)) } else { @@ -354,20 +360,6 @@ private func forumRevealOptions(strings: PresentationStrings, theme: Presentatio return options } -private func forumLeftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, location: ChatListControllerLocation, peer: EnginePeer) -> [ItemListRevealOption] { - var options: [ItemListRevealOption] = [] - - if case let .channel(channel) = peer, channel.hasPermission(.pinMessages) { - if isPinned { - options.append(ItemListRevealOption(key: RevealOptionKey.unpin.rawValue, title: strings.DialogList_Unpin, icon: unpinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) - } else { - options.append(ItemListRevealOption(key: RevealOptionKey.pin.rawValue, title: strings.DialogList_Pin, icon: pinIcon, color: theme.list.itemDisclosureActions.constructive.fillColor, textColor: theme.list.itemDisclosureActions.constructive.foregroundColor)) - } - } - - return options -} - private func leftRevealOptions(strings: PresentationStrings, theme: PresentationTheme, isUnread: Bool, isEditing: Bool, isPinned: Bool, isSavedMessages: Bool, location: ChatListControllerLocation, peer: EnginePeer, filterData: ChatListItemFilterData?) -> [ItemListRevealOption] { switch location { case let .chatList(groupId): @@ -1869,8 +1861,8 @@ class ChatListItemNode: ItemListRevealOptionsItemNode { if let threadInfo { isClosed = threadInfo.isClosed } - peerRevealOptions = forumRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isEditing: item.editing, canManage: canManage) - peerLeftRevealOptions = forumLeftRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isUnread: unreadCount.unread, isEditing: item.editing, isPinned: isPinned, location: item.chatListLocation, peer: itemPeer.peers[itemPeer.peerId]!) + peerRevealOptions = forumRevealOptions(strings: item.presentationData.strings, theme: item.presentationData.theme, isMuted: (currentMutedIconImage != nil), isClosed: isClosed, isPinned: isPinned, isEditing: item.editing, canManage: canManage) + peerLeftRevealOptions = [] } else { peerRevealOptions = [] peerLeftRevealOptions = [] diff --git a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift index f8a02d9b45..17f0989bf0 100644 --- a/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift +++ b/submodules/InviteLinksUI/Sources/AdditionalLinkItem.swift @@ -193,6 +193,7 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { return { item, params, neighbors, firstWithHeader, last in var updatedTheme: PresentationTheme? + var updatedIsActive = false let titleFont = Font.regular(item.presentationData.fontSize.itemListBaseFontSize) let subtitleFont = Font.regular(floor(item.presentationData.fontSize.itemListBaseFontSize * 14.0 / 17.0)) @@ -200,6 +201,9 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { if currentItem?.presentationData.theme !== item.presentationData.theme { updatedTheme = item.presentationData.theme } + if currentItem?.username?.isActive != item.username?.isActive { + updatedIsActive = true + } let iconColor: UIColor if let username = item.username { @@ -303,7 +307,11 @@ public class AdditionalLinkItemNode: ListViewItemNode, ItemListItemNode { strongSelf.backgroundNode.backgroundColor = itemBackgroundColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.list.itemHighlightedBackgroundColor - strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Link"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) + + } + + if updatedIsActive || updatedTheme != nil { + strongSelf.iconNode.image = generateTintedImage(image: UIImage(bundleImageName: item.username?.isActive == true ? "Chat/Context Menu/Link" : "Chat/Context Menu/Unlink"), color: item.presentationData.theme.list.itemCheckColors.foregroundColor) } let transition = ContainedViewLayoutTransition.immediate diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 4e6b4101df..b69f9668c2 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -1673,7 +1673,20 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta action = presentationData.strings.Channel_Setup_ActivateAlertShow } presentControllerImpl?(textAlertController(context: context, title: title, text: text, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .defaultAction, title: action, action: { - let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: true).start() + let _ = context.engine.peers.toggleAddressNameActive(domain: .peer(peerId), name: name, active: true).start(error: { error in + let errorText: String + switch error { + case .activeLimitReached: + if isGroup { + errorText = presentationData.strings.Group_Setup_ActiveLimitReachedError + } else { + errorText = presentationData.strings.Channel_Setup_ActiveLimitReachedError + } + default: + errorText = presentationData.strings.Login_UnknownError + } + presentControllerImpl?(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + }) })]), nil) }) }, deactivateLink: { name in diff --git a/submodules/SearchBarNode/Sources/SearchBarNode.swift b/submodules/SearchBarNode/Sources/SearchBarNode.swift index da7e7d4983..76d53fd934 100644 --- a/submodules/SearchBarNode/Sources/SearchBarNode.swift +++ b/submodules/SearchBarNode/Sources/SearchBarNode.swift @@ -1042,7 +1042,7 @@ public class SearchBarNode: ASDisplayNode, UITextFieldDelegate { public func deactivate(clear: Bool = true) { self.textField.resignFirstResponder() if clear { - self.textField.text = nil + self.textField.text = nil self.textField.tokens = [] self.textField.prefixString = nil self.textField.placeholderLabel.alpha = 1.0 diff --git a/submodules/SearchUI/Sources/SearchDisplayController.swift b/submodules/SearchUI/Sources/SearchDisplayController.swift index 506d3d7e9a..3dc9dafaa6 100644 --- a/submodules/SearchUI/Sources/SearchDisplayController.swift +++ b/submodules/SearchUI/Sources/SearchDisplayController.swift @@ -42,7 +42,6 @@ public final class SearchDisplayController { self.inline = inline self.searchBar = SearchBarNode(theme: SearchBarNodeTheme(theme: presentationData.theme, hasBackground: hasBackground, hasSeparator: hasSeparator, inline: inline), strings: presentationData.strings, fieldStyle: .modern, forceSeparator: hasSeparator, displayBackground: hasBackground) self.backgroundNode = BackgroundNode() - self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor self.backgroundNode.allowsGroupOpacity = true self.mode = mode @@ -105,6 +104,14 @@ public final class SearchDisplayController { |> deliverOnMainQueue).start(next: { [weak self] value in self?.searchBar.activity = value }) + + if self.contentNode.hasDim { + self.backgroundNode.backgroundColor = .clear + self.backgroundNode.isTransparent = true + } else { + self.backgroundNode.backgroundColor = presentationData.theme.chatList.backgroundColor + self.backgroundNode.isTransparent = false + } } public func updatePresentationData(_ presentationData: PresentationData) { diff --git a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift index 947d3cb4c8..435df4d5bc 100644 --- a/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift +++ b/submodules/SelectablePeerNode/Sources/SelectablePeerNode.swift @@ -73,7 +73,7 @@ public final class SelectablePeerNode: ASDisplayNode { private let avatarNode: AvatarNode private let onlineNode: PeerOnlineMarkerNode private var checkNode: CheckNode? - private let textNode: ASTextNode + private let textNode: ImmediateTextNode private let iconView: ComponentView @@ -117,7 +117,7 @@ public final class SelectablePeerNode: ASDisplayNode { self.avatarNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: 60.0, height: 60.0)) self.avatarNode.isLayerBacked = !smartInvertColorsEnabled() - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false @@ -309,16 +309,16 @@ public final class SelectablePeerNode: ASDisplayNode { self.avatarNodeContainer.frame = CGRect(origin: CGPoint(x: floor((bounds.size.width - 60.0) / 2.0), y: 4.0), size: CGSize(width: 60.0, height: 60.0)) let iconSize = CGSize(width: 18.0, height: 18.0) - let textSize = self.textNode.calculateSizeThatFits(bounds.size) + let textSize = self.textNode.updateLayout(bounds.size) var totalWidth = textSize.width var leftOrigin = floorToScreenPixels((bounds.width - textSize.width) / 2.0) if let iconView = self.iconView.view, iconView.superview != nil { totalWidth += iconView.frame.width + 2.0 leftOrigin = floorToScreenPixels((bounds.width - totalWidth) / 2.0) - iconView.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 1.0), size: iconSize) + iconView.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0 + floorToScreenPixels((textSize.height - iconSize.height) / 2.0)), size: iconSize) leftOrigin += iconSize.width + 2.0 } - self.textNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0), size: CGSize(width: textSize.width, height: 34.0)) + self.textNode.frame = CGRect(origin: CGPoint(x: leftOrigin, y: 4.0 + 60.0 + 4.0), size: textSize) let avatarFrame = self.avatarNode.frame let avatarContainerFrame = self.avatarNodeContainer.frame diff --git a/submodules/SettingsUI/Sources/UsernameSetupController.swift b/submodules/SettingsUI/Sources/UsernameSetupController.swift index f256291c5b..0878b6a2e6 100644 --- a/submodules/SettingsUI/Sources/UsernameSetupController.swift +++ b/submodules/SettingsUI/Sources/UsernameSetupController.swift @@ -443,7 +443,16 @@ public func usernameSetupController(context: AccountContext) -> ViewController { 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 _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true).start() + let _ = context.engine.peers.toggleAddressNameActive(domain: .account, name: name, active: true).start(error: { error in + let errorText: String + switch error { + case .activeLimitReached: + errorText = presentationData.strings.Username_ActiveLimitReachedError + default: + errorText = presentationData.strings.Login_UnknownError + } + presentControllerImpl?(textAlertController(context: context, title: nil, text: errorText, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), nil) + }) })]), nil) }, deactivateLink: { name in dismissInputImpl?() diff --git a/submodules/ShareController/Sources/ShareControllerNode.swift b/submodules/ShareController/Sources/ShareControllerNode.swift index 86ce339e8c..a968a5e7aa 100644 --- a/submodules/ShareController/Sources/ShareControllerNode.swift +++ b/submodules/ShareController/Sources/ShareControllerNode.swift @@ -392,39 +392,53 @@ final class ShareControllerNode: ViewControllerTracingNode, UIScrollViewDelegate } strongSelf.topicsContentNode = topicsContentNode strongSelf.contentNode?.supernode?.addSubnode(topicsContentNode) - topicsContentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in - self?.contentNodeOffsetUpdated(contentOffset, transition: transition) - }) if let (layout, navigationBarHeight, _) = strongSelf.containerLayout { strongSelf.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .immediate) } - if let sourceFrame = strongSelf.peersContentNode?.animateOut(peerId: peer.peerId) { - topicsContentNode.animateIn(sourceFrame: sourceFrame) + if let peersContentNode = strongSelf.peersContentNode { + peersContentNode.setContentOffsetUpdated(nil) + let scrollDelta = topicsContentNode.contentGridNode.scrollView.contentOffset.y - peersContentNode.contentGridNode.scrollView.contentOffset.y + if let sourceFrame = peersContentNode.animateOut(peerId: peer.peerId, scrollDelta: scrollDelta) { + topicsContentNode.animateIn(sourceFrame: sourceFrame, scrollDelta: scrollDelta) + } } + + topicsContentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in + self?.contentNodeOffsetUpdated(contentOffset, transition: transition) + }) + strongSelf.contentNodeOffsetUpdated(topicsContentNode.contentGridNode.scrollView.contentOffset.y, transition: .animated(duration: 0.4, curve: .spring)) }) } func closePeerTopics(_ peerId: EnginePeer.Id) { if let topicsContentNode = self.topicsContentNode, let peersContentNode = self.peersContentNode { + topicsContentNode.setContentOffsetUpdated(nil) topicsContentNode.supernode?.insertSubnode(topicsContentNode, belowSubnode: peersContentNode) } - self.peersContentNode?.setContentOffsetUpdated({ [weak self] contentOffset, transition in - self?.contentNodeOffsetUpdated(contentOffset, transition: transition) - }) if let (layout, navigationBarHeight, _) = self.containerLayout { self.containerLayoutUpdated(layout, navigationBarHeight: navigationBarHeight, transition: .animated(duration: 0.4, curve: .spring)) } - - if let targetFrame = self.peersContentNode?.animateIn(peerId: peerId), let topicsContentNode = self.topicsContentNode { - topicsContentNode.animateOut(targetFrame: targetFrame, completion: { [weak self] in - if let topicsContentNode = self?.topicsContentNode { - topicsContentNode.removeFromSupernode() - self?.topicsContentNode = nil - } + + if let peersContentNode = self.peersContentNode { + peersContentNode.setContentOffsetUpdated({ [weak self] contentOffset, transition in + self?.contentNodeOffsetUpdated(contentOffset, transition: transition) }) + self.contentNodeOffsetUpdated(peersContentNode.contentGridNode.scrollView.contentOffset.y, transition: .animated(duration: 0.4, curve: .spring)) + } + + if let peersContentNode = self.peersContentNode, let topicsContentNode = self.topicsContentNode { + let scrollDelta = topicsContentNode.contentGridNode.scrollView.contentOffset.y - peersContentNode.contentGridNode.scrollView.contentOffset.y + if let targetFrame = peersContentNode.animateIn(peerId: peerId, scrollDelta: scrollDelta) { + topicsContentNode.animateOut(targetFrame: targetFrame, scrollDelta: scrollDelta, completion: { [weak self] in + if let topicsContentNode = self?.topicsContentNode { + topicsContentNode.removeFromSupernode() + self?.topicsContentNode = nil + } + }) + } } } diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index 654b4daad9..b51b6d8a5d 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -115,7 +115,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { private var entries: [SharePeerEntry] = [] private var enqueuedTransitions: [(ShareGridTransaction, Bool)] = [] - private let contentGridNode: GridNode + let contentGridNode: GridNode + private let headerNode: ASDisplayNode private let contentTitleNode: ASTextNode private let contentSubtitleNode: ASTextNode private let contentTitleAccountNode: AvatarNode @@ -193,6 +194,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } self.contentGridNode = GridNode() + self.headerNode = ASDisplayNode() self.contentTitleNode = ASTextNode() self.contentTitleNode.attributedText = NSAttributedString(string: strings.ShareMenu_ShareTo, font: Font.medium(20.0), textColor: self.theme.actionSheet.primaryTextColor) @@ -247,17 +249,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { super.init() self.addSubnode(self.contentGridNode) + self.addSubnode(self.headerNode) - self.addSubnode(self.contentTitleNode) - self.addSubnode(self.contentSubtitleNode) - self.addSubnode(self.contentTitleAccountNode) - self.addSubnode(self.segmentedNode) - self.addSubnode(self.searchButtonNode) + self.headerNode.addSubnode(self.contentTitleNode) + self.headerNode.addSubnode(self.contentSubtitleNode) + self.headerNode.addSubnode(self.contentTitleAccountNode) + self.headerNode.addSubnode(self.segmentedNode) + self.headerNode.addSubnode(self.searchButtonNode) self.shareContainerNode.addSubnode(self.shareReferenceNode) self.shareButtonNode.addSubnode(self.shareContainerNode) - self.addSubnode(self.shareButtonNode) + self.headerNode.addSubnode(self.shareButtonNode) + self.addSubnode(self.contentSeparatorNode) self.shareContainerNode.activated = { [weak self] gesture, _ in @@ -367,7 +371,11 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { node = itemNode } } - return node?.frame.offsetBy(dx: 0.0, dy: -10.0) + if let node = node { + return node.frame.offsetBy(dx: 0.0, dy: -10.0) + } else { + return nil + } } func generateMaskImage() -> UIImage? { @@ -385,10 +393,16 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { })?.stretchableImage(withLeftCapWidth: 49, topCapHeight: 49) } - func animateIn(peerId: EnginePeer.Id) -> CGRect? { + func animateIn(peerId: EnginePeer.Id, scrollDelta: CGFloat) -> CGRect? { + self.headerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -scrollDelta), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.searchButtonNode.alpha = 1.0 self.searchButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.searchButtonNode.layer.animatePosition(from: CGPoint(x: -20.0, y: 0.0), to: .zero, duration: 0.2, additive: true) + + self.shareButtonNode.alpha = 1.0 + self.shareButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.shareButtonNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 0.0), to: .zero, duration: 0.2, additive: true) self.contentTitleNode.alpha = 1.0 self.contentTitleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) @@ -400,14 +414,17 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentSubtitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -10.0), to: .zero, duration: 0.2, additive: true) self.contentSubtitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2) + self.contentGridNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -scrollDelta), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + if let targetFrame = self.frameForPeerId(peerId), let (size, bottomInset) = self.validLayout { - let sourceCenter = targetFrame.center let clippedNode = ASDisplayNode() clippedNode.clipsToBounds = true clippedNode.cornerRadius = 16.0 clippedNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentTitleNode.frame.minY - 15.0), size: CGSize(width: size.width, height: size.height - bottomInset)) self.contentGridNode.view.superview?.insertSubview(clippedNode.view, aboveSubview: self.contentGridNode.view) + clippedNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -scrollDelta), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + let maskView = UIView() maskView.frame = clippedNode.bounds @@ -415,33 +432,31 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { maskImageView.image = generateMaskImage() maskImageView.frame = maskView.bounds.offsetBy(dx: 0.0, dy: 36.0) maskView.addSubview(maskImageView) - clippedNode.view.mask = maskView + self.contentGridNode.alpha = 1.0 self.contentGridNode.forEachItemNode { itemNode in - if let snapshotView = itemNode.view.snapshotView(afterScreenUpdates: false) { + if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false) + itemNode.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak clippedNode] _ in + clippedNode?.view.removeFromSuperview() + }) + } else if let snapshotView = itemNode.view.snapshotView(afterScreenUpdates: false) { snapshotView.frame = itemNode.view.convert(itemNode.bounds, to: clippedNode.view) - if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId { - itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false) - itemNode.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak clippedNode] _ in - clippedNode?.view.removeFromSuperview() - }) - } else { - clippedNode.view.addSubview(snapshotView) - - itemNode.alpha = 0.0 - let angle = sourceCenter.angle(to: itemNode.position) - let distance = sourceCenter.distance(to: itemNode.position) - let newDistance = distance * 2.8 - let newPosition = snapshotView.center.offsetBy(distance: newDistance, inDirection: angle) - snapshotView.layer.animatePosition(from: newPosition, to: snapshotView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) - snapshotView.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak itemNode] _ in - itemNode?.alpha = 1.0 - }) - snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false) - } + clippedNode.view.addSubview(snapshotView) + + itemNode.alpha = 0.0 + let angle = targetFrame.center.angle(to: itemNode.position) + let distance = targetFrame.center.distance(to: itemNode.position) + let newDistance = distance * 2.8 + let newPosition = snapshotView.center.offsetBy(distance: newDistance, inDirection: angle) + snapshotView.layer.animatePosition(from: newPosition, to: snapshotView.center, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) + snapshotView.layer.animateScale(from: 1.35, to: 1.0, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak itemNode] _ in + itemNode?.alpha = 1.0 + }) + snapshotView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15, removeOnCompletion: false) } } @@ -451,11 +466,17 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { } } - func animateOut(peerId: EnginePeer.Id) -> CGRect? { + func animateOut(peerId: EnginePeer.Id, scrollDelta: CGFloat) -> CGRect? { + self.headerNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -scrollDelta), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.searchButtonNode.alpha = 0.0 self.searchButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) self.searchButtonNode.layer.animatePosition(from: .zero, to: CGPoint(x: -20.0, y: 0.0), duration: 0.2, additive: true) + self.shareButtonNode.alpha = 0.0 + self.shareButtonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + self.shareButtonNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 0.0), duration: 0.2, additive: true) + self.contentTitleNode.alpha = 0.0 self.contentTitleNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) self.contentTitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, additive: true) @@ -466,14 +487,17 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentSubtitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -10.0), duration: 0.2, additive: true) self.contentSubtitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.3) + self.contentGridNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -scrollDelta), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + if let sourceFrame = self.frameForPeerId(peerId), let (size, bottomInset) = self.validLayout { - let sourceCenter = sourceFrame.center let clippedNode = ASDisplayNode() clippedNode.clipsToBounds = true clippedNode.cornerRadius = 16.0 clippedNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.contentTitleNode.frame.minY - 15.0), size: CGSize(width: size.width, height: size.height - bottomInset)) self.contentGridNode.view.superview?.insertSubview(clippedNode.view, aboveSubview: self.contentGridNode.view) + clippedNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: -scrollDelta), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + let maskView = UIView() maskView.frame = clippedNode.bounds @@ -481,7 +505,6 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { maskImageView.image = generateMaskImage() maskImageView.frame = maskView.bounds.offsetBy(dx: 0.0, dy: 36.0) maskView.addSubview(maskImageView) - clippedNode.view.mask = maskView self.contentGridNode.forEachItemNode { itemNode in @@ -492,8 +515,8 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { if let itemNode = itemNode as? ShareControllerPeerGridItemNode, itemNode.peerId == peerId { } else { - let angle = sourceCenter.angle(to: itemNode.position) - let distance = sourceCenter.distance(to: itemNode.position) + let angle = sourceFrame.center.angle(to: itemNode.position) + let distance = sourceFrame.center.distance(to: itemNode.position) let newDistance = distance * 2.8 let newPosition = snapshotView.center.offsetBy(distance: newDistance, inDirection: angle) snapshotView.layer.animatePosition(from: snapshotView.center, to: newPosition, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) @@ -562,12 +585,15 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { let rawTitleOffset = -titleAreaHeight - presentationLayout.contentOffset.y let titleOffset = max(-titleAreaHeight, rawTitleOffset) + let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: titleOffset), size: CGSize(width: size.width, height: 64.0)) + transition.updateFrame(node: self.headerNode, frame: headerFrame) + let titleSize = self.contentTitleNode.measure(size) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: titleOffset + 15.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 15.0), size: titleSize) transition.updateFrame(node: self.contentTitleNode, frame: titleFrame) let subtitleSize = self.contentSubtitleNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: titleAreaHeight)) - let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleOffset + 40.0), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: 40.0), size: subtitleSize) var originalSubtitleFrame = self.contentSubtitleNode.frame originalSubtitleFrame.origin.x = subtitleFrame.origin.x originalSubtitleFrame.size = subtitleFrame.size @@ -575,19 +601,19 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { transition.updateFrame(node: self.contentSubtitleNode, frame: subtitleFrame) let titleButtonSize = CGSize(width: 44.0, height: 44.0) - let searchButtonFrame = CGRect(origin: CGPoint(x: 12.0, y: titleOffset + 12.0), size: titleButtonSize) + let searchButtonFrame = CGRect(origin: CGPoint(x: 12.0, y: 12.0), size: titleButtonSize) transition.updateFrame(node: self.searchButtonNode, frame: searchButtonFrame) - let shareButtonFrame = CGRect(origin: CGPoint(x: size.width - titleButtonSize.width - 12.0, y: titleOffset + 12.0), size: titleButtonSize) + let shareButtonFrame = CGRect(origin: CGPoint(x: size.width - titleButtonSize.width - 12.0, y: 12.0), size: titleButtonSize) transition.updateFrame(node: self.shareButtonNode, frame: shareButtonFrame) transition.updateFrame(node: self.shareContainerNode, frame: CGRect(origin: CGPoint(), size: titleButtonSize)) transition.updateFrame(node: self.shareReferenceNode, frame: CGRect(origin: CGPoint(), size: titleButtonSize)) let segmentedSize = self.segmentedNode.updateLayout(.sizeToFit(maximumWidth: size.width - titleButtonSize.width * 2.0, minimumWidth: 160.0, height: 32.0), transition: transition) - transition.updateFrame(node: self.segmentedNode, frame: CGRect(origin: CGPoint(x: floor((size.width - segmentedSize.width) / 2.0), y: titleOffset + 18.0), size: segmentedSize)) + transition.updateFrame(node: self.segmentedNode, frame: CGRect(origin: CGPoint(x: floor((size.width - segmentedSize.width) / 2.0), y: 18.0), size: segmentedSize)) let avatarButtonSize = CGSize(width: 36.0, height: 36.0) - let avatarButtonFrame = CGRect(origin: CGPoint(x: size.width - avatarButtonSize.width - 20.0, y: titleOffset + 15.0), size: avatarButtonSize) + let avatarButtonFrame = CGRect(origin: CGPoint(x: size.width - avatarButtonSize.width - 20.0, y: 15.0), size: avatarButtonSize) transition.updateFrame(node: self.contentTitleAccountNode, frame: avatarButtonFrame) transition.updateFrame(node: self.contentSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: titleOffset + titleAreaHeight), size: CGSize(width: size.width, height: UIScreenPixel))) diff --git a/submodules/ShareController/Sources/ShareTopicGridItem.swift b/submodules/ShareController/Sources/ShareTopicGridItem.swift index 20047d4b1c..d795e3b446 100644 --- a/submodules/ShareController/Sources/ShareTopicGridItem.swift +++ b/submodules/ShareController/Sources/ShareTopicGridItem.swift @@ -63,6 +63,8 @@ final class ShareTopicGridItemNode: GridItemNode { override init() { self.iconView = ComponentView() self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 2 + self.textNode.textAlignment = .center super.init() diff --git a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift index 9e5c77cb85..1ab6f849cf 100644 --- a/submodules/ShareController/Sources/ShareTopicsContainerNode.swift +++ b/submodules/ShareController/Sources/ShareTopicsContainerNode.swift @@ -165,7 +165,8 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { private var entries: [ShareTopicEntry] = [] private var enqueuedTransitions: [(ShareGridTransaction, Bool)] = [] - private let contentGridNode: GridNode + let contentGridNode: GridNode + private let headerNode: ASDisplayNode private let contentTitleNode: ASTextNode private let contentSubtitleNode: ASTextNode private let backNode: CancelButtonNode @@ -203,6 +204,7 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { } self.contentGridNode = GridNode() + self.headerNode = ASDisplayNode() self.contentTitleNode = ASTextNode() self.contentTitleNode.attributedText = NSAttributedString(string: peer.compactDisplayTitle, font: Font.medium(20.0), textColor: self.theme.actionSheet.primaryTextColor) @@ -219,10 +221,11 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { super.init() self.addSubnode(self.contentGridNode) + self.addSubnode(self.headerNode) - self.addSubnode(self.contentTitleNode) - self.addSubnode(self.contentSubtitleNode) - self.addSubnode(self.backNode) + self.headerNode.addSubnode(self.contentTitleNode) + self.headerNode.addSubnode(self.contentSubtitleNode) + self.headerNode.addSubnode(self.backNode) let previousItems = Atomic<[ShareTopicEntry]?>(value: []) self.disposable.set((items @@ -304,7 +307,9 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { func deactivate() { } - func animateIn(sourceFrame: CGRect) { + func animateIn(sourceFrame: CGRect, scrollDelta: CGFloat) { + self.headerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: scrollDelta), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backNode.alpha = 1.0 self.backNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) self.backNode.layer.animatePosition(from: CGPoint(x: 20.0, y: 0.0), to: .zero, duration: 0.2, additive: true) @@ -319,6 +324,7 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentSubtitleNode.layer.animatePosition(from: CGPoint(x: 0.0, y: 10.0), to: .zero, duration: 0.2, additive: true) self.contentSubtitleNode.layer.animateScale(from: 0.85, to: 1.0, duration: 0.2) + self.contentGridNode.layer.animatePosition(from: CGPoint(x: 0.0, y: scrollDelta), to: .zero, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) self.contentGridNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.contentGridNode.forEachItemNode { itemNode in @@ -327,7 +333,9 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { } } - func animateOut(targetFrame: CGRect, completion: @escaping () -> Void = {}) { + func animateOut(targetFrame: CGRect, scrollDelta: CGFloat, completion: @escaping () -> Void = {}) { + self.headerNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: scrollDelta), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.backNode.alpha = 0.0 self.backNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) self.backNode.layer.animatePosition(from: .zero, to: CGPoint(x: 20.0, y: 0.0), duration: 0.2, additive: true) @@ -342,13 +350,15 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { self.contentSubtitleNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: 10.0), duration: 0.2, additive: true) self.contentSubtitleNode.layer.animateScale(from: 1.0, to: 0.85, duration: 0.2) + self.contentGridNode.layer.animatePosition(from: .zero, to: CGPoint(x: 0.0, y: scrollDelta), duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + self.contentGridNode.alpha = 0.0 self.contentGridNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { _ in completion() }) self.contentGridNode.forEachItemNode { itemNode in - itemNode.layer.animatePosition(from: itemNode.position, to: targetFrame.center, duration: 0.45, timingFunction: kCAMediaTimingFunctionSpring) + itemNode.layer.animatePosition(from: itemNode.position, to: targetFrame.center, duration: 0.4, timingFunction: kCAMediaTimingFunctionSpring) itemNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring) } } @@ -412,19 +422,22 @@ final class ShareTopicsContainerNode: ASDisplayNode, ShareContentContainerNode { let rawTitleOffset = -titleAreaHeight - presentationLayout.contentOffset.y let titleOffset = max(-titleAreaHeight, rawTitleOffset) + let headerFrame = CGRect(origin: CGPoint(x: 0.0, y: titleOffset), size: CGSize(width: size.width, height: 64.0)) + transition.updateFrame(node: self.headerNode, frame: headerFrame) + let titleSize = self.contentTitleNode.measure(size) - let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: titleOffset + 15.0), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: 15.0), size: titleSize) transition.updateFrame(node: self.contentTitleNode, frame: titleFrame) let subtitleSize = self.contentSubtitleNode.measure(CGSize(width: size.width - 44.0 * 2.0 - 8.0 * 2.0, height: titleAreaHeight)) - let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: titleOffset + 40.0), size: subtitleSize) + let subtitleFrame = CGRect(origin: CGPoint(x: floor((size.width - subtitleSize.width) / 2.0), y: 40.0), size: subtitleSize) var originalSubtitleFrame = self.contentSubtitleNode.frame originalSubtitleFrame.origin.x = subtitleFrame.origin.x originalSubtitleFrame.size = subtitleFrame.size self.contentSubtitleNode.frame = originalSubtitleFrame transition.updateFrame(node: self.contentSubtitleNode, frame: subtitleFrame) - let backFrame = CGRect(origin: CGPoint(x: 30.0, y: titleOffset + 6.0), size: CGSize(width: 90.0, height: 56.0)) + let backFrame = CGRect(origin: CGPoint(x: 30.0, y: 6.0), size: CGSize(width: 90.0, height: 56.0)) transition.updateFrame(node: self.backNode, frame: backFrame) self.contentOffsetUpdated?(presentationLayout.contentOffset.y, actualTransition) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift index 5cb76f4eb6..2fb8ac007f 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/AddressNames.swift @@ -172,8 +172,49 @@ func _internal_updateAddressName(account: Account, domain: AddressNameDomain, na } |> mapError { _ -> UpdateAddressNameError in } |> switchToLatest } +public enum DeactivateAllAddressNamesError { + case generic +} + +func _internal_deactivateAllAddressNames(account: Account, peerId: EnginePeer.Id) -> Signal { + return account.postbox.transaction { transaction -> Signal in + if let peer = transaction.getPeer(peerId), let inputChannel = apiInputChannel(peer) { + return account.network.request(Api.functions.channels.deactivateAllUsernames(channel: inputChannel), automaticFloodWait: false) + |> mapError { _ -> DeactivateAllAddressNamesError in + return .generic + } + |> mapToSignal { result -> Signal in + return account.postbox.transaction { transaction -> Signal in + if case .boolTrue = result, let peer = transaction.getPeer(account.peerId) as? TelegramChannel { + var updatedNames: [TelegramPeerUsername] = [] + for username in peer.usernames { + var updatedFlags = username.flags + updatedFlags.remove(.isActive) + updatedNames.append(TelegramPeerUsername(flags: updatedFlags, username: username.username)) + } + let updatedUser = peer.withUpdatedAddressNames(updatedNames) + updatePeers(transaction: transaction, peers: [updatedUser], update: { _, updated in + return updated + }) + } + return .complete() + } + |> castError(DeactivateAllAddressNamesError.self) + |> switchToLatest + |> ignoreValues + } + } else { + return .never() + } + } + |> mapError { _ -> DeactivateAllAddressNamesError in + } + |> switchToLatest +} + public enum ToggleAddressNameActiveError { case generic + case activeLimitReached } func _internal_toggleAddressNameActive(account: Account, domain: AddressNameDomain, name: String, active: Bool) -> Signal { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index 50671f5fa4..39c7794410 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -47,6 +47,10 @@ public extension TelegramEngine { return _internal_updateAddressName(account: self.account, domain: domain, name: name) } + public func deactivateAllAddressNames(peerId: EnginePeer.Id) -> Signal { + return _internal_deactivateAllAddressNames(account: self.account, peerId: peerId) + } + public func toggleAddressNameActive(domain: AddressNameDomain, name: String, active: Bool) -> Signal { return _internal_toggleAddressNameActive(account: self.account, domain: domain, name: name, active: active) } diff --git a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift index 73e30e027e..e776083d51 100644 --- a/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift +++ b/submodules/TelegramUI/Components/ForumCreateTopicScreen/Sources/ForumCreateTopicScreen.swift @@ -27,6 +27,7 @@ private final class TitleFieldComponent: Component { let iconColor: Int32 let text: String let textUpdated: (String) -> Void + let iconPressed: () -> Void init( context: AccountContext, @@ -36,7 +37,8 @@ private final class TitleFieldComponent: Component { fileId: Int64, iconColor: Int32, text: String, - textUpdated: @escaping (String) -> Void + textUpdated: @escaping (String) -> Void, + iconPressed: @escaping () -> Void ) { self.context = context self.textColor = textColor @@ -46,6 +48,7 @@ private final class TitleFieldComponent: Component { self.iconColor = iconColor self.text = text self.textUpdated = textUpdated + self.iconPressed = iconPressed } static func ==(lhs: TitleFieldComponent, rhs: TitleFieldComponent) -> Bool { @@ -74,33 +77,51 @@ private final class TitleFieldComponent: Component { } final class View: UIView { + private let iconButton: HighlightTrackingButton private let iconView: ComponentView + private let placeholderView: ComponentView private let textField: TextFieldNodeView private var component: TitleFieldComponent? private weak var state: EmptyComponentState? override init(frame: CGRect) { + self.iconButton = HighlightTrackingButton() self.iconView = ComponentView() + self.placeholderView = ComponentView() self.textField = TextFieldNodeView(frame: .zero) super.init(frame: frame) - self.textField.placeholder = "What do you want to discuss?" self.textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged) self.addSubview(self.textField) + self.addSubview(self.iconButton) + + self.iconButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self, let iconView = strongSelf.iconView.view { + if highlighted { + iconView.layer.animateScale(from: 1.0, to: 0.8, duration: 0.25, removeOnCompletion: false) + } else if let presentationLayer = iconView.layer.presentation() { + iconView.layer.animateScale(from: CGFloat((presentationLayer.value(forKeyPath: "transform.scale.y") as? NSNumber)?.floatValue ?? 1.0), to: 1.0, duration: 0.2, removeOnCompletion: false) + } + } + } + self.iconButton.addTarget(self, action: #selector(self.iconButtonPressed), for: .touchUpInside) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - deinit { + @objc func iconButtonPressed() { + self.component?.iconPressed() } @objc func textChanged(_ sender: Any) { - self.component?.textUpdated(self.textField.text ?? "") + let text = self.textField.text ?? "" + self.component?.textUpdated(text) + self.placeholderView.view?.isHidden = !text.isEmpty } func update(component: TitleFieldComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: Transition) -> CGSize { @@ -114,8 +135,31 @@ private final class TitleFieldComponent: Component { let iconContent: EmojiStatusComponent.Content if component.fileId == 0 { iconContent = .topic(title: String(component.text.prefix(1)), color: component.iconColor, size: CGSize(width: 32.0, height: 32.0)) + self.iconButton.isUserInteractionEnabled = true } else { iconContent = .animation(content: .customEmoji(fileId: component.fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: component.placeholderColor, themeColor: component.accentColor, loopMode: .count(2)) + self.iconButton.isUserInteractionEnabled = false + } + + let placeholderSize = self.placeholderView.update( + transition: .easeInOut(duration: 0.2), + component: AnyComponent( + Text( + text: "What do you want to discuss?", + font: Font.regular(17.0), + color: component.placeholderColor + ) + ), + environment: {}, + containerSize: availableSize + ) + + if let placeholderComponentView = self.placeholderView.view { + if placeholderComponentView.superview == nil { + self.insertSubview(placeholderComponentView, at: 0) + } + + placeholderComponentView.frame = CGRect(origin: CGPoint(x: 62.0, y: floorToScreenPixels((availableSize.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize) } let iconSize = self.iconView.update( @@ -134,11 +178,11 @@ private final class TitleFieldComponent: Component { if let iconComponentView = self.iconView.view { if iconComponentView.superview == nil { - self.addSubview(iconComponentView) + self.insertSubview(iconComponentView, at: 0) } iconComponentView.frame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((availableSize.height - iconSize.height) / 2.0)), size: iconSize) - + self.iconButton.frame = iconComponentView.frame.insetBy(dx: -4.0, dy: -4.0) self.textField.becomeFirstResponder() } @@ -443,9 +487,11 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { func updateTitle(_ text: String) { self.title = text self.updated(transition: .immediate) - self.titleUpdated(text) - + self.updateEmojiContent() + } + + func updateEmojiContent() { self.emojiContentDisposable.set(( EmojiPagerContentComponent.emojiInputData( context: self.context, @@ -469,6 +515,18 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { })) } + func switchIcon() { + let colors = ForumCreateTopicScreen.iconColors + if let index = colors.firstIndex(where: { $0 == self.iconColor }) { + let nextIndex = (index + 1) % colors.count + self.iconColor = colors[nextIndex] + } else { + self.iconColor = colors.first ?? 0 + } + self.updated(transition: .immediate) + self.updateEmojiContent() + } + func applyItem(groupId: AnyHashable, item: EmojiPagerContentComponent.Item?) { guard let item = item else { return @@ -486,30 +544,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { } self.updated(transition: .immediate) - self.iconUpdated(self.fileId != 0 ? self.fileId : nil) - - self.emojiContentDisposable.set(( - EmojiPagerContentComponent.emojiInputData( - context: self.context, - animationCache: self.context.animationCache, - animationRenderer: self.context.animationRenderer, - isStandalone: false, - isStatusSelection: false, - isReactionSelection: false, - isTopicIconSelection: true, - topReactionItems: [], - areUnicodeEmojiEnabled: false, - areCustomEmojiEnabled: true, - chatPeerId: self.context.account.peerId, - selectedItems: Set([MediaId(namespace: Namespaces.Media.CloudFile, id: self.fileId)]), - topicTitle: self.title, - topicColor: self.iconColor - ) - |> deliverOnMainQueue).start(next: { [weak self] content in - self?.emojiContent = content - self?.updated(transition: .immediate) - })) + self.updateEmojiContent() } } @@ -599,6 +635,9 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { text: state.title, textUpdated: { [weak state] text in state?.updateTitle(text) + }, + iconPressed: { [weak state] in + state?.switchIcon() } ), availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 44.0), @@ -736,6 +775,8 @@ private final class ForumCreateTopicScreenComponent: CombinedComponent { } public class ForumCreateTopicScreen: ViewControllerComponentContainer { + public static let iconColors: [Int32] = [0x6FB9F0, 0xFFD67E, 0xCB86DB, 0x8EEE98, 0xFF93B2, 0xFB6F5F] + public enum Mode: Equatable { case create case edit(topic: EngineMessageHistoryThread.Info) diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/Contents.json new file mode 100644 index 0000000000..7b46ce32f4 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "linkexpired.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/linkexpired.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/linkexpired.pdf new file mode 100644 index 0000000000..9c817ee031 Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/Unlink.imageset/linkexpired.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 49f4115232..b9aa0152da 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1070,6 +1070,10 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState copyTextWithEntities() } }) + break + } else { + copyTextWithEntities() + break } } } else { diff --git a/submodules/TelegramUI/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Sources/ChatLoadingNode.swift index ae3d3a4fb5..3f76c67250 100644 --- a/submodules/TelegramUI/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Sources/ChatLoadingNode.swift @@ -177,7 +177,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { let bubbleBorderImage = messageBubbleImage(maxCornerRadius: bubbleCorners.mainRadius, minCornerRadius: bubbleCorners.auxiliaryRadius, incoming: true, fillColor: .clear, strokeColor: .red, neighbors: .none, theme: theme.chat, wallpaper: .color(0xffffff), knockout: true, mask: true, extendedEdges: true, onlyOutline: true) var messageContainers: [ChatLoadingPlaceholderMessageContainer] = [] - for _ in 0 ..< 8 { + for _ in 0 ..< 11 { let container = ChatLoadingPlaceholderMessageContainer(bubbleImage: bubbleImage, bubbleBorderImage: bubbleBorderImage) container.setup(maskNode: self.maskNode, borderMaskNode: self.borderMaskNode) messageContainers.append(container) @@ -274,10 +274,6 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { return } - if let bubbleItemNode = listItemNode as? ChatMessageBubbleItemNode, bubbleItemNode.contentNodes.contains(where: { $0 is ChatMessageActionBubbleContentNode }) { - return - } - let messageContainer = self.messageContainers[index] messageContainer.animateWith(listItemNode, delay: delay, transition: transition) @@ -398,6 +394,9 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { CGSize(width: floorToScreenPixels(0.36 * size.width), height: shortHeight), CGSize(width: floorToScreenPixels(0.47 * size.width), height: tallHeight), CGSize(width: floorToScreenPixels(0.57 * size.width), height: tallHeight), + CGSize(width: floorToScreenPixels(0.73 * size.width), height: tallHeight), + CGSize(width: floorToScreenPixels(0.36 * size.width), height: tallHeight), + CGSize(width: floorToScreenPixels(0.57 * size.width), height: shortHeight), ] var offset: CGFloat = 5.0 @@ -407,7 +406,7 @@ final class ChatLoadingPlaceholderNode: ASDisplayNode { } for messageContainer in self.messageContainers { - let messageSize = dimensions[index % 8] + let messageSize = dimensions[index % 11] messageContainer.update(size: size, hasAvatar: self.isGroup, rect: CGRect(origin: CGPoint(x: 0.0, y: size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition) offset += messageSize.height index += 1 diff --git a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift index c4e7adcdea..ded4ba96e5 100644 --- a/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -2319,7 +2319,8 @@ class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { let progress = abs(translation.x) / swipeOffset swipeToReplyNode.updateProgress(progress) - if progress == 1.0 && !self.playedSwipeToReplyHaptic { + if progress > 1.0 - .ulpOfOne && !self.playedSwipeToReplyHaptic { + self.playedSwipeToReplyHaptic = true self.swipeToReplyFeedback?.impact(.heavy) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift index f8cf7b0c6b..91fd93c81e 100644 --- a/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageBubbleItemNode.swift @@ -3963,7 +3963,8 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewItemNode let progress = abs(translation.x) / swipeOffset swipeToReplyNode.updateProgress(progress) - if progress == 1.0 && !self.playedSwipeToReplyHaptic { + if progress > 1.0 - .ulpOfOne && !self.playedSwipeToReplyHaptic { + self.playedSwipeToReplyHaptic = true self.swipeToReplyFeedback?.impact(.heavy) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift index 6b9346f53e..2901480717 100644 --- a/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageInstantVideoItemNode.swift @@ -1031,7 +1031,8 @@ class ChatMessageInstantVideoItemNode: ChatMessageItemView, UIGestureRecognizerD let progress = abs(translation.x) / swipeOffset swipeToReplyNode.updateProgress(progress) - if progress == 1.0 && !self.playedSwipeToReplyHaptic { + if progress > 1.0 - .ulpOfOne && !self.playedSwipeToReplyHaptic { + self.playedSwipeToReplyHaptic = true self.swipeToReplyFeedback?.impact(.heavy) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift index aea7fe6b5f..3f45d85c54 100644 --- a/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageStickerItemNode.swift @@ -1283,7 +1283,8 @@ class ChatMessageStickerItemNode: ChatMessageItemView { let progress = abs(translation.x) / swipeOffset swipeToReplyNode.updateProgress(progress) - if progress == 1.0 && !self.playedSwipeToReplyHaptic { + if progress > 1.0 - .ulpOfOne && !self.playedSwipeToReplyHaptic { + self.playedSwipeToReplyHaptic = true self.swipeToReplyFeedback?.impact(.heavy) } } diff --git a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift b/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift index 582cfc2b4b..590e43752b 100644 --- a/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageSwipeToReplyNode.swift @@ -20,8 +20,9 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { private let foregroundNode: ASImageNode private let maskNode: ASDisplayNode - private let progressLayer: CAShapeLayer - private let fillLayer: CAShapeLayer + private let progressLayer: SimpleShapeLayer + private let fillLayer: SimpleShapeLayer + private let semiFillLayer: SimpleShapeLayer private var absolutePosition: (CGRect, CGSize)? @@ -67,9 +68,10 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { }) self.maskNode = ASDisplayNode() - self.progressLayer = CAShapeLayer() - self.fillLayer = CAShapeLayer() - + self.progressLayer = SimpleShapeLayer() + self.fillLayer = SimpleShapeLayer() + self.semiFillLayer = SimpleShapeLayer() + super.init() self.allowsGroupOpacity = true @@ -78,6 +80,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { self.maskNode.layer.addSublayer(self.progressLayer) self.maskNode.layer.addSublayer(self.fillLayer) + self.maskNode.layer.addSublayer(self.semiFillLayer) self.addSubnode(self.foregroundNode) @@ -95,11 +98,16 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { self.fillLayer.strokeColor = UIColor.white.cgColor self.fillLayer.fillColor = UIColor.clear.cgColor self.fillLayer.isHidden = true + + self.semiFillLayer.fillColor = UIColor(rgb: 0xffffff, alpha: 0.6).cgColor self.maskNode.frame = CGRect(origin: CGPoint(x: 22.0, y: 22.0), size: backgroundFrame.size) self.progressLayer.frame = CGRect(origin: .zero, size: size).insetBy(dx: -20.0, dy: -20.0) self.fillLayer.frame = CGRect(origin: .zero, size: size) + self.semiFillLayer.frame = self.fillLayer.frame + self.semiFillLayer.path = UIBezierPath(ovalIn: self.semiFillLayer.bounds).cgPath + let path = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: size.width / 2.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true) self.progressLayer.path = path.cgPath @@ -154,6 +162,8 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { self.maskNode.alpha = progress } + self.semiFillLayer.opacity = Float(progress) + self.layer.sublayerTransform = CATransform3DMakeScale(scaleProgress, scaleProgress, 1.0) self.foregroundNode.alpha = foregroundProgress @@ -180,7 +190,7 @@ final class ChatMessageSwipeToReplyNode: ASDisplayNode { var path = self.progressLayer.path var targetPath = UIBezierPath(arcCenter: CGPoint(x: self.progressLayer.frame.width / 2.0, y: self.progressLayer.frame.height / 2.0), radius: 35.0, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(-0.5 * .pi + 2.0 * .pi), clockwise: true).cgPath self.progressLayer.path = targetPath - self.progressLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.2) + self.progressLayer.animate(from: path, to: targetPath, keyPath: "path", timingFunction: kCAMediaTimingFunctionSpring, duration: 0.25) self.fillLayer.isHidden = false self.fillLayer.path = UIBezierPath(ovalIn: CGRect(origin: .zero, size: size)).cgPath