diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index dbe7223935..b57e179d00 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -448,7 +448,7 @@ public protocol SharedAccountContext: class { func openExternalUrl(context: AccountContext, urlContext: OpenURLContext, url: String, forceExternal: Bool, presentationData: PresentationData, navigationController: NavigationController?, dismissInput: @escaping () -> Void) func chatAvailableMessageActions(postbox: Postbox, accountPeerId: PeerId, messageIds: Set) -> Signal func resolveUrl(account: Account, url: String) -> Signal - func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) + func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) func openAddContact(context: AccountContext, firstName: String, lastName: String, phoneNumber: String, label: String, present: @escaping (ViewController, Any?) -> Void, pushController: @escaping (ViewController) -> Void, completed: @escaping () -> Void) func openAddPersonContact(context: AccountContext, peerId: PeerId, pushController: @escaping (ViewController) -> Void, present: @escaping (ViewController, Any?) -> Void) func presentContactsWarningSuppression(context: AccountContext, present: (ViewController, Any?) -> Void) diff --git a/submodules/Display/Display/GridNode.swift b/submodules/Display/Display/GridNode.swift index 304b3c6f30..8fca747118 100644 --- a/submodules/Display/Display/GridNode.swift +++ b/submodules/Display/Display/GridNode.swift @@ -225,6 +225,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? public var scrollingInitiated: (() -> Void)? public var scrollingCompleted: (() -> Void)? + public var interactiveScrollingEnded: (() -> Void)? public var visibleContentOffsetChanged: (GridNodeVisibleContentOffset) -> Void = { _ in } public final var floatingSections = false @@ -374,6 +375,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + self.interactiveScrollingEnded?() if !decelerate { self.updateItemNodeVisibilititesAndScrolling() self.updateVisibleContentOffset() @@ -478,7 +480,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } else if let fillWidth = fillWidth, fillWidth { let nextItemOriginX = nextItemOrigin.x + itemSize.width + itemSpacing let remainingWidth = remainingWidth - CGFloat(itemsInRow - 1) * itemSpacing - if nextItemOriginX + itemSize.width > self.gridLayout.size.width && remainingWidth > 0.0 { + if nextItemOriginX + itemSize.width > self.gridLayout.size.width - itemInsets.right && remainingWidth > 0.0 { itemSize.width += remainingWidth } } @@ -492,7 +494,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { index += 1 nextItemOrigin.x += itemSize.width + itemSpacing - if nextItemOrigin.x + itemSize.width > gridLayout.size.width { + if nextItemOrigin.x + itemSize.width > gridLayout.size.width - itemInsets.right { nextItemOrigin.x = initialSpacing + itemInsets.left nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false diff --git a/submodules/Display/Display/Navigation/NavigationController.swift b/submodules/Display/Display/Navigation/NavigationController.swift index 9881f5a2d9..36c70c4b6a 100644 --- a/submodules/Display/Display/Navigation/NavigationController.swift +++ b/submodules/Display/Display/Navigation/NavigationController.swift @@ -827,7 +827,7 @@ open class NavigationController: UINavigationController, ContainableController, if topModalIsFlat { maxScale = 1.0 maxOffset = 0.0 - } else if visibleModalCount == 1 { + } else if visibleModalCount <= 1 { maxScale = (layout.size.width - 16.0 * 2.0) / layout.size.width maxOffset = (topInset - (layout.size.height - layout.size.height * maxScale) / 2.0) } else { @@ -837,7 +837,7 @@ open class NavigationController: UINavigationController, ContainableController, let scale = 1.0 * visibleRootModalDismissProgress + (1.0 - visibleRootModalDismissProgress) * maxScale let offset = (1.0 - visibleRootModalDismissProgress) * maxOffset - transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset)) + transition.updateSublayerTransformScaleAndOffset(node: rootContainerNode, scale: scale, offset: CGPoint(x: 0.0, y: offset), beginWithCurrentState: true) } } else { if let rootModalFrame = self.rootModalFrame { diff --git a/submodules/Display/Display/Navigation/NavigationModalFrame.swift b/submodules/Display/Display/Navigation/NavigationModalFrame.swift index 7c6e7bf552..b40bec751f 100644 --- a/submodules/Display/Display/Navigation/NavigationModalFrame.swift +++ b/submodules/Display/Display/Navigation/NavigationModalFrame.swift @@ -102,7 +102,7 @@ final class NavigationModalFrame: ASDisplayNode { } } - private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { + private func updateShades(layout: ContainerViewLayout, progress: CGFloat, additionalProgress: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { let sideInset: CGFloat = 16.0 var topInset: CGFloat = 0.0 if let statusBarHeight = layout.statusBarHeight { @@ -131,10 +131,10 @@ final class NavigationModalFrame: ASDisplayNode { let cornerSideOffset: CGFloat = progress * sideInset + additionalProgress * sideInset let cornerTopOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset let cornerBottomOffset: CGFloat = progress * bottomInset - transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize))) - transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize))) - transition.updateFrame(node: self.bottomLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize))) - transition.updateFrame(node: self.bottomRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize))) + transition.updateFrame(node: self.topLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true) + transition.updateFrame(node: self.topRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: cornerTopOffset), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true) + transition.updateFrame(node: self.bottomLeftCorner, frame: CGRect(origin: CGPoint(x: cornerSideOffset, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true) + transition.updateFrame(node: self.bottomRightCorner, frame: CGRect(origin: CGPoint(x: layout.size.width - cornerSideOffset - cornerSize, y: layout.size.height - cornerBottomOffset - cornerSize), size: CGSize(width: cornerSize, height: cornerSize)), beginWithCurrentState: true) let topShadeOffset: CGFloat = progress * topInset + additionalProgress * additionalTopInset let bottomShadeOffset: CGFloat = progress * bottomInset @@ -142,10 +142,10 @@ final class NavigationModalFrame: ASDisplayNode { let rightShadeWidth: CGFloat = progress * sideInset + additionalProgress * sideInset let rightShadeOffset: CGFloat = layout.size.width - rightShadeWidth - transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset))) + transition.updateFrame(node: self.topShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: topShadeOffset)), beginWithCurrentState: true) transition.updateFrame(node: self.bottomShade, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - bottomShadeOffset), size: CGSize(width: layout.size.width, height: bottomShadeOffset))) - transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height))) - transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), completion: { _ in + transition.updateFrame(node: self.leftShade, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: leftShadeOffset, height: layout.size.height)), beginWithCurrentState: true) + transition.updateFrame(node: self.rightShade, frame: CGRect(origin: CGPoint(x: rightShadeOffset, y: 0.0), size: CGSize(width: rightShadeWidth, height: layout.size.height)), beginWithCurrentState: true, completion: { _ in completion() }) } diff --git a/submodules/Display/Display/PeekController.swift b/submodules/Display/Display/PeekController.swift index 456c3ceb29..2947e1ecbf 100644 --- a/submodules/Display/Display/PeekController.swift +++ b/submodules/Display/Display/PeekController.swift @@ -37,6 +37,8 @@ public final class PeekController: ViewController { self.sourceNode = sourceNode super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore } required public init(coder aDecoder: NSCoder) { diff --git a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift index 8b27e241c8..71102c7d06 100644 --- a/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageControllerNode.swift @@ -1191,7 +1191,7 @@ final class InstantPageControllerNode: ASDisplayNode, UIScrollViewDelegate { self?.present(c, a) }, dismissInput: { self?.view.endEditing(true) - }) + }, contentContext: nil) } } })) diff --git a/submodules/PeerInfoUI/Sources/UserInfoController.swift b/submodules/PeerInfoUI/Sources/UserInfoController.swift index ea279d02ff..c613246f5f 100644 --- a/submodules/PeerInfoUI/Sources/UserInfoController.swift +++ b/submodules/PeerInfoUI/Sources/UserInfoController.swift @@ -1450,7 +1450,7 @@ public func userInfoController(context: AccountContext, peerId: PeerId, mode: Pe presentControllerImpl?(c, a) }, dismissInput: { dismissInputImpl?() - }) + }, contentContext: nil) } shareBotImpl = { [weak controller] in let _ = (getUserPeer(postbox: context.account.postbox, peerId: peerId) diff --git a/submodules/SettingsUI/Sources/LogoutOptionsController.swift b/submodules/SettingsUI/Sources/LogoutOptionsController.swift index 9daaab5181..e7c8a6abc4 100644 --- a/submodules/SettingsUI/Sources/LogoutOptionsController.swift +++ b/submodules/SettingsUI/Sources/LogoutOptionsController.swift @@ -186,7 +186,7 @@ func logoutOptionsController(context: AccountContext, navigationController: Navi context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation in }, sendFile: nil, sendSticker: nil, present: { controller, arguments in pushControllerImpl?(controller) - }, dismissInput: {}) + }, dismissInput: {}, contentContext: nil) }) } diff --git a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift index 0ba8e705b3..de46ee2e42 100644 --- a/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift +++ b/submodules/SettingsUI/Sources/Search/SettingsSearchableItems.swift @@ -887,7 +887,7 @@ func settingsSearchableItems(context: AccountContext, notificationExceptionsList context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: navigationController, openPeer: { peer, navigation in }, sendFile: nil, sendSticker: nil, present: { controller, arguments in present(.push, controller) - }, dismissInput: {}) + }, dismissInput: {}, contentContext: nil) }) }) allItems.append(faq) diff --git a/submodules/SettingsUI/Sources/SettingsController.swift b/submodules/SettingsUI/Sources/SettingsController.swift index a5caf22fd2..20d71f6cf1 100644 --- a/submodules/SettingsUI/Sources/SettingsController.swift +++ b/submodules/SettingsUI/Sources/SettingsController.swift @@ -824,7 +824,7 @@ public func settingsController(context: AccountContext, accountManager: AccountM context.sharedContext.openResolvedUrl(resolvedUrl, context: context, urlContext: .generic, navigationController: getNavigationControllerImpl?(), openPeer: { peer, navigation in }, sendFile: nil, sendSticker: nil, present: { controller, arguments in pushControllerImpl?(controller) - }, dismissInput: {}) + }, dismissInput: {}, contentContext: nil) }) }) } diff --git a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift index 5f3ec60bfc..cf76c59293 100644 --- a/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift +++ b/submodules/StickerPackPreviewUI/Sources/StickerPackScreen.swift @@ -7,4 +7,772 @@ import TelegramCore import SyncCore import SwiftSignalKit import AccountContext +import TelegramPresentationData +import TelegramUIPreferences +import MergeLists +private struct StickerPackPreviewGridEntry: Comparable, Identifiable { + let index: Int + let stickerItem: StickerPackItem + + var stableId: MediaId { + return self.stickerItem.file.fileId + } + + static func <(lhs: StickerPackPreviewGridEntry, rhs: StickerPackPreviewGridEntry) -> Bool { + return lhs.index < rhs.index + } + + func item(account: Account, interaction: StickerPackPreviewInteraction) -> StickerPackPreviewGridItem { + return StickerPackPreviewGridItem(account: account, stickerItem: self.stickerItem, interaction: interaction) + } +} + +private struct StickerPackPreviewGridTransaction { + let deletions: [Int] + let insertions: [GridNodeInsertItem] + let updates: [GridNodeUpdateItem] + + init(previousList: [StickerPackPreviewGridEntry], list: [StickerPackPreviewGridEntry], account: Account, interaction: StickerPackPreviewInteraction) { + let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: previousList, rightList: list) + + self.deletions = deleteIndices + self.insertions = indicesAndItems.map { GridNodeInsertItem(index: $0.0, item: $0.1.item(account: account, interaction: interaction), previousIndex: $0.2) } + self.updates = updateIndices.map { GridNodeUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, interaction: interaction)) } + } +} + +private enum StickerPackAction { + case add + case remove +} + +private enum StickerPackNextAction { + case navigatedNext + case dismiss +} + +private final class StickerPackContainer: ASDisplayNode { + private let context: AccountContext + private var presentationData: PresentationData + private let stickerPack: StickerPackReference + private let decideNextAction: (StickerPackContainer, StickerPackAction) -> StickerPackNextAction + private let requestDismiss: () -> Void + private let presentInGlobalOverlay: (ViewController, Any?) -> Void + private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + private let backgroundNode: ASImageNode + private let gridNode: GridNode + private let actionAreaBackgroundNode: ASDisplayNode + private let actionAreaSeparatorNode: ASDisplayNode + private let buttonNode: HighlightableButtonNode + private let titleNode: ImmediateTextNode + private let titleContainer: ASDisplayNode + private let titleSeparatorNode: ASDisplayNode + + private(set) var validLayout: (ContainerViewLayout, CGRect, CGFloat, UIEdgeInsets)? + + private var currentEntries: [StickerPackPreviewGridEntry] = [] + private var enqueuedTransactions: [StickerPackPreviewGridTransaction] = [] + + private var itemsDisposable: Disposable? + private(set) var currentStickerPack: (StickerPackCollectionInfo, [ItemCollectionItem], Bool)? + + private let isReadyValue = Promise() + private var didSetReady = false + var isReady: Signal { + return self.isReadyValue.get() + } + + var expandProgress: CGFloat = 0.0 + var modalProgress: CGFloat = 0.0 + let expandProgressUpdated: (StickerPackContainer, ContainedViewLayoutTransition) -> Void + + private let interaction: StickerPackPreviewInteraction + + init(context: AccountContext, presentationData: PresentationData, stickerPack: StickerPackReference, decideNextAction: @escaping (StickerPackContainer, StickerPackAction) -> StickerPackNextAction, requestDismiss: @escaping () -> Void, expandProgressUpdated: @escaping (StickerPackContainer, ContainedViewLayoutTransition) -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { + self.context = context + self.presentationData = presentationData + self.stickerPack = stickerPack + self.decideNextAction = decideNextAction + self.requestDismiss = requestDismiss + self.presentInGlobalOverlay = presentInGlobalOverlay + self.expandProgressUpdated = expandProgressUpdated + self.sendSticker = sendSticker + + self.backgroundNode = ASImageNode() + self.backgroundNode.displaysAsynchronously = true + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 20.0, color: self.presentationData.theme.actionSheet.opaqueItemBackgroundColor) + + self.gridNode = GridNode() + self.gridNode.scrollView.alwaysBounceVertical = true + self.gridNode.scrollView.showsVerticalScrollIndicator = false + + self.actionAreaBackgroundNode = ASDisplayNode() + self.actionAreaBackgroundNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemBackgroundColor + + self.actionAreaSeparatorNode = ASDisplayNode() + self.actionAreaSeparatorNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemSeparatorColor + + self.buttonNode = HighlightableButtonNode() + self.titleNode = ImmediateTextNode() + self.titleContainer = ASDisplayNode() + self.titleSeparatorNode = ASDisplayNode() + self.titleSeparatorNode.backgroundColor = self.presentationData.theme.actionSheet.opaqueItemSeparatorColor + + self.interaction = StickerPackPreviewInteraction(playAnimatedStickers: true) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.gridNode) + self.addSubnode(self.actionAreaBackgroundNode) + self.addSubnode(self.actionAreaSeparatorNode) + self.addSubnode(self.buttonNode) + + self.titleContainer.addSubnode(self.titleNode) + self.addSubnode(self.titleContainer) + self.addSubnode(self.titleSeparatorNode) + + self.gridNode.presentationLayoutUpdated = { [weak self] presentationLayout, transition in + self?.gridPresentationLayoutUpdated(presentationLayout, transition: transition) + } + + self.gridNode.interactiveScrollingEnded = { [weak self] in + guard let strongSelf = self else { + return + } + let contentOffset = strongSelf.gridNode.scrollView.contentOffset + let insets = strongSelf.gridNode.scrollView.contentInset + + if contentOffset.y <= -insets.top - 30.0 { + DispatchQueue.main.async { + self?.requestDismiss() + } + } + } + + self.itemsDisposable = (loadedStickerPack(postbox: context.account.postbox, network: context.account.network, reference: stickerPack, forceActualized: false) + |> deliverOnMainQueue).start(next: { [weak self] contents in + guard let strongSelf = self else { + return + } + strongSelf.updateStickerPackContents(contents) + }) + + self.buttonNode.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + self.buttonNode.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.buttonNode.layer.removeAnimation(forKey: "opacity") + strongSelf.buttonNode.alpha = 0.8 + } else { + strongSelf.buttonNode.alpha = 1.0 + strongSelf.buttonNode.layer.animateAlpha(from: 0.8, to: 1.0, duration: 0.3) + } + } + } + } + + deinit { + self.itemsDisposable?.dispose() + } + + override func didLoad() { + super.didLoad() + + self.gridNode.view.addGestureRecognizer(PeekControllerGestureRecognizer(contentAtPoint: { [weak self] point -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? in + if let strongSelf = self { + if let itemNode = strongSelf.gridNode.itemNodeAtPoint(point) as? StickerPackPreviewGridItemNode, let item = itemNode.stickerPackItem { + return strongSelf.context.account.postbox.transaction { transaction -> Bool in + return getIsStickerSaved(transaction: transaction, fileId: item.file.fileId) + } + |> deliverOnMainQueue + |> map { isStarred -> (ASDisplayNode, PeekControllerContent)? in + if let strongSelf = self { + var menuItems: [PeekControllerMenuItem] = [] + if let (info, _, _) = strongSelf.currentStickerPack, info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + if strongSelf.sendSticker != nil { + menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.ShareMenu_Send, color: .accent, font: .bold, action: { node, rect in + if let strongSelf = self { + return strongSelf.sendSticker?(.standalone(media: item.file), node, rect) ?? false + } else { + return false + } + })) + } + menuItems.append(PeekControllerMenuItem(title: isStarred ? strongSelf.presentationData.strings.Stickers_RemoveFromFavorites : strongSelf.presentationData.strings.Stickers_AddToFavorites, color: isStarred ? .destructive : .accent, action: { _, _ in + if let strongSelf = self { + if isStarred { + let _ = removeSavedSticker(postbox: strongSelf.context.account.postbox, mediaId: item.file.fileId).start() + } else { + let _ = addSavedSticker(postbox: strongSelf.context.account.postbox, network: strongSelf.context.account.network, file: item.file).start() + } + } + return true + })) + menuItems.append(PeekControllerMenuItem(title: strongSelf.presentationData.strings.Common_Cancel, color: .accent, font: .bold, action: { _, _ in return true })) + } + return (itemNode, StickerPreviewPeekContent(account: strongSelf.context.account, item: .pack(item), menu: menuItems)) + } else { + return nil + } + } + } + } + return nil + }, present: { [weak self] content, sourceNode in + if let strongSelf = self { + let controller = PeekController(theme: PeekControllerTheme(presentationTheme: strongSelf.presentationData.theme), content: content, sourceNode: { + return sourceNode + }) + strongSelf.presentInGlobalOverlay(controller, nil) + return controller + } + return nil + }, updateContent: { [weak self] content in + if let strongSelf = self { + var item: StickerPreviewPeekItem? + if let content = content as? StickerPreviewPeekContent { + item = content.item + } + strongSelf.updatePreviewingItem(item: item, animated: true) + } + }, activateBySingleTap: true)) + } + + @objc func buttonPressed() { + guard let (info, items, installed) = currentStickerPack else { + return + } + + let _ = (self.context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.stickerSettings]) + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] sharedData in + guard let strongSelf = self else { + return + } + var stickerSettings = StickerSettings.defaultSettings + if let value = sharedData.entries[ApplicationSpecificSharedDataKeys.stickerSettings] as? StickerSettings { + stickerSettings = value + } + + if installed { + let _ = removeStickerPackInteractively(postbox: strongSelf.context.account.postbox, id: info.id, option: .delete).start() + } else { + let _ = addStickerPackInteractively(postbox: strongSelf.context.account.postbox, info: info, items: items).start() + } + + switch strongSelf.decideNextAction(strongSelf, installed ? .remove : .add) { + case .dismiss: + strongSelf.requestDismiss() + case .navigatedNext: + strongSelf.updateStickerPackContents(.result(info: info, items: items, installed: !installed)) + } + }) + } + + private func updateStickerPackContents(_ contents: LoadedStickerPack) { + var entries: [StickerPackPreviewGridEntry] = [] + + var updateLayout = false + + switch contents { + case .fetching: + entries = [] + case .none: + entries = [] + case let .result(info, items, installed): + self.currentStickerPack = (info, items, installed) + + if installed { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_RemoveStickerCount(info.count) + } else { + text = self.presentationData.strings.StickerPack_RemoveMaskCount(info.count) + } + self.buttonNode.setTitle(text.uppercased(), with: Font.semibold(17.0), with: self.presentationData.theme.list.itemDestructiveColor, for: .normal) + self.buttonNode.setBackgroundImage(nil, for: []) + } else { + let text: String + if info.id.namespace == Namespaces.ItemCollection.CloudStickerPacks { + text = self.presentationData.strings.StickerPack_AddStickerCount(info.count) + } else { + text = self.presentationData.strings.StickerPack_AddMaskCount(info.count) + } + self.buttonNode.setTitle(text.uppercased(), with: Font.semibold(17.0), with: self.presentationData.theme.list.itemCheckColors.foregroundColor, for: .normal) + let roundedAccentBackground = generateImage(CGSize(width: 50.0, height: 50.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(self.presentationData.theme.list.itemCheckColors.fillColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: size.height))) + })?.stretchableImage(withLeftCapWidth: 25, topCapHeight: 25) + self.buttonNode.setBackgroundImage(roundedAccentBackground, for: []) + } + + self.titleNode.attributedText = NSAttributedString(string: info.title, font: Font.semibold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) + updateLayout = true + + for item in items { + guard let item = item as? StickerPackItem else { + continue + } + entries.append(StickerPackPreviewGridEntry(index: entries.count, stickerItem: item)) + } + } + let previousEntries = self.currentEntries + self.currentEntries = entries + + if updateLayout, let (layout, _, _, _) = self.validLayout { + let titleSize = self.titleNode.updateLayout(CGSize(width: layout.size.width - 12.0 * 2.0, height: .greatestFiniteMagnitude)) + self.titleNode.frame = CGRect(origin: CGPoint(x: floor((-titleSize.width) / 2.0), y: floor((-titleSize.height) / 2.0)), size: titleSize) + } + + let transaction = StickerPackPreviewGridTransaction(previousList: previousEntries, list: entries, account: self.context.account, interaction: self.interaction) + self.enqueueTransaction(transaction) + } + + var topContentInset: CGFloat { + guard let (_, gridFrame, titleAreaInset, gridInsets) = self.validLayout else { + return 0.0 + } + return gridFrame.minY + gridInsets.top - titleAreaInset + } + + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var insets = layout.insets(options: [.statusBar]) + insets.top += 10.0 + + let buttonHeight: CGFloat = 50.0 + let actionAreaTopInset: CGFloat = 12.0 + let buttonSideInset: CGFloat = 10.0 + let titleAreaInset: CGFloat = 50.0 + + var actionAreaHeight: CGFloat = 0.0 + actionAreaHeight += insets.bottom + + transition.updateFrame(node: self.buttonNode, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: layout.size.height - actionAreaHeight - buttonHeight), size: CGSize(width: layout.size.width - buttonSideInset * 2.0, height: buttonHeight))) + actionAreaHeight += buttonHeight + + actionAreaHeight += actionAreaTopInset + + transition.updateFrame(node: self.actionAreaBackgroundNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: actionAreaHeight))) + transition.updateFrame(node: self.actionAreaSeparatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - actionAreaHeight), size: CGSize(width: layout.size.width, height: UIScreenPixel))) + + let gridFrame = CGRect(origin: CGPoint(x: 0.0, y: insets.top + titleAreaInset), size: CGSize(width: layout.size.width, height: layout.size.height - insets.top - titleAreaInset)) + + let itemsPerRow = 4 + let fillingWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: 0.0) + let itemWidth = floor(fillingWidth / CGFloat(itemsPerRow)) + let gridLeftInset = floor((layout.size.width - fillingWidth) / 2.0) + + let initialRevealedRowCount: CGFloat = 4.5 + + let topInset = max(0.0, layout.size.height - floor(initialRevealedRowCount * itemWidth) - insets.top - actionAreaHeight - titleAreaInset) + + let gridInsets = UIEdgeInsets(top: insets.top + topInset, left: gridLeftInset, bottom: actionAreaHeight, right: layout.size.width - fillingWidth - gridLeftInset) + + let firstTime = self.validLayout == nil + self.validLayout = (layout, gridFrame, titleAreaInset, gridInsets) + + transition.updateFrame(node: self.gridNode, frame: gridFrame) + self.gridNode.transaction(GridNodeTransaction(deleteItems: [], insertItems: [], updateItems: [], scrollToItem: nil, updateLayout: GridNodeUpdateLayout(layout: GridNodeLayout(size: gridFrame.size, insets: gridInsets, scrollIndicatorInsets: nil, preloadSize: 200.0, type: .fixed(itemSize: CGSize(width: itemWidth, height: itemWidth), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)), transition: transition), itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil, updateOpaqueState: nil, synchronousLoads: false), completion: { [weak self] _ in + guard let strongSelf = self else { + return + } + if !strongSelf.didSetReady { + strongSelf.didSetReady = true + strongSelf.isReadyValue.set(.single(true)) + } + }) + + if firstTime { + while !self.enqueuedTransactions.isEmpty { + self.dequeueTransaction() + } + } + } + + private func gridPresentationLayoutUpdated(_ presentationLayout: GridNodeCurrentPresentationLayout, transition: ContainedViewLayoutTransition) { + guard let (layout, gridFrame, titleAreaInset, gridInsets) = self.validLayout else { + return + } + + let minBackgroundY = gridFrame.minY - titleAreaInset + let unclippedBackgroundY = gridFrame.minY - presentationLayout.contentOffset.y - titleAreaInset + + let offsetFromInitialPosition = presentationLayout.contentOffset.y + gridInsets.top + let expandHeight: CGFloat = 100.0 + let expandProgress = max(0.0, min(1.0, offsetFromInitialPosition / expandHeight)) + + var expandProgressTransition = transition + var expandUpdated = false + + let modalProgress: CGFloat = unclippedBackgroundY < minBackgroundY ? 1.0 : 0.0 + if abs(self.modalProgress - modalProgress) > CGFloat.ulpOfOne { + self.modalProgress = modalProgress + expandUpdated = true + expandProgressTransition = .animated(duration: 0.3, curve: .easeInOut) + } + + if abs(self.expandProgress - expandProgress) > CGFloat.ulpOfOne { + self.expandProgress = expandProgress + expandUpdated = true + } + + if expandUpdated { + self.expandProgressUpdated(self, expandProgressTransition) + } + + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: max(minBackgroundY, unclippedBackgroundY)), size: CGSize(width: layout.size.width, height: layout.size.height)) + transition.updateFrame(node: self.backgroundNode, frame: backgroundFrame) + transition.updateFrame(node: self.titleContainer, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX + floor((backgroundFrame.width) / 2.0), y: backgroundFrame.minY + floor((50.0) / 2.0)), size: CGSize())) + transition.updateFrame(node: self.titleSeparatorNode, frame: CGRect(origin: CGPoint(x: backgroundFrame.minX, y: backgroundFrame.minY + 50.0 - UIScreenPixel), size: CGSize(width: backgroundFrame.width, height: UIScreenPixel))) + self.titleSeparatorNode.alpha = unclippedBackgroundY < minBackgroundY ? 1.0 : 0.0 + } + + private func enqueueTransaction(_ transaction: StickerPackPreviewGridTransaction) { + self.enqueuedTransactions.append(transaction) + + if let _ = self.validLayout { + self.dequeueTransaction() + } + } + + private func dequeueTransaction() { + if self.enqueuedTransactions.isEmpty { + return + } + let transaction = self.enqueuedTransactions.removeFirst() + + self.gridNode.transaction(GridNodeTransaction(deleteItems: transaction.deletions, insertItems: transaction.insertions, updateItems: transaction.updates, scrollToItem: nil, updateLayout: nil, itemTransition: .immediate, stationaryItems: .none, updateFirstIndexInSectionOffset: nil), completion: { _ in }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) { + if !self.backgroundNode.bounds.contains(self.convert(point, to: self.backgroundNode)) { + return nil + } + } + + let result = super.hitTest(point, with: event) + return result + } + + private func updatePreviewingItem(item: StickerPreviewPeekItem?, animated: Bool) { + if self.interaction.previewedItem != item { + self.interaction.previewedItem = item + + self.gridNode.forEachItemNode { itemNode in + if let itemNode = itemNode as? StickerPackPreviewGridItemNode { + itemNode.updatePreviewing(animated: animated) + } + } + } + } +} + +private final class StickerPackScreenNode: ViewControllerTracingNode { + private let context: AccountContext + private var presentationData: PresentationData + private let stickerPacks: [StickerPackReference] + private let modalProgressUpdated: (CGFloat, ContainedViewLayoutTransition) -> Void + private let dismissed: () -> Void + + private let dimNode: ASDisplayNode + private let containerContainingNode: ASDisplayNode + + private var containers: [StickerPackContainer] = [] + private var selectedStickerPackIndex: Int + private var relativeToSelectedStickerPackTransition: CGFloat = 0.0 + + private var validLayout: ContainerViewLayout? + private var isDismissed: Bool = false + + private let _ready = Promise() + var ready: Promise { + return self._ready + } + + init(context: AccountContext, stickerPacks: [StickerPackReference], initialSelectedStickerPackIndex: Int, modalProgressUpdated: @escaping (CGFloat, ContainedViewLayoutTransition) -> Void, dismissed: @escaping () -> Void, presentInGlobalOverlay: @escaping (ViewController, Any?) -> Void, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { + self.context = context + self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.stickerPacks = stickerPacks + self.selectedStickerPackIndex = initialSelectedStickerPackIndex + self.modalProgressUpdated = modalProgressUpdated + self.dismissed = dismissed + + self.dimNode = ASDisplayNode() + self.dimNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.dimNode.alpha = 0.0 + + self.containerContainingNode = ASDisplayNode() + + super.init() + + self.containers = self.stickerPacks.map { stickerPack in + return StickerPackContainer(context: context, presentationData: self.presentationData, stickerPack: stickerPack, decideNextAction: { [weak self] container, action in + guard let strongSelf = self, let layout = strongSelf.validLayout, let index = strongSelf.containers.index(where: { $0 === container }) else { + return .dismiss + } + if index == strongSelf.containers.count - 1 { + return .dismiss + } else { + switch action { + case .add: + var allAdded = true + for i in index + 1 ..< strongSelf.containers.count { + if let (_, _, installed) = strongSelf.containers[i].currentStickerPack { + if !installed { + allAdded = false + } + } else { + allAdded = false + } + } + if allAdded { + return .dismiss + } + case .remove: + if strongSelf.containers.count == 1 { + return .dismiss + } + } + } + + strongSelf.selectedStickerPackIndex = strongSelf.selectedStickerPackIndex + 1 + strongSelf.containerLayoutUpdated(layout, transition: .animated(duration: 0.3, curve: .spring)) + return .navigatedNext + }, requestDismiss: { [weak self] in + self?.dismiss() + }, expandProgressUpdated: { [weak self] container, transition in + guard let strongSelf = self, let layout = strongSelf.validLayout, let index = strongSelf.containers.index(where: { $0 === container }) else { + return + } + if index == strongSelf.selectedStickerPackIndex { + strongSelf.containerLayoutUpdated(layout, transition: transition) + } + }, presentInGlobalOverlay: presentInGlobalOverlay, + sendSticker: sendSticker) + } + + for container in self.containers { + self.containerContainingNode.addSubnode(container) + } + + self.addSubnode(self.dimNode) + + self.addSubnode(self.containerContainingNode) + + self._ready.set(combineLatest(self.containers.map { $0.isReady }) + |> map { values -> Bool in + for value in values { + if !value { + return false + } + } + return true + }) + } + + override func didLoad() { + super.didLoad() + + self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTapGesture(_:)))) + self.containerContainingNode.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let firstTime = self.validLayout == nil + + self.validLayout = layout + + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(node: self.containerContainingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let expandProgress: CGFloat + if self.containers.count == 1 { + expandProgress = 1.0 + } else { + expandProgress = self.containers[self.selectedStickerPackIndex].expandProgress + } + let scaledInset: CGFloat = 16.0 + let scaledDistance: CGFloat = 4.0 + let minScale = (layout.size.width - scaledInset * 2.0) / layout.size.width + let containerScale = expandProgress * 1.0 + (1.0 - expandProgress) * minScale + + let containerVerticalOffset: CGFloat = (1.0 - expandProgress) * scaledInset * 2.0 + + for i in 0 ..< self.containers.count { + let container = self.containers[i] + + let indexOffset = i - self.selectedStickerPackIndex + var scaledOffset: CGFloat = 0.0 + scaledOffset = -CGFloat(indexOffset) * (1.0 - expandProgress) * (scaledInset * 2.0) + CGFloat(indexOffset) * scaledDistance + + transition.updateFrame(node: container, frame: CGRect(origin: CGPoint(x: CGFloat(indexOffset) * layout.size.width + self.relativeToSelectedStickerPackTransition + scaledOffset, y: containerVerticalOffset), size: layout.size)) + transition.updateSublayerTransformScale(node: container, scale: containerScale) + if container.validLayout?.0 != layout { + container.updateLayout(layout: layout, transition: transition) + } + } + + let modalProgress = self.containers[self.selectedStickerPackIndex].modalProgress + self.modalProgressUpdated(modalProgress, transition) + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + break + case .changed: + let translation = recognizer.translation(in: self.view) + self.relativeToSelectedStickerPackTransition = translation.x + if self.selectedStickerPackIndex == 0 { + self.relativeToSelectedStickerPackTransition = min(0.0, self.relativeToSelectedStickerPackTransition) + } + if self.selectedStickerPackIndex == self.containers.count - 1 { + self.relativeToSelectedStickerPackTransition = max(0.0, self.relativeToSelectedStickerPackTransition) + } + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .immediate) + } + case .ended, .cancelled: + let translation = recognizer.translation(in: self.view) + if abs(translation.x) > 30.0 { + let deltaIndex = translation.x > 0 ? -1 : 1 + self.selectedStickerPackIndex = max(0, min(self.containers.count - 1, Int(self.selectedStickerPackIndex + deltaIndex))) + } + self.relativeToSelectedStickerPackTransition = 0.0 + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.2, curve: .easeInOut)) + } + default: + break + } + } + + func animateIn() { + self.dimNode.alpha = 1.0 + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + + let minInset: CGFloat = (self.containers.map { container -> CGFloat in container.topContentInset }).max() ?? 0.0 + self.containerContainingNode.layer.animatePosition(from: CGPoint(x: 0.0, y: self.containerContainingNode.bounds.height - minInset), to: CGPoint(), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, additive: true) + } + + func animateOut(completion: @escaping () -> Void) { + self.dimNode.alpha = 0.0 + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + + let minInset: CGFloat = (self.containers.map { container -> CGFloat in container.topContentInset }).max() ?? 0.0 + self.containerContainingNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: self.containerContainingNode.bounds.height - minInset), duration: 0.2, timingFunction: CAMediaTimingFunctionName.easeInEaseOut.rawValue, removeOnCompletion: false, additive: true, completion: { _ in + completion() + }) + } + + func dismiss() { + if self.isDismissed { + return + } + self.isDismissed = true + self.animateOut(completion: { [weak self] in + self?.dismissed() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if !self.bounds.contains(point) { + return nil + } + + let selectedContainer = self.containers[self.selectedStickerPackIndex] + if selectedContainer.hitTest(self.view.convert(point, to: selectedContainer.view), with: event) == nil { + return self.dimNode.view + } + + let result = super.hitTest(point, with: event) + return result + } + + @objc private func dimNodeTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismiss() + } + } +} + +public final class StickerPackScreen: ViewController { + private let context: AccountContext + private let stickerPacks: [StickerPackReference] + private let initialSelectedStickerPackIndex: Int + private let sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)? + + private var controllerNode: StickerPackScreenNode { + return self.displayNode as! StickerPackScreenNode + } + + private let _ready = Promise() + override public var ready: Promise { + return self._ready + } + + private var alreadyDidAppear: Bool = false + + public init(context: AccountContext, stickerPacks: [StickerPackReference], selectedStickerPackIndex: Int = 0, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?) { + self.context = context + self.stickerPacks = stickerPacks + self.initialSelectedStickerPackIndex = selectedStickerPackIndex + self.sendSticker = sendSticker + + super.init(navigationBarPresentationData: nil) + + self.statusBar.statusBarStyle = .Ignore + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func loadDisplayNode() { + self.displayNode = StickerPackScreenNode(context: self.context, stickerPacks: self.stickerPacks, initialSelectedStickerPackIndex: self.initialSelectedStickerPackIndex, modalProgressUpdated: { [weak self] value, transition in + DispatchQueue.main.async { + guard let strongSelf = self else { + return + } + strongSelf.updateModalStyleOverlayTransitionFactor(value, transition: transition) + } + }, dismissed: { [weak self] in + self?.dismiss() + }, presentInGlobalOverlay: { [weak self] c, a in + self?.presentInGlobalOverlay(c, with: a) + }, sendSticker: self.sendSticker.flatMap { [weak self] sendSticker in + return { file, sourceNode, sourceRect in + if sendSticker(file, sourceNode, sourceRect) { + self?.dismiss() + return true + } else { + return false + } + } + }) + + self._ready.set(self.controllerNode.ready.get()) + + super.displayNodeDidLoad() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.alreadyDidAppear { + self.alreadyDidAppear = true + self.controllerNode.animateIn() + } + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } +} diff --git a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift index e2c856eeb3..36e83fd4a7 100644 --- a/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift +++ b/submodules/TelegramUI/TelegramUI/ChatBotInfoItem.swift @@ -300,7 +300,7 @@ final class ChatBotInfoItemNode: ListViewItemNode { case .none, .ignore: break case let .url(url, concealed): - self.item?.controllerInteraction.openUrl(url, concealed, nil) + self.item?.controllerInteraction.openUrl(url, concealed, nil, nil) case let .peerMention(peerId, _): self.item?.controllerInteraction.openPeer(peerId, .chat(textInputState: nil, subject: nil), nil) case let .textMention(name): diff --git a/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift index c061e21e35..8bb088f540 100644 --- a/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatButtonKeyboardInputNode.swift @@ -166,7 +166,7 @@ final class ChatButtonKeyboardInputNode: ChatInputNode { case .text: self.controllerInteraction.sendMessage(markupButton.title) case let .url(url): - self.controllerInteraction.openUrl(url, true, nil) + self.controllerInteraction.openUrl(url, true, nil, nil) case .requestMap: self.controllerInteraction.shareCurrentLocation() case .requestPhone: diff --git a/submodules/TelegramUI/TelegramUI/ChatController.swift b/submodules/TelegramUI/TelegramUI/ChatController.swift index 9a14a4b18f..ce90772947 100644 --- a/submodules/TelegramUI/TelegramUI/ChatController.swift +++ b/submodules/TelegramUI/TelegramUI/ChatController.swift @@ -455,7 +455,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } strongSelf.chatDisplayNode.historyNode.view.superview?.insertSubview(view, aboveSubview: strongSelf.chatDisplayNode.historyNode.view) }, openUrl: { url in - self?.openUrl(url, concealed: false) + self?.openUrl(url, concealed: false, message: nil) }, openPeer: { peer, navigation in self?.openPeer(peerId: peer.id, navigation: navigation, fromMessage: nil) }, callPeer: { peerId in @@ -506,7 +506,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } }, actionInteraction: GalleryControllerActionInteraction(openUrl: { [weak self] url, concealed in if let strongSelf = self { - strongSelf.controllerInteraction?.openUrl(url, concealed, nil) + strongSelf.controllerInteraction?.openUrl(url, concealed, nil, nil) } }, openUrlIn: { [weak self] url in if let strongSelf = self { @@ -928,9 +928,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } else { strongSelf.openPeer(peerId: peerId, navigation: .chat(textInputState: ChatTextInputState(inputText: NSAttributedString(string: inputString)), subject: nil), fromMessage: nil) } - }, openUrl: { [weak self] url, concealed, _ in + }, openUrl: { [weak self] url, concealed, _, message in if let strongSelf = self { - strongSelf.openUrl(url, concealed: concealed) + strongSelf.openUrl(url, concealed: concealed, message: message) } }, shareCurrentLocation: { [weak self] in if let strongSelf = self { @@ -7220,7 +7220,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G })) } - private func openResolved(_ result: ResolvedUrl) { + private func openResolved(_ result: ResolvedUrl, message: Message? = nil) { self.context.sharedContext.openResolvedUrl(result, context: self.context, urlContext: .chat, navigationController: self.effectiveNavigationController, openPeer: { [weak self] peerId, navigation in guard let strongSelf = self else { return @@ -7262,10 +7262,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self?.present(c, in: .window(.root), with: a) }, dismissInput: { [weak self] in self?.chatDisplayNode.dismissInput() - }) + }, contentContext: message) } - private func openUrl(_ url: String, concealed: Bool) { + private func openUrl(_ url: String, concealed: Bool, message: Message? = nil) { self.commitPurposefulAction() let openImpl: () -> Void = { [weak self] in @@ -7308,7 +7308,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { - strongSelf.openResolved(result) + strongSelf.openResolved(result, message: message) } })) } diff --git a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift index 02b137bb06..cd1d8b5424 100644 --- a/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/TelegramUI/ChatControllerInteraction.swift @@ -64,7 +64,7 @@ public final class ChatControllerInteraction { let requestMessageActionCallback: (MessageId, MemoryBuffer?, Bool) -> Void let requestMessageActionUrlAuth: (String, MessageId, Int32) -> Void let activateSwitchInline: (PeerId?, String) -> Void - let openUrl: (String, Bool, Bool?) -> Void + let openUrl: (String, Bool, Bool?, Message?) -> Void let shareCurrentLocation: () -> Void let shareAccountContact: () -> Void let sendBotCommand: (MessageId?, String) -> Void @@ -115,7 +115,7 @@ public final class ChatControllerInteraction { var searchTextHighightState: (String, [MessageIndex])? var seenOneTimeAnimatedMedia = Set() - init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { + init(openMessage: @escaping (Message, ChatControllerInteractionOpenMessageMode) -> Bool, openPeer: @escaping (PeerId?, ChatControllerInteractionNavigateToPeer, Message?) -> Void, openPeerMention: @escaping (String) -> Void, openMessageContextMenu: @escaping (Message, Bool, ASDisplayNode, CGRect, TapLongTapOrDoubleTapGestureRecognizer?) -> Void, openMessageContextActions: @escaping (Message, ASDisplayNode, CGRect, ContextGesture?) -> Void, navigateToMessage: @escaping (MessageId, MessageId) -> Void, clickThroughMessage: @escaping () -> Void, toggleMessagesSelection: @escaping ([MessageId], Bool) -> Void, sendCurrentMessage: @escaping (Bool) -> Void, sendMessage: @escaping (String) -> Void, sendSticker: @escaping (FileMediaReference, Bool, ASDisplayNode, CGRect) -> Bool, sendGif: @escaping (FileMediaReference, ASDisplayNode, CGRect) -> Bool, requestMessageActionCallback: @escaping (MessageId, MemoryBuffer?, Bool) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageId, Int32) -> Void, activateSwitchInline: @escaping (PeerId?, String) -> Void, openUrl: @escaping (String, Bool, Bool?, Message?) -> Void, shareCurrentLocation: @escaping () -> Void, shareAccountContact: @escaping () -> Void, sendBotCommand: @escaping (MessageId?, String) -> Void, openInstantPage: @escaping (Message, ChatMessageItemAssociatedData?) -> Void, openWallpaper: @escaping (Message) -> Void, openTheme: @escaping (Message) -> Void, openHashtag: @escaping (String?, String) -> Void, updateInputState: @escaping ((ChatTextInputState) -> ChatTextInputState) -> Void, updateInputMode: @escaping ((ChatInputMode) -> ChatInputMode) -> Void, openMessageShareMenu: @escaping (MessageId) -> Void, presentController: @escaping (ViewController, Any?) -> Void, navigationController: @escaping () -> NavigationController?, chatControllerNode: @escaping () -> ASDisplayNode?, reactionContainerNode: @escaping () -> ReactionSelectionParentNode?, presentGlobalOverlayController: @escaping (ViewController, Any?) -> Void, callPeer: @escaping (PeerId) -> Void, longTap: @escaping (ChatControllerInteractionLongTapAction, Message?) -> Void, openCheckoutOrReceipt: @escaping (MessageId) -> Void, openSearch: @escaping () -> Void, setupReply: @escaping (MessageId) -> Void, canSetupReply: @escaping (Message) -> Bool, navigateToFirstDateMessage: @escaping(Int32) ->Void, requestRedeliveryOfFailedMessages: @escaping (MessageId) -> Void, addContact: @escaping (String) -> Void, rateCall: @escaping (Message, CallId) -> Void, requestSelectMessagePollOption: @escaping (MessageId, Data) -> Void, openAppStorePage: @escaping () -> Void, displayMessageTooltip: @escaping (MessageId, String, ASDisplayNode?, CGRect?) -> Void, seekToTimecode: @escaping (Message, Double, Bool) -> Void, scheduleCurrentMessage: @escaping () -> Void, sendScheduledMessagesNow: @escaping ([MessageId]) -> Void, editScheduledMessagesTime: @escaping ([MessageId]) -> Void, performTextSelectionAction: @escaping (UInt32, String, TextSelectionAction) -> Void, updateMessageReaction: @escaping (MessageId, String?) -> Void, openMessageReactions: @escaping (MessageId) -> Void, displaySwipeToReplyHint: @escaping () -> Void, requestMessageUpdate: @escaping (MessageId) -> Void, cancelInteractiveKeyboardGestures: @escaping () -> Void, automaticMediaDownloadSettings: MediaAutoDownloadSettings, pollActionState: ChatInterfacePollActionState, stickerSettings: ChatInterfaceStickerSettings) { self.openMessage = openMessage self.openPeer = openPeer self.openPeerMention = openPeerMention @@ -180,7 +180,7 @@ public final class ChatControllerInteraction { static var `default`: ChatControllerInteraction { return ChatControllerInteraction(openMessage: { _, _ in - return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + return false }, openPeer: { _, _, _ in }, openPeerMention: { _ in }, openMessageContextMenu: { _, _, _, _, _ in }, openMessageContextActions: { _, _, _, _ in }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { _, _, _, _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, navigationController: { return nil }, chatControllerNode: { diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift index 54bef158ce..554a9b1f43 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageBubbleItemNode.swift @@ -208,7 +208,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode return false } if let singleUrl = accessibilityData.singleUrl { - strongSelf.item?.controllerInteraction.openUrl(singleUrl, false, false) + strongSelf.item?.controllerInteraction.openUrl(singleUrl, false, false, strongSelf.item?.content.firstMessage) } return false } @@ -2304,7 +2304,7 @@ class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePrevewItemNode break case let .url(url, concealed): foundTapAction = true - self.item?.controllerInteraction.openUrl(url, concealed, nil) + self.item?.controllerInteraction.openUrl(url, concealed, nil, self.item?.content.firstMessage) break loop case let .peerMention(peerId, _): foundTapAction = true diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift index b6b51c671b..84141ce0d7 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageItemView.swift @@ -763,7 +763,7 @@ public class ChatMessageItemView: ListViewItemNode { case .text: item.controllerInteraction.sendMessage(button.title) case let .url(url): - item.controllerInteraction.openUrl(url, true, nil) + item.controllerInteraction.openUrl(url, true, nil, nil) case .requestMap: item.controllerInteraction.shareCurrentLocation() case .requestPhone: diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift index d0a192ef72..b04c4517ea 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageTextBubbleContentNode.swift @@ -64,7 +64,7 @@ class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { self.addSubnode(self.textAccessibilityOverlayNode) self.textAccessibilityOverlayNode.openUrl = { [weak self] url in - self?.item?.controllerInteraction.openUrl(url, false, false) + self?.item?.controllerInteraction.openUrl(url, false, false, nil) } self.statusNode.openReactions = { [weak self] in diff --git a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift index 2dfdb1f388..2a7a5360b4 100644 --- a/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatMessageWebpageBubbleContentNode.swift @@ -149,7 +149,7 @@ final class ChatMessageWebpageBubbleContentNode: ChatMessageBubbleContentNode { } } if let webpage = webPageContent { - item.controllerInteraction.openUrl(webpage.url, false, nil) + item.controllerInteraction.openUrl(webpage.url, false, nil, nil) } } } diff --git a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift index ad7e3cf4ff..7994d97c62 100644 --- a/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/ChatRecentActionsControllerNode.swift @@ -195,7 +195,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, openMessageContextMenu: { [weak self] message, selectAll, node, frame, _ in self?.openMessageContextMenu(message: message, selectAll: selectAll, node: node, frame: frame) }, openMessageContextActions: { _, _, _, _ in - }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _ in + }, navigateToMessage: { _, _ in }, clickThroughMessage: { }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _ in return false }, sendGif: { _, _, _ in return false }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in }, openUrl: { [weak self] url, _, _, _ in self?.openUrl(url) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { @@ -800,7 +800,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self?.presentController(c, a) }, dismissInput: { self?.view.endEditing(true) - }) + }, contentContext: nil) case .wallpaper: break case .theme: diff --git a/submodules/TelegramUI/TelegramUI/ListMessageSnippetItemNode.swift b/submodules/TelegramUI/TelegramUI/ListMessageSnippetItemNode.swift index bd72494ca3..55a3757745 100644 --- a/submodules/TelegramUI/TelegramUI/ListMessageSnippetItemNode.swift +++ b/submodules/TelegramUI/TelegramUI/ListMessageSnippetItemNode.swift @@ -501,12 +501,12 @@ final class ListMessageSnippetItemNode: ListMessageNode { } } else { if isTelegramMeLink(content.url) || !item.controllerInteraction.openMessage(item.message, .link) { - item.controllerInteraction.openUrl(currentPrimaryUrl, false, false) + item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) } } } else { if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openUrl(currentPrimaryUrl, false, false) + item.controllerInteraction.openUrl(currentPrimaryUrl, false, false, nil) } } } @@ -556,10 +556,10 @@ final class ListMessageSnippetItemNode: ListMessageNode { item.controllerInteraction.longTap(ChatControllerInteractionLongTapAction.url(url), item.message) } else if url == self.currentPrimaryUrl { if !item.controllerInteraction.openMessage(item.message, .default) { - item.controllerInteraction.openUrl(url, false, false) + item.controllerInteraction.openUrl(url, false, false, nil) } } else { - item.controllerInteraction.openUrl(url, false, true) + item.controllerInteraction.openUrl(url, false, true, nil) } } case .hold, .doubleTap: diff --git a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift index 65ca9e0dd9..616989d0ac 100644 --- a/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift +++ b/submodules/TelegramUI/TelegramUI/OpenChatMessage.swift @@ -295,6 +295,12 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { params.navigationController?.pushViewController(controller) return true case let .stickerPack(reference): + if true { + let controller = StickerPackScreen(context: params.context, stickerPacks: [reference], selectedStickerPackIndex: 0, sendSticker: params.sendSticker) + params.dismissInput() + params.present(controller, nil) + return true + } let controller = StickerPackPreviewController(context: params.context, stickerPack: reference, parentNavigationController: params.navigationController) controller.sendSticker = params.sendSticker params.dismissInput() diff --git a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift index 6bd2dc85e8..f7547c8819 100644 --- a/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenResolvedUrl.swift @@ -17,6 +17,7 @@ import StickerPackPreviewUI import JoinLinkPreviewUI import LanguageLinkPreviewUI import SettingsUI +import UrlHandling private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatControllerInteractionNavigateToPeer) -> ChatControllerInteractionNavigateToPeer { if case .default = navigation { @@ -34,7 +35,7 @@ private func defaultNavigationForPeerId(_ peerId: PeerId?, navigation: ChatContr } } -func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) { +func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } switch resolvedUrl { case let .externalUrl(url): @@ -88,9 +89,47 @@ func openResolvedUrlImpl(_ resolvedUrl: ResolvedUrl, context: AccountContext, ur openPeer(peerId, .chat(textInputState: nil, subject: .message(messageId))) case let .stickerPack(name): dismissInput() - let controller = StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: navigationController) - controller.sendSticker = sendSticker - present(controller, nil) + if true { + var stickerPacks: [StickerPackReference] = [] + var initialIndex: Int = 0 + if let message = contentContext as? Message { + let dataDetector = try? NSDataDetector(types: NSTextCheckingResult.CheckingType([.link]).rawValue) + var foundMain = false + if let matches = dataDetector?.matches(in: message.text, options: [], range: NSRange(message.text.startIndex ..< message.text.endIndex, in: message.text)) { + for match in matches { + guard let stringRange = Range(match.range, in: message.text) else { + continue + } + let urlText = String(message.text[stringRange]) + if let resultName = parseStickerPackUrl(urlText) { + stickerPacks.append(.name(resultName)) + if resultName == name { + foundMain = true + initialIndex = stickerPacks.count - 1 + } + } + } + if !foundMain { + stickerPacks.insert(.name(name), at: 0) + initialIndex = 0 + } + } else { + stickerPacks = [.name(name)] + initialIndex = 0 + } + } else { + stickerPacks = [.name(name)] + initialIndex = 0 + } + if !stickerPacks.isEmpty { + let controller = StickerPackScreen(context: context, stickerPacks: stickerPacks, selectedStickerPackIndex: initialIndex, sendSticker: sendSticker) + present(controller, nil) + } + } else { + let controller = StickerPackPreviewController(context: context, stickerPack: .name(name), parentNavigationController: navigationController) + controller.sendSticker = sendSticker + present(controller, nil) + } case let .instantView(webpage, anchor): navigationController?.pushViewController(InstantPageController(context: context, webPage: webpage, sourcePeerType: .channel, anchor: anchor)) case let .join(link): diff --git a/submodules/TelegramUI/TelegramUI/OpenUrl.swift b/submodules/TelegramUI/TelegramUI/OpenUrl.swift index dd4ebfc627..386f67ae4a 100644 --- a/submodules/TelegramUI/TelegramUI/OpenUrl.swift +++ b/submodules/TelegramUI/TelegramUI/OpenUrl.swift @@ -237,7 +237,7 @@ func openExternalUrlImpl(context: AccountContext, urlContext: OpenURLContext, ur context.sharedContext.applicationBindings.getWindowHost()?.present(c, on: .root, blockInteraction: false, completion: {}) }, dismissInput: { dismissInput() - }) + }, contentContext: nil) } } diff --git a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift index 1e8e9d8c81..818bdce8d8 100644 --- a/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift +++ b/submodules/TelegramUI/TelegramUI/OverlayPlayerControllerNode.swift @@ -77,7 +77,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, UIGestu }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in - }, openUrl: { _, _, _ in + }, openUrl: { _, _, _, _ in }, shareCurrentLocation: { }, shareAccountContact: { }, sendBotCommand: { _, _ in diff --git a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift index 32b916f6e1..8838e42a43 100644 --- a/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift +++ b/submodules/TelegramUI/TelegramUI/PeerMediaCollectionController.swift @@ -328,7 +328,7 @@ public class PeerMediaCollectionController: TelegramBaseController { }, requestMessageActionCallback: { _, _, _ in }, requestMessageActionUrlAuth: { _, _, _ in }, activateSwitchInline: { _, _ in - }, openUrl: { [weak self] url, _, external in + }, openUrl: { [weak self] url, _, external, _ in self?.openUrl(url, external: external ?? false) }, shareCurrentLocation: { }, shareAccountContact: { @@ -774,7 +774,7 @@ public class PeerMediaCollectionController: TelegramBaseController { self?.present(c, in: .window(.root), with: a) }, dismissInput: { self?.view.endEditing(true) - }) + }, contentContext: nil) } })) } diff --git a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift index 996a40dafa..8130fe3194 100644 --- a/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift +++ b/submodules/TelegramUI/TelegramUI/SharedAccountContext.swift @@ -983,8 +983,8 @@ public final class SharedAccountContextImpl: SharedAccountContext { return resolveUrlImpl(account: account, url: url) } - public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void) { - openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, present: present, dismissInput: dismissInput) + public func openResolvedUrl(_ resolvedUrl: ResolvedUrl, context: AccountContext, urlContext: OpenURLContext, navigationController: NavigationController?, openPeer: @escaping (PeerId, ChatControllerInteractionNavigateToPeer) -> Void, sendFile: ((FileMediaReference) -> Void)?, sendSticker: ((FileMediaReference, ASDisplayNode, CGRect) -> Bool)?, present: @escaping (ViewController, Any?) -> Void, dismissInput: @escaping () -> Void, contentContext: Any?) { + openResolvedUrlImpl(resolvedUrl, context: context, urlContext: urlContext, navigationController: navigationController, openPeer: openPeer, sendFile: sendFile, sendSticker: sendSticker, present: present, dismissInput: dismissInput, contentContext: contentContext) } public func makeDeviceContactInfoController(context: AccountContext, subject: DeviceContactInfoSubject, completed: (() -> Void)?, cancelled: (() -> Void)?) -> ViewController { diff --git a/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift b/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift index e440f40f72..a8dd9455ec 100644 --- a/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift +++ b/submodules/TelegramUI/TelegramUI/TextLinkHandling.swift @@ -42,7 +42,7 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: PeerId?, navigate } }, sendFile: nil, sendSticker: nil, - present: presentImpl, dismissInput: {}) + present: presentImpl, dismissInput: {}, contentContext: nil) } let openLinkImpl: (String) -> Void = { [weak controller] url in diff --git a/submodules/UrlHandling/Sources/UrlHandling.swift b/submodules/UrlHandling/Sources/UrlHandling.swift index 75f53c78ae..98d45bc634 100644 --- a/submodules/UrlHandling/Sources/UrlHandling.swift +++ b/submodules/UrlHandling/Sources/UrlHandling.swift @@ -351,6 +351,28 @@ public func parseProxyUrl(_ url: String) -> (host: String, port: Int32, username return nil } +public func parseStickerPackUrl(_ url: String) -> String? { + let schemes = ["http://", "https://", ""] + let baseTelegramMePaths = ["telegram.me", "t.me"] + for basePath in baseTelegramMePaths { + for scheme in schemes { + let basePrefix = scheme + basePath + "/" + if url.lowercased().hasPrefix(basePrefix) { + if let internalUrl = parseInternalUrl(query: String(url[basePrefix.endIndex...])), case let .stickerPack(name) = internalUrl { + return name + } + } + } + } + if let parsedUrl = URL(string: url), parsedUrl.scheme == "tg", let host = parsedUrl.host, let query = parsedUrl.query { + if let internalUrl = parseInternalUrl(query: host + "?" + query), case let .stickerPack(name) = internalUrl { + return name + } + } + + return nil +} + public func parseWallpaperUrl(_ url: String) -> WallpaperUrlParameter? { let schemes = ["http://", "https://", ""] let baseTelegramMePaths = ["telegram.me", "t.me"]