diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 2f7a3e9983..eb9dace69e 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -1200,7 +1200,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController, let location = CGRect(origin: CGPoint(x: absoluteFrame.midX, y: absoluteFrame.minY - 8.0), size: CGSize()) - parentController.present(TooltipScreen(text: text, icon: .chatListPress, location: location, shouldDismissOnTouch: { point in + parentController.present(TooltipScreen(text: text, icon: .chatListPress, location: .point(location), shouldDismissOnTouch: { point in guard let strongSelf = self, let parentController = strongSelf.parent as? TabBarController else { return .dismiss(consume: false) } diff --git a/submodules/Display/Source/ContainerViewLayout.swift b/submodules/Display/Source/ContainerViewLayout.swift index e9ce3089db..ffb4393b08 100644 --- a/submodules/Display/Source/ContainerViewLayout.swift +++ b/submodules/Display/Source/ContainerViewLayout.swift @@ -84,7 +84,7 @@ public extension ContainerViewLayout { func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets { var insets = self.intrinsicInsets if let statusBarHeight = self.statusBarHeight, options.contains(.statusBar) { - insets.top += statusBarHeight + insets.top = max(statusBarHeight, insets.top) } if let inputHeight = self.inputHeight, options.contains(.input) { insets.bottom = max(inputHeight, insets.bottom) diff --git a/submodules/Display/Source/PresentationContext.swift b/submodules/Display/Source/PresentationContext.swift index 8c47306966..b396a44582 100644 --- a/submodules/Display/Source/PresentationContext.swift +++ b/submodules/Display/Source/PresentationContext.swift @@ -56,7 +56,7 @@ public final class PresentationContext { private var presentationDisposables = DisposableSet() - var topLevelSubview: () -> UIView? = { nil } + public var topLevelSubview: () -> UIView? = { nil } var isCurrentlyOpaque: Bool { for (controller, _) in self.controllers { diff --git a/submodules/TelegramCore/Sources/ManagedSynchronizeInstalledStickerPacksOperations.swift b/submodules/TelegramCore/Sources/ManagedSynchronizeInstalledStickerPacksOperations.swift index e57bdb862f..e98bf1384d 100644 --- a/submodules/TelegramCore/Sources/ManagedSynchronizeInstalledStickerPacksOperations.swift +++ b/submodules/TelegramCore/Sources/ManagedSynchronizeInstalledStickerPacksOperations.swift @@ -548,16 +548,6 @@ private func continueSynchronizeInstalledStickerPacks(transaction: Transaction, for info in resultingCollectionInfos { resultingInfos[info.0.id] = info } - for (id, info) in resultingInfos { - if info.0.shortName.lowercased() == "thevirus" { - print("id1 = \(id)") - } - } - for (id, info) in localInfos { - if info.shortName.lowercased() == "thevirus" { - print("id1 = \(id)") - } - } let resolvedItems = resolveStickerPacks(network: network, remoteInfos: resultingInfos, localInfos: localInfos) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 11c62f9621..9fb606b756 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -373,6 +373,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } super.init(context: context, navigationBarPresentationData: navigationBarPresentationData, mediaAccessoryPanelVisibility: mediaAccessoryPanelVisibility, locationBroadcastPanelSource: locationBroadcastPanelSource) + self.automaticallyControlPresentationContextLayout = false self.blocksBackgroundWhenInOverlay = true self.navigationItem.backBarButtonItem = UIBarButtonItem(title: self.presentationData.strings.Common_Back, style: .plain, target: nil, action: nil) @@ -1950,7 +1951,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf = self else { return } - var foundItemNode: ListViewItemNode? + + /*var foundItemNode: ListViewItemNode? strongSelf.chatDisplayNode.historyNode.forEachItemNode { itemNode in if let itemNode = itemNode as? ChatMessageItemView { if sourceNode.view.isDescendant(of: itemNode.view) { @@ -1959,6 +1961,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } if let foundItemNode = foundItemNode { + let absoluteFrame = sourceNode.view.convert(sourceNode.bounds, to: strongSelf.view).insetBy(dx: 0.0, dy: -4.0).offsetBy(dx: 0.0, dy: 0.0) let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: nil, location: absoluteFrame, shouldDismissOnTouch: { point in return .dismiss(consume: absoluteFrame.contains(point)) @@ -2012,7 +2015,82 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode)) strongSelf.present(tooltipScreen, in: .current) + }*/ + + var found = false + strongSelf.forEachController({ controller in + if let controller = controller as? TooltipScreen { + if controller.text == solution.text && controller.textEntities == solution.entities { + found = true + return false + } + } + return true + }) + if found { + return } + + let tooltipScreen = TooltipScreen(text: solution.text, textEntities: solution.entities, icon: .info, location: .top, shouldDismissOnTouch: { point in + return .ignore + }, openActiveTextItem: { item, action in + guard let strongSelf = self else { + return + } + switch item { + case let .url(url): + switch action { + case .tap: + strongSelf.openUrl(url, concealed: false) + case .longTap: + strongSelf.controllerInteraction?.longTap(.url(url), nil) + } + case let .mention(peerId, mention): + switch action { + case .tap: + strongSelf.controllerInteraction?.openPeer(peerId, .default, nil) + case .longTap: + strongSelf.controllerInteraction?.longTap(.peerMention(peerId, mention), nil) + } + case let .textMention(mention): + switch action { + case .tap: + strongSelf.controllerInteraction?.openPeerMention(mention) + case .longTap: + strongSelf.controllerInteraction?.longTap(.mention(mention), nil) + } + case let .botCommand(command): + switch action { + case .tap: + strongSelf.controllerInteraction?.sendBotCommand(nil, command) + case .longTap: + strongSelf.controllerInteraction?.longTap(.command(command), nil) + } + case let .hashtag(hashtag): + switch action { + case .tap: + strongSelf.controllerInteraction?.openHashtag(nil, hashtag) + case .longTap: + strongSelf.controllerInteraction?.longTap(.hashtag(hashtag), nil) + } + } + }) + /*tooltipScreen.becameDismissed = { tooltipScreen in + guard let strongSelf = self else { + return + } + strongSelf.currentMessageTooltipScreens.removeAll(where: { $0.0 === tooltipScreen }) + } + strongSelf.currentMessageTooltipScreens.append((tooltipScreen, foundItemNode))*/ + + strongSelf.forEachController({ controller in + if let controller = controller as? TooltipScreen { + controller.dismiss() + } + return true + }) + + strongSelf.present(tooltipScreen, in: .current) }, requestMessageUpdate: { [weak self] id in if let strongSelf = self { strongSelf.chatDisplayNode.historyNode.requestMessageUpdate(id) diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index aa9a01fdf9..1222f398cc 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -247,6 +247,13 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { super.init() + self.controller?.presentationContext.topLevelSubview = { [weak self] in + guard let strongSelf = self else { + return nil + } + return strongSelf.titleAccessoryPanelContainer.view + } + self.setViewBlock({ return ChatControllerNodeView() }) @@ -1087,6 +1094,10 @@ class ChatControllerNode: ASDisplayNode, UIScrollViewDelegate { listInsets.top = listInsets.top + messageActionSheetControllerAdditionalInset } + var childredLayout = layout + childredLayout.intrinsicInsets = UIEdgeInsets(top: listInsets.bottom, left: listInsets.right, bottom: listInsets.top, right: listInsets.left) + self.controller?.presentationContext.containerLayoutUpdated(childredLayout, transition: transition) + listViewTransaction(ListViewUpdateSizeAndInsets(size: contentBounds.size, insets: listInsets, scrollIndicatorInsets: listScrollIndicatorInsets, duration: duration, curve: curve, ensureTopInsetForOverlayHighlightedItems: ensureTopInsetForOverlayHighlightedItems), additionalScrollDistance, scrollToTop, { [weak self] in if let strongSelf = self { strongSelf.notifyTransitionCompletionListeners(transition: transition) diff --git a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift index af3f4f36f9..50d89edb3d 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputNode.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputNode.swift @@ -1579,10 +1579,14 @@ final class ChatMediaInputNode: ChatInputNode { placeholderNode = self.gifPane.searchPlaceholderNode paneIsEmpty = self.gifPane.isEmpty case .sticker: + paneIsEmpty = true self.stickerPane.gridNode.forEachItemNode { itemNode in if let itemNode = itemNode as? PaneSearchBarPlaceholderNode { placeholderNode = itemNode } + if let _ = itemNode as? ChatMediaInputStickerGridItemNode { + paneIsEmpty = false + } } case .trending: /*self.trendingPane.gridNode.forEachItemNode { itemNode in diff --git a/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift b/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift index f53007f746..d0597f9236 100644 --- a/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift +++ b/submodules/TelegramUI/Sources/ChatMediaInputTrendingPane.swift @@ -87,7 +87,7 @@ final class TrendingPanePackEntry: Identifiable, Comparable { func item(account: Account, interaction: TrendingPaneInteraction, grid: Bool) -> GridItem { let info = self.info - return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: { + return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: false, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: { interaction.openPack(info) }, install: { interaction.installPack(info) diff --git a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift index c8ef9cb01f..faf858738b 100644 --- a/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessagePollBubbleContentNode.swift @@ -1009,6 +1009,10 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let message = item.message let incoming = item.message.effectivelyIncoming(item.context.account.peerId) + var isBotChat: Bool = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramUser, peer.botInfo != nil { + isBotChat = true + } let additionalTextRightInset: CGFloat = 24.0 @@ -1120,8 +1124,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } let (typeLayout, typeApply) = makeTypeLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: typeText, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let votersString: String - if let poll = poll, let totalVoters = poll.results.totalVoters { + let votersString: String? + + if isBotChat { + votersString = nil + } else if let poll = poll, let totalVoters = poll.results.totalVoters { switch poll.kind { case .poll: if totalVoters == 0 { @@ -1139,7 +1146,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else { votersString = " " } - let (votersLayout, votersApply) = makeVotersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: votersString, font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) + let (votersLayout, votersApply) = makeVotersLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: votersString ?? "", font: labelsFont, textColor: messageTheme.secondaryTextColor), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let (buttonSubmitInactiveTextLayout, buttonSubmitInactiveTextApply) = makeSubmitInactiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.accentControlDisabledColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) let (buttonSubmitActiveTextLayout, buttonSubmitActiveTextApply) = makeSubmitActiveTextLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.presentationData.strings.MessagePoll_SubmitVote, font: Font.regular(17.0), textColor: messageTheme.polls.bar), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: textConstrainedSize, alignment: .natural, cutout: nil, insets: textInsets)) @@ -1274,7 +1281,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { let optionsVotersSpacing: CGFloat = 11.0 let optionsButtonSpacing: CGFloat = 9.0 let votersBottomSpacing: CGFloat = 11.0 - resultSize.height += optionsVotersSpacing + votersLayout.size.height + votersBottomSpacing + if votersString != nil { + resultSize.height += optionsVotersSpacing + votersLayout.size.height + votersBottomSpacing + } else { + resultSize.height += 26.0 + } let buttonSubmitInactiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitInactiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitInactiveTextLayout.size) let buttonSubmitActiveTextFrame = CGRect(origin: CGPoint(x: floor((resultSize.width - buttonSubmitActiveTextLayout.size.width) / 2.0), y: optionsButtonSpacing), size: buttonSubmitActiveTextLayout.size) @@ -1437,7 +1448,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.timerNode = timerNode strongSelf.addSubnode(timerNode) timerNode.reachedTimeout = { - guard let strongSelf = self, let item = strongSelf.item else { + guard let strongSelf = self, let _ = strongSelf.item else { return } //item.controllerInteraction.requestMessageUpdate(item.message.id) @@ -1502,6 +1513,7 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { strongSelf.avatarsNode.frame = avatarsFrame strongSelf.avatarsNode.updateLayout(size: avatarsFrame.size) strongSelf.avatarsNode.update(context: item.context, peers: avatarPeers, synchronousLoad: synchronousLoad) + strongSelf.avatarsNode.isHidden = isBotChat let alphaTransition: ContainedViewLayoutTransition if animation.isAnimated { alphaTransition = .animated(duration: 0.25, curve: .easeInOut) @@ -1542,6 +1554,11 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { return } + var isBotChat: Bool = false + if let peer = item.message.peers[item.message.id.peerId] as? TelegramUser, peer.botInfo != nil { + isBotChat = true + } + let disableAllActions = false var hasSelection = false @@ -1593,8 +1610,14 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { } else { if case .public = poll.publicity, hasResults, !disableAllActions { self.votersNode.isHidden = true - self.buttonViewResultsTextNode.isHidden = false - self.buttonNode.isHidden = false + + if isBotChat { + self.buttonViewResultsTextNode.isHidden = true + self.buttonNode.isHidden = true + } else { + self.buttonViewResultsTextNode.isHidden = false + self.buttonNode.isHidden = false + } if Namespaces.Message.allScheduled.contains(item.message.id.namespace) { self.buttonNode.isUserInteractionEnabled = false @@ -1650,11 +1673,16 @@ class ChatMessagePollBubbleContentNode: ChatMessageBubbleContentNode { return .none } } else { + var isBotChat: Bool = false + if let item = self.item, let peer = item.message.peers[item.message.id.peerId] as? TelegramUser, peer.botInfo != nil { + isBotChat = true + } + for optionNode in self.optionNodes { if optionNode.frame.contains(point), case .tap = gesture { if optionNode.isUserInteractionEnabled { return .ignore - } else if let result = optionNode.currentResult, let item = self.item, !Namespaces.Message.allScheduled.contains(item.message.id.namespace), let poll = self.poll, let option = optionNode.option { + } else if let result = optionNode.currentResult, let item = self.item, !Namespaces.Message.allScheduled.contains(item.message.id.namespace), let poll = self.poll, let option = optionNode.option, !isBotChat { switch poll.publicity { case .anonymous: let string: String diff --git a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift index 73a2b2a29c..e3aa071f45 100644 --- a/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift +++ b/submodules/TelegramUI/Sources/FeaturedStickersScreen.swift @@ -89,7 +89,7 @@ private final class FeaturedPackEntry: Identifiable, Comparable { func item(account: Account, interaction: FeaturedInteraction, grid: Bool) -> GridItem { let info = self.info - return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: { + return StickerPaneSearchGlobalItem(account: account, theme: self.theme, strings: self.strings, listAppearance: true, info: self.info, topItems: self.topItems, grid: grid, topSeparator: self.topSeparator, installed: self.installed, unread: self.unread, open: { interaction.openPack(info) }, install: { interaction.installPack(info, !self.installed) @@ -303,6 +303,9 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { self.searchNode?.updateActivity = { [weak self] activity in self?.controller?.searchNavigationNode?.setActivity(activity) } + self.searchNode?.deactivateSearchBar = { [weak self] in + self?.view.endEditing(true) + } let interaction = FeaturedInteraction( installPack: { [weak self] info, install in @@ -478,6 +481,15 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { self.loadMoreDisposable.dispose() } + func updatePresentationData(presentationData: PresentationData) { + self.presentationData = presentationData + + self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + + self.searchNode?.updateThemeAndStrings(theme: self.presentationData.theme, strings: self.presentationData.strings) + + } + private func loadMore() { if self.isLoadingMore || !self.canLoadMore { return @@ -506,6 +518,8 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { override func didLoad() { super.didLoad() + self.view.disablesInteractiveTransitionGestureRecognizer = true + self.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point in guard let strongSelf = self else { return nil @@ -667,7 +681,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { if let searchNode = self.searchNode { let searchNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top)) transition.updateFrame(node: searchNode, frame: searchNodeFrame) - searchNode.updateLayout(size: searchNodeFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom, inputHeight: layout.inputHeight ?? 0.0, deviceMetrics: layout.deviceMetrics, transition: transition) + searchNode.updateLayout(size: searchNodeFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: insets.bottom + layout.safeInsets.bottom, inputHeight: layout.inputHeight ?? 0.0, deviceMetrics: layout.deviceMetrics, transition: transition) } let itemSize: CGSize @@ -677,7 +691,7 @@ private final class FeaturedStickersScreenNode: ViewControllerTracingNode { itemSize = CGSize(width: layout.size.width, height: 128.0) } - self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: insets.bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: layout.size, insets: UIEdgeInsets(top: insets.top, left: layout.safeInsets.left, bottom: insets.bottom + layout.safeInsets.bottom, right: layout.safeInsets.right), preloadSize: 300.0, type: .fixed(itemSize: itemSize, fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) transition.updateFrame(node: self.gridNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height))) @@ -751,6 +765,7 @@ final class FeaturedStickersScreen: ViewController { } private var presentationData: PresentationData + private var presentationDataDisposable: Disposable? private let _ready = Promise() override public var ready: Promise { @@ -777,12 +792,36 @@ final class FeaturedStickersScreen: ViewController { self.searchNavigationNode = searchNavigationNode self.navigationBar?.setContentNode(searchNavigationNode, animated: false) + + self.presentationDataDisposable = (context.sharedContext.presentationData + |> deliverOnMainQueue).start(next: { [weak self] presentationData in + if let strongSelf = self { + let previous = strongSelf.presentationData + strongSelf.presentationData = presentationData + + if previous.theme !== presentationData.theme || previous.strings !== presentationData.strings { + strongSelf.updatePresentationData() + } + } + }) } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + self.presentationDataDisposable?.dispose() + } + + private func updatePresentationData() { + self.statusBar.statusBarStyle = self.presentationData.theme.rootController.statusBarStyle.style + + self.searchNavigationNode?.updatePresentationData(theme: self.presentationData.theme, strings: self.presentationData.strings) + + self.controllerNode.updatePresentationData(presentationData: presentationData) + } + override public func loadDisplayNode() { self.displayNode = FeaturedStickersScreenNode( context: self.context, @@ -816,8 +855,8 @@ final class FeaturedStickersScreen: ViewController { } private final class SearchNavigationContentNode: NavigationBarContentNode { - private let theme: PresentationTheme - private let strings: PresentationStrings + private var theme: PresentationTheme + private var strings: PresentationStrings private let cancel: () -> Void @@ -851,6 +890,13 @@ private final class SearchNavigationContentNode: NavigationBarContentNode { } } + func updatePresentationData(theme: PresentationTheme, strings: PresentationStrings) { + self.theme = theme + self.strings = strings + + self.searchBar.updateThemeAndStrings(theme: SearchBarNodeTheme(theme: theme), strings: strings) + } + func setQueryUpdated(_ f: @escaping (String, String?) -> Void) { self.queryUpdated = f } @@ -951,7 +997,7 @@ private enum FeaturedSearchEntry: Identifiable, Comparable { interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) }) case let .global(_, info, topItems, installed, topSeparator): - return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: { + return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: { interaction.open(info) }, install: { interaction.install(info, topItems, !installed) diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift b/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift index 5d043d2bc7..6590a59553 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchContentNode.swift @@ -104,7 +104,7 @@ private enum StickerSearchEntry: Identifiable, Comparable { interaction.sendSticker(.standalone(media: stickerItem.file), node, rect) }) case let .global(_, info, topItems, installed, topSeparator): - return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: { + return StickerPaneSearchGlobalItem(account: account, theme: theme, strings: strings, listAppearance: false, info: info, topItems: topItems, grid: false, topSeparator: topSeparator, installed: installed, unread: false, open: { interaction.open(info) }, install: { interaction.install(info, topItems, !installed) diff --git a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift index d37c75ba0b..d6560aae90 100644 --- a/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift +++ b/submodules/TelegramUI/Sources/StickerPaneSearchGlobaltem.swift @@ -37,6 +37,7 @@ final class StickerPaneSearchGlobalItem: GridItem { let account: Account let theme: PresentationTheme let strings: PresentationStrings + let listAppearance: Bool let info: StickerPackCollectionInfo let topItems: [StickerPackItem] let grid: Bool @@ -53,10 +54,11 @@ final class StickerPaneSearchGlobalItem: GridItem { return self.grid ? nil : (128.0 + (self.topSeparator ? 12.0 : 0.0)) } - init(account: Account, theme: PresentationTheme, strings: PresentationStrings, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { + init(account: Account, theme: PresentationTheme, strings: PresentationStrings, listAppearance: Bool, info: StickerPackCollectionInfo, topItems: [StickerPackItem], grid: Bool, topSeparator: Bool, installed: Bool, installing: Bool = false, unread: Bool, open: @escaping () -> Void, install: @escaping () -> Void, getItemIsPreviewed: @escaping (StickerPackItem) -> Bool) { self.account = account self.theme = theme self.strings = strings + self.listAppearance = listAppearance self.info = info self.topItems = topItems self.grid = grid @@ -95,6 +97,9 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { private let installTextNode: TextNode private let installBackgroundNode: ASImageNode private let installButtonNode: HighlightTrackingButtonNode + private let uninstallTextNode: TextNode + private let uninstallBackgroundNode: ASImageNode + private let uninstallButtonNode: HighlightTrackingButtonNode private var itemNodes: [TrendingTopItemNode] private let topSeparatorNode: ASDisplayNode @@ -149,6 +154,18 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.installButtonNode = HighlightTrackingButtonNode() + self.uninstallTextNode = TextNode() + self.uninstallTextNode.isUserInteractionEnabled = false + self.uninstallTextNode.contentMode = .left + self.uninstallTextNode.contentsScale = UIScreen.main.scale + + self.uninstallBackgroundNode = ASImageNode() + self.uninstallBackgroundNode.isLayerBacked = true + self.uninstallBackgroundNode.displayWithoutProcessing = true + self.uninstallBackgroundNode.displaysAsynchronously = false + + self.uninstallButtonNode = HighlightTrackingButtonNode() + self.topSeparatorNode = ASDisplayNode() self.topSeparatorNode.isLayerBacked = true @@ -162,6 +179,9 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.addSubnode(self.installBackgroundNode) self.addSubnode(self.installTextNode) self.addSubnode(self.installButtonNode) + self.addSubnode(self.uninstallBackgroundNode) + self.addSubnode(self.uninstallTextNode) + self.addSubnode(self.uninstallButtonNode) self.addSubnode(self.topSeparatorNode) self.installButtonNode.highligthedChanged = { [weak self] highlighted in @@ -179,8 +199,24 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { } } } - self.installButtonNode.addTarget(self, action: #selector(self.installPressed), forControlEvents: .touchUpInside) + + self.uninstallButtonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.uninstallBackgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.uninstallBackgroundNode.alpha = 0.4 + strongSelf.uninstallTextNode.layer.removeAnimation(forKey: "opacity") + strongSelf.uninstallTextNode.alpha = 0.4 + } else { + strongSelf.uninstallBackgroundNode.alpha = 1.0 + strongSelf.uninstallBackgroundNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.uninstallTextNode.alpha = 1.0 + strongSelf.uninstallTextNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.uninstallButtonNode.addTarget(self, action: #selector(self.installPressed), forControlEvents: .touchUpInside) } deinit { @@ -219,9 +255,14 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.topSeparatorNode.isHidden = !item.topSeparator self.topSeparatorNode.frame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: CGSize(width: params.width - 16.0 * 2.0, height: UIScreenPixel)) - self.topSeparatorNode.backgroundColor = item.theme.chat.inputMediaPanel.stickersSectionTextColor.withAlphaComponent(0.3) + if item.listAppearance { + self.topSeparatorNode.backgroundColor = item.theme.list.itemPlainSeparatorColor + } else { + self.topSeparatorNode.backgroundColor = item.theme.chat.inputMediaPanel.stickersSectionTextColor.withAlphaComponent(0.3) + } let makeInstallLayout = TextNode.asyncLayout(self.installTextNode) + let makeUninstallLayout = TextNode.asyncLayout(self.uninstallTextNode) let makeTitleLayout = TextNode.asyncLayout(self.titleNode) let makeDescriptionLayout = TextNode.asyncLayout(self.descriptionNode) @@ -229,43 +270,57 @@ class StickerPaneSearchGlobalItemNode: GridItemNode { self.appliedItem = item var updateButtonBackgroundImage: UIImage? - if currentItem?.theme !== item.theme || currentItem?.installed != item.installed { - if item.installed { - updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddedPackButtonImage(item.theme) - } else { - updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddPackButtonImage(item.theme) - } + var updateUninstallButtonBackgroundImage: UIImage? + if currentItem?.theme !== item.theme { + updateUninstallButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddedPackButtonImage(item.theme) + updateButtonBackgroundImage = PresentationResourcesChat.chatInputMediaPanelAddPackButtonImage(item.theme) } let unreadImage = PresentationResourcesItemList.stickerUnreadDotImage(item.theme) let leftInset: CGFloat = 14.0 let rightInset: CGFloat = 16.0 - let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.installed ? item.strings.Stickers_Installed : item.strings.Stickers_Install, font: buttonFont, textColor: item.installed ? item.theme.list.itemCheckColors.fillColor : item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (installLayout, installApply) = makeInstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_Install, font: buttonFont, textColor: item.theme.list.itemCheckColors.foregroundColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) - let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.info.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - installLayout.size.width, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + let (uninstallLayout, uninstallApply) = makeUninstallLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.Stickers_Installed, font: buttonFont, textColor: item.theme.list.itemCheckColors.fillColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) + + let (titleLayout, titleApply) = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.info.title, font: titleFont, textColor: item.theme.list.itemPrimaryTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0 - max(installLayout.size.width, uninstallLayout.size.width), height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let (descriptionLayout, descriptionApply) = makeDescriptionLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: item.strings.StickerPack_StickerCount(item.info.count), font: statusFont, textColor: item.theme.chat.inputMediaPanel.stickersSectionTextColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: params.width - params.leftInset - params.rightInset - leftInset - rightInset - 20.0, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets())) let strongSelf = self let _ = installApply() + let _ = uninstallApply() let _ = titleApply() let _ = descriptionApply() if let updateButtonBackgroundImage = updateButtonBackgroundImage { strongSelf.installBackgroundNode.image = updateButtonBackgroundImage } + if let updateUninstallButtonBackgroundImage = updateUninstallButtonBackgroundImage { + strongSelf.uninstallBackgroundNode.image = updateUninstallButtonBackgroundImage + } let installWidth: CGFloat = installLayout.size.width + 32.0 let buttonFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - installWidth, y: 4.0 + topOffset), size: CGSize(width: installWidth, height: 28.0)) strongSelf.installBackgroundNode.frame = buttonFrame strongSelf.installTextNode.frame = CGRect(origin: CGPoint(x: buttonFrame.minX + floor((buttonFrame.width - installLayout.size.width) / 2.0), y: buttonFrame.minY + floor((buttonFrame.height - installLayout.size.height) / 2.0) + 1.0), size: installLayout.size) strongSelf.installButtonNode.frame = buttonFrame + + let uninstallWidth: CGFloat = uninstallLayout.size.width + 32.0 + let uninstallButtonFrame = CGRect(origin: CGPoint(x: params.width - params.rightInset - rightInset - uninstallWidth, y: 4.0 + topOffset), size: CGSize(width: uninstallWidth, height: 28.0)) + strongSelf.uninstallBackgroundNode.frame = uninstallButtonFrame + strongSelf.uninstallTextNode.frame = CGRect(origin: CGPoint(x: uninstallButtonFrame.minX + floor((uninstallButtonFrame.width - uninstallLayout.size.width) / 2.0), y: uninstallButtonFrame.minY + floor((uninstallButtonFrame.height - uninstallLayout.size.height) / 2.0) + 1.0), size: uninstallLayout.size) + strongSelf.uninstallButtonNode.frame = uninstallButtonFrame - strongSelf.installButtonNode.isHidden = false - strongSelf.installBackgroundNode.isHidden = false - strongSelf.installTextNode.isHidden = false + strongSelf.installButtonNode.isHidden = item.installed + strongSelf.installBackgroundNode.isHidden = item.installed + strongSelf.installTextNode.isHidden = item.installed + + strongSelf.uninstallButtonNode.isHidden = !item.installed + strongSelf.uninstallBackgroundNode.isHidden = !item.installed + strongSelf.uninstallTextNode.isHidden = !item.installed let titleFrame = CGRect(origin: CGPoint(x: params.leftInset + leftInset, y: 2.0 + topOffset), size: titleLayout.size) strongSelf.titleNode.frame = titleFrame diff --git a/submodules/TooltipUI/Sources/TooltipScreen.swift b/submodules/TooltipUI/Sources/TooltipScreen.swift index e018933882..455b2c07b3 100644 --- a/submodules/TooltipUI/Sources/TooltipScreen.swift +++ b/submodules/TooltipUI/Sources/TooltipScreen.swift @@ -25,7 +25,7 @@ public enum TooltipActiveTextAction { private final class TooltipScreenNode: ViewControllerTracingNode { private let icon: TooltipScreen.Icon? - private let location: CGRect + private let location: TooltipScreen.Location private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch private let requestDismiss: () -> Void @@ -41,7 +41,7 @@ private final class TooltipScreenNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? - init(text: String, textEntities: [MessageTextEntity], icon: TooltipScreen.Icon?, location: CGRect, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void) { + init(text: String, textEntities: [MessageTextEntity], icon: TooltipScreen.Icon?, location: TooltipScreen.Location, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, requestDismiss: @escaping () -> Void, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void) { self.icon = icon self.location = location self.shouldDismissOnTouch = shouldDismissOnTouch @@ -184,27 +184,35 @@ private final class TooltipScreenNode: ViewControllerTracingNode { let textSize = self.textNode.updateLayout(CGSize(width: containerWidth - contentInset * 2.0 - animationSize.width - animationSpacing, height: .greatestFiniteMagnitude)) - let backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing + var backgroundFrame: CGRect + let backgroundHeight = max(animationSize.height, textSize.height) + contentVerticalInset * 2.0 - var backgroundFrame = CGRect(origin: CGPoint(x: self.location.midX - backgroundWidth / 2.0, y: self.location.minY - bottomInset - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight)) - if backgroundFrame.minX < sideInset { - backgroundFrame.origin.x = sideInset - } - if backgroundFrame.maxX > layout.size.width - sideInset { - backgroundFrame.origin.x = layout.size.width - sideInset - backgroundFrame.width - } + var invertArrow = false - if backgroundFrame.minY < layout.insets(options: .statusBar).top { - backgroundFrame.origin.y = self.location.maxY + bottomInset - invertArrow = true + switch self.location { + case let .point(rect): + let backgroundWidth = textSize.width + contentInset * 2.0 + animationSize.width + animationSpacing + backgroundFrame = CGRect(origin: CGPoint(x: rect.midX - backgroundWidth / 2.0, y: rect.minY - bottomInset - backgroundHeight), size: CGSize(width: backgroundWidth, height: backgroundHeight)) + if backgroundFrame.minX < sideInset { + backgroundFrame.origin.x = sideInset + } + if backgroundFrame.maxX > layout.size.width - sideInset { + backgroundFrame.origin.x = layout.size.width - sideInset - backgroundFrame.width + } + if backgroundFrame.minY < layout.insets(options: .statusBar).top { + backgroundFrame.origin.y = rect.maxY + bottomInset + invertArrow = true + } + self.isArrowInverted = invertArrow + case .top: + backgroundFrame = CGRect(origin: CGPoint(x: sideInset, y: layout.insets(options: [.statusBar]).top + 13.0), size: CGSize(width: layout.size.width - sideInset * 2.0, height: backgroundHeight)) } - self.isArrowInverted = invertArrow transition.updateFrame(node: self.containerNode, frame: backgroundFrame) transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: backgroundFrame.size)) - if let image = self.arrowNode.image { + if let image = self.arrowNode.image, case let .point(rect) = self.location { let arrowSize = image.size - let arrowCenterX = self.location.midX + let arrowCenterX = rect.midX let arrowFrame: CGRect @@ -219,11 +227,13 @@ private final class TooltipScreenNode: ViewControllerTracingNode { ContainedViewLayoutTransition.immediate.updateTransformScale(node: self.arrowContainer, scale: CGPoint(x: 1.0, y: invertArrow ? -1.0 : 1.0)) self.arrowNode.frame = CGRect(origin: CGPoint(), size: arrowFrame.size) + } else { + self.arrowNode.isHidden = true } transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: contentInset + animationSize.width + animationSpacing, y: floor((backgroundHeight - textSize.height) / 2.0)), size: textSize)) - transition.updateFrame(node: self.animatedStickerNode, frame: CGRect(origin: CGPoint(x: contentInset - animationInset, y: floor((backgroundHeight - animationSize.height) / 2.0) - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))) + transition.updateFrame(node: self.animatedStickerNode, frame: CGRect(origin: CGPoint(x: contentInset - animationInset, y: contentVerticalInset - animationInset), size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0))) self.animatedStickerNode.updateLayout(size: CGSize(width: animationSize.width + animationInset * 2.0, height: animationSize.height + animationInset * 2.0)) } @@ -254,10 +264,19 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } func animateIn() { - self.containerNode.layer.animateSpring(from: NSNumber(value: Float(0.01)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.4, damping: 105.0) - let arrowY: CGFloat = self.isArrowInverted ? self.arrowContainer.frame.minY : self.arrowContainer.frame.maxY - self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, damping: 105.0, additive: true) - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + switch self.location { + case .top: + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.containerNode.layer.animateScale(from: 0.96, to: 1.0, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + if let _ = self.validLayout { + self.containerNode.layer.animatePosition(from: CGPoint(x: 0.0, y: -13.0 - self.backgroundNode.frame.height), to: CGPoint(), duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + case .point: + self.containerNode.layer.animateSpring(from: NSNumber(value: Float(0.01)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.4, damping: 105.0) + let arrowY: CGFloat = self.isArrowInverted ? self.arrowContainer.frame.minY : self.arrowContainer.frame.maxY + self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, damping: 105.0, additive: true) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } let animationDelay: Double switch self.icon { @@ -275,13 +294,24 @@ private final class TooltipScreenNode: ViewControllerTracingNode { } func animateOut(completion: @escaping () -> Void) { - self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in - completion() - }) - self.containerNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) - - let arrowY: CGFloat = self.isArrowInverted ? self.arrowContainer.frame.minY : self.arrowContainer.frame.maxY - self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0), duration: 0.2, removeOnCompletion: false, additive: true) + switch self.location { + case .top: + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + completion() + }) + self.containerNode.layer.animateScale(from: 1.0, to: 0.96, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false) + if let _ = self.validLayout { + self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -13.0 - self.backgroundNode.frame.height), duration: 0.3, removeOnCompletion: false, additive: true) + } + case .point: + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completion() + }) + self.containerNode.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + + let arrowY: CGFloat = self.isArrowInverted ? self.arrowContainer.frame.minY : self.arrowContainer.frame.maxY + self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: self.arrowContainer.frame.midX - self.containerNode.bounds.width / 2.0, y: arrowY - self.containerNode.bounds.height / 2.0), duration: 0.2, removeOnCompletion: false, additive: true) + } } func addRelativeScrollingOffset(_ value: CGFloat, transition: ContainedViewLayoutTransition) { @@ -308,10 +338,15 @@ public final class TooltipScreen: ViewController { case dismiss(consume: Bool) } - private let text: String - private let textEntities: [MessageTextEntity] + public enum Location { + case point(CGRect) + case top + } + + public let text: String + public let textEntities: [MessageTextEntity] private let icon: TooltipScreen.Icon? - private let location: CGRect + private let location: TooltipScreen.Location private let shouldDismissOnTouch: (CGPoint) -> TooltipScreen.DismissOnTouch private let openActiveTextItem: (TooltipActiveTextItem, TooltipActiveTextAction) -> Void @@ -324,7 +359,7 @@ public final class TooltipScreen: ViewController { public var becameDismissed: ((TooltipScreen) -> Void)? - public init(text: String, textEntities: [MessageTextEntity] = [], icon: TooltipScreen.Icon?, location: CGRect, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void = { _, _ in }) { + public init(text: String, textEntities: [MessageTextEntity] = [], icon: TooltipScreen.Icon?, location: TooltipScreen.Location, shouldDismissOnTouch: @escaping (CGPoint) -> TooltipScreen.DismissOnTouch, openActiveTextItem: @escaping (TooltipActiveTextItem, TooltipActiveTextAction) -> Void = { _, _ in }) { self.text = text self.textEntities = textEntities self.icon = icon @@ -346,7 +381,7 @@ public final class TooltipScreen: ViewController { self.controllerNode.animateIn() - DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 20.0, execute: { [weak self] in + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 15.0, execute: { [weak self] in self?.dismiss() }) }