diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index 83186fa567..0ff2b26bf7 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -10510,11 +10510,15 @@ Sorry for the inconvenience."; "Notification.GiveawayResultsNoWinners_1" = "Due to the giveaway terms, no winners could be selected by Telegram, a gift link was forwarded to channel administrators."; "Notification.GiveawayResultsNoWinners_any" = "Due to the giveaway terms, no winners could be selected by Telegram, all **%@** gift links were forwarded to channel administrators."; +"Notification.GiveawayResultsNoWinners.Group_1" = "Due to the giveaway terms, no winners could be selected by Telegram, a gift link was forwarded to group administrators."; +"Notification.GiveawayResultsNoWinners.Group_any" = "Due to the giveaway terms, no winners could be selected by Telegram, all **%@** gift links were forwarded to group administrators."; "Notification.GiveawayResultsMixedWinners_1" = "**%@** winner of the giveaway was randomly selected by Telegram and received their gift link in a private message."; "Notification.GiveawayResultsMixedWinners_any" = "**%@** winners of the giveaway were randomly selected by Telegram and received their gift links in private messages."; "Notification.GiveawayResultsMixedUnclaimed_1" = "**%@** undistributed gift link was forwarded to channel administrators"; "Notification.GiveawayResultsMixedUnclaimed_any" = "**%@** undistributed gift links were forwarded to channel administrators"; +"Notification.GiveawayResultsMixedUnclaimed.Group_1" = "**%@** undistributed gift link was forwarded to group administrators"; +"Notification.GiveawayResultsMixedUnclaimed.Group_any" = "**%@** undistributed gift links were forwarded to group administrators"; "Chat.Giveaway.DeleteConfirmation.Title" = "Do you want to delete the Giveaway Announcement?"; "Chat.Giveaway.DeleteConfirmation.Text" = "Deleting this message won't cancel the giveaway - the winners will still be selected on **%@**.\n\nOnce deleted, the Giveaway Announcement cannot be recovered."; @@ -12876,6 +12880,11 @@ Sorry for the inconvenience."; "BoostGift.Stars.Info" = "Choose how many stars to give away and how many boosts to receive for 1 year."; "BoostGift.AdditionalPrizesInfoStarsOff" = "Turn this on if you want to give the winners your own prizes in addition to Stars."; +"BoostGift.AdditionalPrizesInfoStars_1" = "**%@** Star"; +"BoostGift.AdditionalPrizesInfoStars_any" = "**%@** Stars"; +"BoostGift.AdditionalPrizesInfoStarsOn" = "All prizes: %1$@%2$@."; +"BoostGift.AdditionalPrizesInfoStarsAndOther" = " and **%1$@** %2$@"; + "BoostGift.Stars.Winners" = "NUMBER OF WINNERS"; "BoostGift.Stars.WinnersInfo" = "Choose how many winners you want to distribute stars among."; @@ -12915,3 +12924,6 @@ Sorry for the inconvenience."; "Premium.BoostByGiveawayDescription" = "Get more boosts and subscribers for your channel by giving away prizes. [Get boosts >]()"; "Premium.Group.BoostByGiveawayDescription" = "Get more boosts and members for your group by giving away prizes. [Get boosts >]()"; + +"Notification.StarsGiveawayResultsNoWinners" = "Due to the giveaway terms, no winners could be selected by Telegram, all stars were credited to channel administrators."; +"Notification.StarsGiveawayResultsNoWinners.Group" = "Due to the giveaway terms, no winners could be selected by Telegram, all stars were credited to group administrators."; diff --git a/submodules/AttachmentUI/Sources/AttachmentController.swift b/submodules/AttachmentUI/Sources/AttachmentController.swift index 0e99e3710c..4653f67ba9 100644 --- a/submodules/AttachmentUI/Sources/AttachmentController.swift +++ b/submodules/AttachmentUI/Sources/AttachmentController.swift @@ -213,6 +213,8 @@ public protocol AttachmentMediaPickerContext { var price: Int64? { get } func setPrice(_ price: Int64) -> Void + var hasTimers: Bool { get } + var loadingProgress: Signal { get } var mainButtonState: Signal { get } var secondaryButtonState: Signal { get } @@ -257,6 +259,10 @@ public extension AttachmentMediaPickerContext { func setPrice(_ price: Int64) -> Void { } + var hasTimers: Bool { + return false + } + var loadingProgress: Signal { return .single(nil) } diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index aab44bc395..4cbcb30cd0 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1004,10 +1004,12 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { var captionIsAboveMedia: Signal = .single(false) var canMakePaidContent = false var currentPrice: Int64? + var hasTimers = false if let controller = strongSelf.controller, let mediaPickerContext = controller.mediaPickerContext { captionIsAboveMedia = mediaPickerContext.captionIsAboveMedia canMakePaidContent = mediaPickerContext.canMakePaidContent currentPrice = mediaPickerContext.price + hasTimers = mediaPickerContext.hasTimers } let _ = (combineLatest( @@ -1039,7 +1041,8 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { canSendWhenOnline: sendWhenOnlineAvailable, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], canMakePaidContent: canMakePaidContent, - currentPrice: currentPrice + currentPrice: currentPrice, + hasTimers: hasTimers )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, diff --git a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift index 93acd7ab16..1f72255144 100644 --- a/submodules/BrowserUI/Sources/BrowserDocumentContent.swift +++ b/submodules/BrowserUI/Sources/BrowserDocumentContent.swift @@ -92,6 +92,17 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate self.webView.underPageBackgroundColor = presentationData.theme.list.plainBackgroundColor } self.addSubview(self.webView) + + self.webView.interactiveTransitionGestureRecognizerTest = { [weak self] point in + if let self { + if let result = self.webView.hitTest(point, with: nil), let scrollView = findScrollView(view: result), scrollView.isDescendant(of: self.webView) { + if scrollView.contentSize.width > scrollView.frame.width, scrollView.contentOffset.x > -scrollView.contentInset.left { + return true + } + } + } + return false + } } required init?(coder: NSCoder) { @@ -416,3 +427,14 @@ final class BrowserDocumentContent: UIView, BrowserContent, WKNavigationDelegate return imageView } } + +private func findScrollView(view: UIView?) -> UIScrollView? { + if let view = view { + if let view = view as? UIScrollView { + return view + } + return findScrollView(view: view.superview) + } else { + return nil + } +} diff --git a/submodules/BrowserUI/Sources/BrowserPdfContent.swift b/submodules/BrowserUI/Sources/BrowserPdfContent.swift index ca74de7ccd..2b1aec9f0f 100644 --- a/submodules/BrowserUI/Sources/BrowserPdfContent.swift +++ b/submodules/BrowserUI/Sources/BrowserPdfContent.swift @@ -117,12 +117,41 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF self.pageNumber = (1, self.pdfView.document?.pageCount ?? 1) self.startPageIndicatorTimer() + + self.pdfView.interactiveTransitionGestureRecognizerTest = { [weak self] point in + if let self { + if let result = self.pdfView.hitTest(point, with: nil), let scrollView = findScrollView(view: result), scrollView.isDescendant(of: self.pdfView) { + if scrollView.contentSize.width > scrollView.frame.width, scrollView.contentOffset.x > -scrollView.contentInset.left { + return true + } + } + } + return false + } + + NotificationCenter.default.addObserver(self, selector: #selector(self.pageChangeHandler(_:)), name: .PDFViewPageChanged, object: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + NotificationCenter.default.removeObserver(self, name: .PDFViewPageChanged, object: nil) + } + + @objc func pageChangeHandler(_ notification: Notification) { + if let document = self.pdfView.document, let page = self.pdfView.currentPage { + let number = document.index(for: page) + 1 + if number != self.pageNumber?.0 { + self.pageNumber = (number, document.pageCount) + if let (size, insets, fullInsets) = self.validLayout { + self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate) + } + } + } + } + func updatePresentationData(_ presentationData: PresentationData) { self.presentationData = presentationData if #available(iOS 15.0, *) { @@ -421,16 +450,6 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF } if !scrollView.isZooming && !self.wasZooming { self.updateScrollingOffset(isReset: false, transition: .immediate) - - if let document = self.pdfView.document, let page = self.pdfView.currentPage { - let number = document.index(for: page) + 1 - if number != self.pageNumber?.0 { - self.pageNumber = (number, document.pageCount) - if let (size, insets, fullInsets) = self.validLayout { - self.updateLayout(size: size, insets: insets, fullInsets: fullInsets, safeInsets: .zero, transition: .immediate) - } - } - } } } @@ -546,3 +565,14 @@ final class BrowserPdfContent: UIView, BrowserContent, UIScrollViewDelegate, PDF return nil } } + +private func findScrollView(view: UIView?) -> UIScrollView? { + if let view = view { + if let view = view as? UIScrollView { + return view + } + return findScrollView(view: view.superview) + } else { + return nil + } +} diff --git a/submodules/BrowserUI/Sources/BrowserScreen.swift b/submodules/BrowserUI/Sources/BrowserScreen.swift index be250f15ef..fc4a9e0af7 100644 --- a/submodules/BrowserUI/Sources/BrowserScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserScreen.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import SwiftSignalKit import Display +import Postbox import TelegramCore import TelegramPresentationData import ComponentFlow @@ -1473,10 +1474,19 @@ public class BrowserScreen: ViewController, MinimizableController { case instantPage(webPage: TelegramMediaWebpage, anchor: String?, sourceLocation: InstantPageSourceLocation, preloadedResources: [Any]?) case document(file: TelegramMediaFile, canShare: Bool) case pdfDocument(file: TelegramMediaFile, canShare: Bool) + + public var fileId: MediaId? { + switch self { + case let .document(file, _), let .pdfDocument(file, _): + return file.fileId + default: + return nil + } + } } private let context: AccountContext - fileprivate let subject: Subject + public let subject: Subject private var preferredConfiguration: WKWebViewConfiguration? private var openPreviousOnClose = false diff --git a/submodules/BrowserUI/Sources/BrowserWebContent.swift b/submodules/BrowserUI/Sources/BrowserWebContent.swift index 567111ff02..f4a3df64ba 100644 --- a/submodules/BrowserUI/Sources/BrowserWebContent.swift +++ b/submodules/BrowserUI/Sources/BrowserWebContent.swift @@ -740,7 +740,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU // }) // } else { if let url = navigationAction.request.url?.absoluteString { - if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") { + if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://")) && !url.contains("/auth/push?") && !self._state.url.contains("/auth/push?") { decisionHandler(.cancel, preferences) self.minimize() self.openAppUrl(url) @@ -776,7 +776,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if let url = navigationAction.request.url?.absoluteString { - if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url)) { + if (navigationAction.targetFrame == nil || navigationAction.targetFrame?.isMainFrame == true) && (isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://")) { decisionHandler(.cancel) self.minimize() self.openAppUrl(url) @@ -858,7 +858,10 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { if [-1003, -1100, 102].contains((error as NSError).code) { - self.currentError = error + if let url = (error as NSError).userInfo["NSErrorFailingURLKey"] as? URL, url.absoluteString.hasPrefix("itms-appss:") { + } else { + self.currentError = error + } } else { self.currentError = nil } @@ -870,7 +873,7 @@ final class BrowserWebContent: UIView, BrowserContent, WKNavigationDelegate, WKU func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? { if navigationAction.targetFrame == nil { if let url = navigationAction.request.url?.absoluteString { - if isTelegramMeLink(url) || isTelegraPhLink(url) { + if isTelegramMeLink(url) || isTelegraPhLink(url) || url.hasPrefix("tg://") { self.minimize() self.openAppUrl(url) } else { @@ -1350,7 +1353,7 @@ let setupFontFunctions = """ """ private let videoSource = """ -function disableWebkitEnterFullscreen(videoElement) { +function tgBrowserDisableWebkitEnterFullscreen(videoElement) { if (videoElement && videoElement.webkitEnterFullscreen) { Object.defineProperty(videoElement, 'webkitEnterFullscreen', { value: undefined @@ -1358,11 +1361,11 @@ function disableWebkitEnterFullscreen(videoElement) { } } -function disableFullscreenOnExistingVideos() { - document.querySelectorAll('video').forEach(disableWebkitEnterFullscreen); +function tgBrowserDisableFullscreenOnExistingVideos() { + document.querySelectorAll('video').forEach(tgBrowserDisableWebkitEnterFullscreen); } -function handleMutations(mutations) { +function tgBrowserHandleMutations(mutations) { mutations.forEach((mutation) => { if (mutation.addedNodes && mutation.addedNodes.length > 0) { mutation.addedNodes.forEach((newNode) => { @@ -1377,17 +1380,17 @@ function handleMutations(mutations) { }); } -disableFullscreenOnExistingVideos(); +tgBrowserDisableFullscreenOnExistingVideos(); -const observer = new MutationObserver(handleMutations); +const _tgbrowser_observer = new MutationObserver(tgBrowserHandleMutations); -observer.observe(document.body, { +_tgbrowser_observer.observe(document.body, { childList: true, subtree: true }); -function disconnectObserver() { - observer.disconnect(); +function tgBrowserDisconnectObserver() { + _tgbrowser_observer.disconnect(); } """ diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift index d209814b8d..9e87f7d610 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageActionSheetController.swift @@ -22,6 +22,7 @@ public enum SendMessageActionSheetControllerParams { public let forwardMessageIds: [EngineMessage.Id] public let canMakePaidContent: Bool public let currentPrice: Int64? + public let hasTimers: Bool public init( isScheduledMessages: Bool, @@ -32,7 +33,8 @@ public enum SendMessageActionSheetControllerParams { canSendWhenOnline: Bool, forwardMessageIds: [EngineMessage.Id], canMakePaidContent: Bool, - currentPrice: Int64? + currentPrice: Int64?, + hasTimers: Bool ) { self.isScheduledMessages = isScheduledMessages self.mediaPreview = mediaPreview @@ -43,6 +45,7 @@ public enum SendMessageActionSheetControllerParams { self.forwardMessageIds = forwardMessageIds self.canMakePaidContent = canMakePaidContent self.currentPrice = currentPrice + self.hasTimers = hasTimers } } diff --git a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift index a470303e9d..29125b33ac 100644 --- a/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift +++ b/submodules/ChatSendMessageActionUI/Sources/ChatSendMessageContextScreen.swift @@ -452,6 +452,9 @@ final class ChatSendMessageContextScreenComponent: Component { if sendMessage.isScheduledMessages { canSchedule = false } + if sendMessage.hasTimers { + canSchedule = false + } canMakePaidContent = sendMessage.canMakePaidContent currentPrice = sendMessage.currentPrice case .editMessage: diff --git a/submodules/ComposePollUI/Sources/ComposePollScreen.swift b/submodules/ComposePollUI/Sources/ComposePollScreen.swift index 19026f35d1..6628446965 100644 --- a/submodules/ComposePollUI/Sources/ComposePollScreen.swift +++ b/submodules/ComposePollUI/Sources/ComposePollScreen.swift @@ -488,6 +488,8 @@ final class ComposePollScreenComponent: Component { self.environment = environment if self.component == nil { + self.isQuiz = component.isQuiz ?? false + self.pollOptions.append(ComposePollScreenComponent.PollOption( id: self.nextPollOptionId )) @@ -1002,28 +1004,35 @@ final class ComposePollScreenComponent: Component { contentHeight += pollOptionsSectionFooterSize.height contentHeight += sectionSpacing + var canBePublic = true + if case let .channel(channel) = component.peer, case .broadcast = channel.info { + canBePublic = false + } + var pollSettingsSectionItems: [AnyComponentWithIdentity] = [] - pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent( - theme: environment.theme, - title: AnyComponent(VStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString( - string: environment.strings.CreatePoll_Anonymous, - font: Font.regular(presentationData.listsFontSize.baseDisplaySize), - textColor: environment.theme.list.itemPrimaryTextColor - )), - maximumNumberOfLines: 1 - ))), - ], alignment: .left, spacing: 2.0)), - accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isAnonymous, action: { [weak self] _ in - guard let self else { - return - } - self.isAnonymous = !self.isAnonymous - self.state?.updated(transition: .spring(duration: 0.4)) - })), - action: nil - )))) + if canBePublic { + pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "anonymous", component: AnyComponent(ListActionItemComponent( + theme: environment.theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: environment.strings.CreatePoll_Anonymous, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.isAnonymous, action: { [weak self] _ in + guard let self else { + return + } + self.isAnonymous = !self.isAnonymous + self.state?.updated(transition: .spring(duration: 0.4)) + })), + action: nil + )))) + } pollSettingsSectionItems.append(AnyComponentWithIdentity(id: "multiAnswer", component: AnyComponent(ListActionItemComponent( theme: environment.theme, title: AnyComponent(VStack([ @@ -1569,7 +1578,7 @@ public class ComposePollScreen: ViewControllerComponentContainer, AttachmentCont public static func initialData(context: AccountContext) -> InitialData { return InitialData( - maxPollTextLength: Int(255), + maxPollTextLength: Int(200), maxPollOptionLength: 100 ) } diff --git a/submodules/ComposePollUI/Sources/CreatePollController.swift b/submodules/ComposePollUI/Sources/CreatePollController.swift index 91d027f899..86fb01227b 100644 --- a/submodules/ComposePollUI/Sources/CreatePollController.swift +++ b/submodules/ComposePollUI/Sources/CreatePollController.swift @@ -134,7 +134,7 @@ private struct OrderedLinkedList: Sequence, Equatable { } } -private let maxTextLength = 255 +private let maxTextLength = 200 private let maxOptionLength = 100 private let maxOptionCount = 10 diff --git a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift index c73e666e38..127cce67fa 100644 --- a/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift +++ b/submodules/GalleryUI/Sources/ChatItemGalleryFooterContentNode.swift @@ -1714,6 +1714,7 @@ final class ChatItemGalleryFooterContentNode: GalleryFooterContentNode, ASScroll shareController.dismissed = { [weak self] _ in self?.interacting?(false) } + shareController.actionCompleted = { [weak self] in if let strongSelf = self, let actionCompletionText = actionCompletionText { let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index 1a39e933c7..bb911f25ea 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -259,6 +259,7 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { private let dataDisposable = MetaDisposable() private let recognitionDisposable = MetaDisposable() private var status: MediaResourceStatus? + private var fetchedDimensions: PixelDimensions? private let pagingEnabledPromise = ValuePromise(true) @@ -806,9 +807,9 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { }) } - func setFile(context: AccountContext, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference) { - if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) { - if var largestSize = fileReference.media.dimensions { + func setFile(context: AccountContext, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, force: Bool = false) { + if self.contextAndMedia == nil || !self.contextAndMedia!.1.media.isEqual(to: fileReference.media) || force { + if var largestSize = (fileReference.media.dimensions ?? self.fetchedDimensions) { var displaySize = largestSize.cgSize.dividedByScreenScale() if let previewDimensions = largestImageRepresentation(fileReference.media.previewRepresentations)?.dimensions { let previewAspect = CGFloat(previewDimensions.width) / CGFloat(previewDimensions.height) @@ -848,6 +849,22 @@ final class ChatImageGalleryItemNode: ZoomableContentGalleryItemNode { self.fetchDisposable.set(fetchedMediaResource(mediaBox: self.context.account.postbox.mediaBox, userLocation: userLocation, userContentType: .image, reference: fileReference.resourceReference(fileReference.media.resource)).start()) } else { + let _ = (chatMessageFileDatas(account: context.account, userLocation: userLocation, fileReference: fileReference, progressive: false, fetched: true) + |> mapToSignal { value -> Signal in + if value._2, let path = value._1, let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + return .single(UIImage(data: data)) + } + return .complete() + } + |> deliverOnMainQueue).start(next: { [weak self] image in + if let self, let image { + self.fetchedDimensions = PixelDimensions(image.size) + self.setFile(context: context, userLocation: userLocation, fileReference: fileReference, force: true) + } + }) + + + self._ready.set(.single(Void())) } } diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index d190cac16e..3fab113259 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -2775,6 +2775,20 @@ final class MediaPickerContext: AttachmentMediaPickerContext { } } + var hasTimers: Bool { + guard let controller = self.controller else { + return false + } + if let selectionContext = controller.interaction?.selectionState, let editingContext = controller.interaction?.editingState { + for case let item as TGMediaEditableItem in selectionContext.selectedItems() { + if let time = editingContext.timer(for: item), time.intValue > 0 { + return true + } + } + } + return false + } + var captionIsAboveMedia: Signal { return Signal { [weak self] subscriber in guard let interaction = self?.controller?.interaction else { diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index b671c67ea4..39f2a74a06 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -271,7 +271,7 @@ func chatMessagePhotoDatas(mediaBox: MediaBox, userLocation: MediaResourceUserLo } } -private func chatMessageFileDatas(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal, NoError> { +public func chatMessageFileDatas(account: Account, userLocation: MediaResourceUserLocation, fileReference: FileMediaReference, pathExtension: String? = nil, progressive: Bool = false, fetched: Bool = false) -> Signal, NoError> { let thumbnailResource = fetched ? nil : smallestImageRepresentation(fileReference.media.previewRepresentations)?.resource let fullSizeResource = fileReference.media.resource diff --git a/submodules/PremiumUI/Sources/CreateGiveawayController.swift b/submodules/PremiumUI/Sources/CreateGiveawayController.swift index d0fa152f3f..69d697d14a 100644 --- a/submodules/PremiumUI/Sources/CreateGiveawayController.swift +++ b/submodules/PremiumUI/Sources/CreateGiveawayController.swift @@ -784,6 +784,7 @@ private func createGiveawayControllerEntries( entries.append(.prepaid(presentationData.theme, title, text, prepaidGiveaway)) } + var starsPerUser: Int64 = 0 if case .starsGiveaway = state.mode, !starsGiveawayOptions.isEmpty { let selectedOption = starsGiveawayOptions.first(where: { $0.giveawayOption.count == state.stars })! entries.append(.starsHeader(presentationData.theme, presentationData.strings.BoostGift_Stars_Title.uppercased(), presentationData.strings.BoostGift_Stars_Boosts(selectedOption.giveawayOption.yearlyBoosts).uppercased())) @@ -800,6 +801,7 @@ private func createGiveawayControllerEntries( let subtitle = presentationData.strings.BoostGift_Stars_PerUser("\(winners.starsPerUser)").string let label = product.storeProduct.price + starsPerUser = winners.starsPerUser let isSelected = product.giveawayOption.count == state.stars entries.append(.stars(i, presentationData.theme, Int32(product.giveawayOption.count), giftTitle, subtitle, label, isSelected, maxWinners)) @@ -933,14 +935,24 @@ private func createGiveawayControllerEntries( if state.showPrizeDescription { entries.append(.prizeDescriptionText(presentationData.theme, presentationData.strings.BoostGift_AdditionalPrizesPlaceholder, state.prizeDescription, state.subscriptions)) - let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 12) - if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { - let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "") - prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string + if state.mode == .starsGiveaway { + let starsString = presentationData.strings.BoostGift_AdditionalPrizesInfoStars(Int32(state.stars)) + if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + let _ = starsPerUser + prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOn(starsString, "").string + } else { + prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoStarsOn(starsString, presentationData.strings.BoostGift_AdditionalPrizesInfoStarsAndOther("\(state.winners)", state.prizeDescription).string).string + } } else { - let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoWithSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "") - let description = "\(state.prizeDescription) \(subscriptionsString)" - prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", description, monthsString).string + let monthsString = presentationData.strings.BoostGift_AdditionalPrizesInfoForMonths(state.selectedMonths ?? 12) + if state.prizeDescription.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty { + let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "") + prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", subscriptionsString, monthsString).string + } else { + let subscriptionsString = presentationData.strings.BoostGift_AdditionalPrizesInfoWithSubscriptions(state.subscriptions).replacingOccurrences(of: "\(state.subscriptions) ", with: "") + let description = "\(state.prizeDescription) \(subscriptionsString)" + prizeDescriptionInfoText = presentationData.strings.BoostGift_AdditionalPrizesInfoOn("\(state.subscriptions)", description, monthsString).string + } } } entries.append(.prizeDescriptionInfo(presentationData.theme, prizeDescriptionInfoText)) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index abd316940f..4f7efc9cc0 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -241,7 +241,6 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } else { type = .video } - break inner case let .Audio(isVoice, _, _, _, _): if isVoice { type = .audio @@ -981,12 +980,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, attributedString = addAttributesToStringWithRanges(resultTitleString._tuple, body: bodyAttributes, argumentAttributes: [0: boldAttributes]) case .joinedChannel: attributedString = NSAttributedString(string: strings.Notification_ChannelJoinedByYou, font: titleBoldFont, textColor: primaryTextColor) - case let .giveawayResults(winners, unclaimed, _): + case let .giveawayResults(winners, unclaimed, stars): + var isGroup = false + let messagePeer = message.peers[message.id.peerId] + if let channel = messagePeer as? TelegramChannel, case .group = channel.info { + isGroup = true + } if winners == 0 { - attributedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) + if stars { + attributedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_StarsGiveawayResultsNoWinners_Group : strings.Notification_StarsGiveawayResultsNoWinners, attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) + } else { + attributedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_GiveawayResultsNoWinners_Group(unclaimed) : strings.Notification_GiveawayResultsNoWinners(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) + } } else if unclaimed > 0 { let winnersString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsMixedWinners(winners), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) - let unclaimedString = parseMarkdownIntoAttributedString(strings.Notification_GiveawayResultsMixedUnclaimed(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) + let unclaimedString = parseMarkdownIntoAttributedString(isGroup ? strings.Notification_GiveawayResultsMixedUnclaimed_Group(unclaimed) : strings.Notification_GiveawayResultsMixedUnclaimed(unclaimed), attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) let combinedString = NSMutableAttributedString(attributedString: winnersString) combinedString.append(NSAttributedString(string: "\n")) combinedString.append(unclaimedString) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift index 31755e895a..e939dc18e6 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGiveawayBubbleContentNode/Sources/ChatMessageGiveawayBubbleContentNode.swift @@ -289,9 +289,10 @@ public class ChatMessageGiveawayBubbleContentNode: ChatMessageBubbleContentNode, badgeString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: badgeString.string)) } + let badgeBackgroundColor = !incoming || !isStars ? accentColor : UIColor(rgb: 0xffaf0a) var updatedBadgeImage: UIImage? if themeUpdated { - updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: isStars ? UIColor(rgb: 0xffaf0a) : accentColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil) + updatedBadgeImage = generateStretchableFilledCircleImage(diameter: 21.0, color: badgeBackgroundColor, strokeColor: backgroundColor, strokeWidth: 1.0 + UIScreenPixel, backgroundColor: nil) } let prizeTitleText: String diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift index bd257a6029..63c25ed5f0 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageTextBubbleContentNode/Sources/ChatMessageTextBubbleContentNode.swift @@ -995,7 +995,7 @@ public class ChatMessageTextBubbleContentNode: ChatMessageBubbleContentNode { return ChatMessageBubbleContentTapAction(content: .none) } else { if let text = self.textNode.textNode.attributeSubstring(name: "Attribute__Blockquote", index: index) { - return ChatMessageBubbleContentTapAction(content: .copy(text.1)) + return ChatMessageBubbleContentTapAction(content: .copy(text.0)) } else { return ChatMessageBubbleContentTapAction(content: .none) } diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index 335f76f486..eb39d8ae64 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -322,7 +322,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { self.expandedDeletedMessages.insert(messageId) } }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in - self?.openUrl(url.url) + self?.openUrl(url.url, progress: url.progress) }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { [weak self] message, associatedData in if let strongSelf = self, let navigationController = strongSelf.getNavigationController() { if let controller = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, message: message, sourcePeerType: associatedData?.automaticDownloadPeerType) { @@ -628,6 +628,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { }, playMessageEffect: { _ in }, editMessageFactCheck: { _ in }, requestMessageUpdate: { _, _ in + }, cancelInteractiveKeyboardGestures: { }, dismissTextInput: { }, scrollToMessageId: { _ in @@ -1159,7 +1160,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { } } - private func openUrl(_ url: String) { + private func openUrl(_ url: String, progress: Promise? = nil) { self.navigationActionDisposable.set((self.context.sharedContext.resolveUrl(context: self.context, peerId: nil, url: url, skipUrlAuth: true) |> deliverOnMainQueue).startStrict(next: { [weak self] result in if let strongSelf = self { switch result { @@ -1235,11 +1236,104 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let browserController = strongSelf.context.sharedContext.makeInstantPageController(context: strongSelf.context, webPage: webPage, anchor: anchor, sourceLocation: InstantPageSourceLocation(userLocation: .peer(strongSelf.peer.id), peerType: .channel)) strongSelf.pushController(browserController) case let .join(link): - strongSelf.presentController(JoinLinkPreviewController(context: strongSelf.context, link: link, navigateToPeer: { peer, peekData in - if let strongSelf = self { - strongSelf.openPeer(peer: peer, peekData: peekData) + let context = strongSelf.context + let navigationController = strongSelf.getNavigationController() + let openPeer: (EnginePeer, ChatPeekTimeout?) -> Void = { [weak self] peer, peekData in + self?.openPeer(peer: peer, peekData: peekData) + } + + if let progress { + let progressSignal = Signal { subscriber in + progress.set(.single(true)) + return ActionDisposable { + Queue.mainQueue().async() { + progress.set(.single(false)) + } + } } - }, parentNavigationController: strongSelf.getNavigationController()), .window(.root), nil) + |> runOn(Queue.mainQueue()) + |> delay(0.1, queue: Queue.mainQueue()) + let progressDisposable = progressSignal.startStrict() + + var signal = context.engine.peers.joinLinkInformation(link) + signal = signal + |> afterDisposed { + Queue.mainQueue().async { + progressDisposable.dispose() + } + } + + let _ = (signal + |> deliverOnMainQueue).startStandalone(next: { [weak navigationController] resolvedState in + switch resolvedState { + case let .alreadyJoined(peer): + openPeer(peer, nil) + case let .peek(peer, deadline): + openPeer(peer, ChatPeekTimeout(deadline: deadline, linkData: link)) + case let .invite(invite): + if let subscriptionPricing = invite.subscriptionPricing, let subscriptionFormId = invite.subscriptionFormId, let starsContext = context.starsContext { + let inputData = Promise() + var photo: [TelegramMediaImageRepresentation] = [] + if let photoRepresentation = invite.photoRepresentation { + photo.append(photoRepresentation) + } + let channel = TelegramChannel(id: PeerId(namespace: Namespaces.Peer.CloudChannel, id: PeerId.Id._internalFromInt64Value(0)), accessHash: .genericPublic(0), title: invite.title, username: nil, photo: photo, creationDate: 0, version: 0, participationStatus: .left, info: .broadcast(TelegramChannelBroadcastInfo(flags: [])), flags: [], restrictionInfo: nil, adminRights: nil, bannedRights: nil, defaultBannedRights: nil, usernames: [], storiesHidden: nil, nameColor: invite.nameColor, backgroundEmojiId: nil, profileColor: nil, profileBackgroundEmojiId: nil, emojiStatus: nil, approximateBoostLevel: nil, subscriptionUntilDate: nil) + let invoice = TelegramMediaInvoice(title: "", description: "", photo: nil, receiptMessageId: nil, currency: "XTR", totalAmount: subscriptionPricing.amount, startParam: "", extendedMedia: nil, flags: [], version: 0) + + inputData.set(.single(BotCheckoutController.InputData( + form: BotPaymentForm( + id: subscriptionFormId, + canSaveCredentials: false, + passwordMissing: false, + invoice: BotPaymentInvoice(isTest: false, requestedFields: [], currency: "XTR", prices: [BotPaymentPrice(label: "", amount: subscriptionPricing.amount)], tip: nil, termsInfo: nil), + paymentBotId: channel.id, + providerId: nil, + url: nil, + nativeProvider: nil, + savedInfo: nil, + savedCredentials: [], + additionalPaymentMethods: [] + ), + validatedFormInfo: nil, + botPeer: EnginePeer(channel) + ))) + + let starsInputData = combineLatest( + inputData.get(), + starsContext.state + ) + |> map { data, state -> (StarsContext.State, BotPaymentForm, EnginePeer?, EnginePeer?)? in + if let data, let state { + return (state, data.form, data.botPeer, nil) + } else { + return nil + } + } + let _ = (starsInputData + |> SwiftSignalKit.filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).start(next: { _ in + let controller = context.sharedContext.makeStarsSubscriptionTransferScreen(context: context, starsContext: starsContext, invoice: invoice, link: link, inputData: starsInputData, navigateToPeer: { peer in + openPeer(peer, nil) + }) + navigationController?.pushViewController(controller) + }) + } else { + strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, peekData) + }, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil) + } + default: + strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, peekData) + }, parentNavigationController: navigationController, resolvedState: resolvedState), .window(.root), nil) + } + }) + } else { + strongSelf.presentController(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in + openPeer(peer, peekData) + }, parentNavigationController: navigationController), .window(.root), nil) + } case let .localization(identifier): strongSelf.presentController(LanguageLinkPreviewController(context: strongSelf.context, identifier: identifier), .window(.root), nil) case .proxy, .confirmationCode, .cancelAccountReset, .share: diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 16575e3244..a7b50740e5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1746,7 +1746,7 @@ private final class ChatSendStarsScreenComponent: Component { self.topPeerItems[topPeer.id] = itemView } - let itemCountString = "\(topPeer.count)" + let itemCountString = presentationStringsFormattedNumber(Int32(topPeer.count), environment.dateTimeFormat.groupingSeparator) /*if topPeer.isMy && myCountAddition != 0 && topPeer.count > myCountAddition { itemCountString = "\(topPeer.count - myCountAddition) +\(myCountAddition)" }*/ diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 3f1c558dd8..934bc52777 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -711,7 +711,8 @@ final class PeerSelectionControllerNode: ASDisplayNode { canSendWhenOnline: false, forwardMessageIds: strongSelf.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], canMakePaidContent: false, - currentPrice: nil + currentPrice: nil, + hasTimers: false )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift index 6c7f3779a9..798f9bf023 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageDisplaySendMessageOptions.swift @@ -235,7 +235,8 @@ func chatMessageDisplaySendMessageOptions(selfController: ChatControllerImpl, no canSendWhenOnline: sendWhenOnlineAvailable, forwardMessageIds: selfController.presentationInterfaceState.interfaceState.forwardMessageIds ?? [], canMakePaidContent: false, - currentPrice: nil + currentPrice: nil, + hasTimers: false )), hasEntityKeyboard: hasEntityKeyboard, gesture: gesture, diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index ab21f3e8f6..1eba3b6a34 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4748,207 +4748,209 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G isMuted = peerIsMuted } - items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in - if isMuted { - let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0) - |> deliverOnMainQueue).startStandalone(completed: { - f(.default) - }) - } else { - var items: [ContextMenuItem] = [] - - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor) - }, action: { c, _ in - var subItems: [ContextMenuItem] = [] - - let presetValues: [Int32] = [ - 1 * 60 * 60, - 8 * 60 * 60, - 1 * 24 * 60 * 60, - 7 * 24 * 60 * 60 - ] + if !"".isEmpty { + items.append(.action(ContextMenuActionItem(text: isMuted ? presentationData.strings.ChatList_Context_Unmute : presentationData.strings.ChatList_Context_Mute, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: isMuted ? "Chat/Context Menu/Unmute" : "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) }, action: { [weak self] c, f in + if isMuted { + let _ = (context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: 0) + |> deliverOnMainQueue).startStandalone(completed: { + f(.default) + }) + } else { + var items: [ContextMenuItem] = [] - for value in presetValues { - subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteFor, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Mute2d"), color: theme.contextMenu.primaryColor) + }, action: { c, _ in + var subItems: [ContextMenuItem] = [] + + let presetValues: [Int32] = [ + 1 * 60 * 60, + 8 * 60 * 60, + 1 * 24 * 60 * 60, + 7 * 24 * 60 * 60 + ] + + for value in presetValues { + subItems.append(.action(ContextMenuActionItem(text: muteForIntervalString(strings: presentationData.strings, value: value), icon: { _ in + return nil + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } + + subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in return nil }, action: { _, f in f(.default) - let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: value).startStandalone() - - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_mute_for", scale: 0.066, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedFor(mutedForTimeIntervalString(strings: presentationData.strings, value: value)).string, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + // if let chatListController = chatListController { + // openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController) + // } }))) - } - - subItems.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForCustom, icon: { _ in - return nil - }, action: { _, f in - f(.default) -// if let chatListController = chatListController { -// openCustomMute(context: context, peerId: peerId, threadId: threadId, baseController: chatListController) -// } + c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) }))) - c?.setItems(.single(ContextController.Items(content: .list(subItems))), minHeight: nil, animated: true) - }))) - - items.append(.separator) - - var isSoundEnabled = true - switch threadData.notificationSettings.messageSound { - case .none: - isSoundEnabled = false - default: - break - } - - if case .muted = threadData.notificationSettings.muteState { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.default) - - let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone() - - let iconColor: UIColor = .white - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ + items.append(.separator) + + var isSoundEnabled = true + switch threadData.notificationSettings.messageSound { + case .none: + isSoundEnabled = false + default: + break + } + + if case .muted = threadData.notificationSettings.muteState { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_ButtonUnmute, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: nil).startStandalone() + + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profileunmute", scale: 0.075, colors: [ "Middle.Group 1.Fill 1": iconColor, "Top.Group 1.Fill 1": iconColor, "Bottom.Group 1.Fill 1": iconColor, "EXAMPLE.Group 1.Fill 1": iconColor, "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } else if !isSoundEnabled { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.default) - - let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone() - - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } else { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.default) - - let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone() - - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - } - - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) - }, action: { _, f in - f(.dismissWithoutContent) + ], title: nil, text: presentationData.strings.PeerInfo_TooltipUnmuted, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else if !isSoundEnabled { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_EnableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOn"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .default).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_on", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundEnabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } else { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_DisableSound, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/SoundOff"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: .none).startStandalone() + + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_sound_off", scale: 0.056, colors: [:], title: nil, text: presentationData.strings.PeerInfo_TooltipSoundDisabled, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) + } - let _ = (context.engine.data.get( - TelegramEngine.EngineData.Item.NotificationSettings.Global() - ) - |> deliverOnMainQueue).startStandalone(next: { globalSettings in - let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue - } + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_NotificationsCustomize, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) - let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in - return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue - } - - let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { - peerId, displayPreviews in - return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue - } - - let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { - peerId, mute in - return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue - } - - let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { - peerId, hideSender in - return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue - } - - let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in - return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue - } - - let defaultSound: PeerMessageSound - - if case .broadcast = channel.info { - defaultSound = globalSettings.channels.sound._asMessageSound() - } else { - defaultSound = globalSettings.groupChats.sound._asMessageSound() - } - - let canRemove = false - - let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in - let _ = (updatePeerSound(peerId, sound) - |> deliverOnMainQueue).startStandalone(next: { _ in + let _ = (context.engine.data.get( + TelegramEngine.EngineData.Item.NotificationSettings.Global() + ) + |> deliverOnMainQueue).startStandalone(next: { globalSettings in + let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerNotificationSoundInteractive(peerId: peerId, threadId: threadId, sound: sound) |> deliverOnMainQueue + } + + let updatePeerNotificationInterval: (PeerId, Int32?) -> Signal = { peerId, muteInterval in + return context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: muteInterval) |> deliverOnMainQueue + } + + let updatePeerDisplayPreviews: (PeerId, PeerNotificationDisplayPreviews) -> Signal = { + peerId, displayPreviews in + return context.engine.peers.updatePeerDisplayPreviewsSetting(peerId: peerId, threadId: threadId, displayPreviews: displayPreviews) |> deliverOnMainQueue + } + + let updatePeerStoriesMuted: (PeerId, PeerStoryNotificationSettings.Mute) -> Signal = { + peerId, mute in + return context.engine.peers.updatePeerStoriesMutedSetting(peerId: peerId, mute: mute) |> deliverOnMainQueue + } + + let updatePeerStoriesHideSender: (PeerId, PeerStoryNotificationSettings.HideSender) -> Signal = { + peerId, hideSender in + return context.engine.peers.updatePeerStoriesHideSenderSetting(peerId: peerId, hideSender: hideSender) |> deliverOnMainQueue + } + + let updatePeerStorySound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in + return context.engine.peers.updatePeerStorySoundInteractive(peerId: peerId, sound: sound) |> deliverOnMainQueue + } + + let defaultSound: PeerMessageSound + + if case .broadcast = channel.info { + defaultSound = globalSettings.channels.sound._asMessageSound() + } else { + defaultSound = globalSettings.groupChats.sound._asMessageSound() + } + + let canRemove = false + + let exceptionController = notificationPeerExceptionController(context: context, updatedPresentationData: nil, peer: .channel(channel), threadId: threadId, isStories: nil, canRemove: canRemove, defaultSound: defaultSound, defaultStoriesSound: defaultSound, edit: true, updatePeerSound: { peerId, sound in + let _ = (updatePeerSound(peerId, sound) + |> deliverOnMainQueue).startStandalone(next: { _ in + }) + }, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in + let _ = (updatePeerNotificationInterval(peerId, muteInterval) + |> deliverOnMainQueue).startStandalone(next: { _ in + if let muteInterval = muteInterval, muteInterval == Int32.max { + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + } + }) + }, updatePeerDisplayPreviews: { peerId, displayPreviews in + let _ = (updatePeerDisplayPreviews(peerId, displayPreviews) + |> deliverOnMainQueue).startStandalone(next: { _ in + + }) + }, updatePeerStoriesMuted: { peerId, mute in + let _ = (updatePeerStoriesMuted(peerId, mute) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStoriesHideSender: { peerId, hideSender in + let _ = (updatePeerStoriesHideSender(peerId, hideSender) + |> deliverOnMainQueue).startStandalone() + }, updatePeerStorySound: { peerId, sound in + let _ = (updatePeerStorySound(peerId, sound) + |> deliverOnMainQueue).startStandalone() + }, removePeerFromExceptions: { + }, modifiedPeer: { }) - }, updatePeerNotificationInterval: { [weak self] peerId, muteInterval in - let _ = (updatePeerNotificationInterval(peerId, muteInterval) - |> deliverOnMainQueue).startStandalone(next: { _ in - if let muteInterval = muteInterval, muteInterval == Int32.max { - let iconColor: UIColor = .white - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - } - }) - }, updatePeerDisplayPreviews: { peerId, displayPreviews in - let _ = (updatePeerDisplayPreviews(peerId, displayPreviews) - |> deliverOnMainQueue).startStandalone(next: { _ in - - }) - }, updatePeerStoriesMuted: { peerId, mute in - let _ = (updatePeerStoriesMuted(peerId, mute) - |> deliverOnMainQueue).startStandalone() - }, updatePeerStoriesHideSender: { peerId, hideSender in - let _ = (updatePeerStoriesHideSender(peerId, hideSender) - |> deliverOnMainQueue).startStandalone() - }, updatePeerStorySound: { peerId, sound in - let _ = (updatePeerStorySound(peerId, sound) - |> deliverOnMainQueue).startStandalone() - }, removePeerFromExceptions: { - }, modifiedPeer: { + exceptionController.navigationPresentation = .modal + self?.push(exceptionController) }) - exceptionController.navigationPresentation = .modal - self?.push(exceptionController) - }) - }))) - - items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor) - }, action: { _, f in - f(.default) + }))) - let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone() + items.append(.action(ContextMenuActionItem(text: presentationData.strings.PeerInfo_MuteForever, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.destructiveColor) + }, action: { _, f in + f(.default) + + let _ = context.engine.peers.updatePeerMuteSetting(peerId: peerId, threadId: threadId, muteInterval: Int32.max).startStandalone() + + let iconColor: UIColor = .white + self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ + "Middle.Group 1.Fill 1": iconColor, + "Top.Group 1.Fill 1": iconColor, + "Bottom.Group 1.Fill 1": iconColor, + "EXAMPLE.Group 1.Fill 1": iconColor, + "Line.Group 1.Stroke 1": iconColor + ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) + }))) - let iconColor: UIColor = .white - self?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_profilemute", scale: 0.075, colors: [ - "Middle.Group 1.Fill 1": iconColor, - "Top.Group 1.Fill 1": iconColor, - "Bottom.Group 1.Fill 1": iconColor, - "EXAMPLE.Group 1.Fill 1": iconColor, - "Line.Group 1.Stroke 1": iconColor - ], title: nil, text: presentationData.strings.PeerInfo_TooltipMutedForever, customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: true, action: { _ in return false }), in: .current) - }))) - - c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) - } - }))) + c?.setItems(.single(ContextController.Items(content: .list(items))), minHeight: nil, animated: true) + } + }))) + } items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Conversation_Search, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Search"), color: theme.actionSheet.primaryTextColor) @@ -4973,13 +4975,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = context.engine.peers.setForumChannelTopicClosed(id: peer.id, threadId: threadId, isClosed: !threadData.isClosed).startStandalone() }))) } -// if channel.hasPermission(.deleteAllMessages) { -// items.append(.action(ContextMenuActionItem(text: strings.ChatList_Context_Delete, textColor: .destructive, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.contextMenu.destructiveColor) }, action: { [weak chatListController] _, f in -// f(.default) -// -// chatListController?.deletePeerThread(peerId: peerId, threadId: threadId) -// }))) -// } } return items diff --git a/submodules/TelegramUI/Sources/OpenChatMessage.swift b/submodules/TelegramUI/Sources/OpenChatMessage.swift index 816c479655..a7712b527b 100644 --- a/submodules/TelegramUI/Sources/OpenChatMessage.swift +++ b/submodules/TelegramUI/Sources/OpenChatMessage.swift @@ -239,6 +239,15 @@ func openChatMessageImpl(_ params: OpenChatMessageParams) -> Bool { useBrowserScreen = true } if useBrowserScreen { + if let navigationController = params.navigationController, let minimizedContainer = navigationController.minimizedContainer { + for controller in minimizedContainer.controllers { + if let controller = controller as? BrowserScreen, controller.subject.fileId == file.fileId { + navigationController.maximizeViewController(controller, animated: true) + return + } + } + } + let subject: BrowserScreen.Subject if file.mimeType == "application/pdf" { subject = .pdfDocument(file: file, canShare: canShare) diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index d387a59935..5aae48a9d0 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -48,6 +48,14 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n if let navigationController = controller?.navigationController as? NavigationController { context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), botStart: botStart, keepStack: .always)) } + case let .withAttachBot(attachBotStart): + if let navigationController = controller?.navigationController as? NavigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), attachBotStart: attachBotStart)) + } + case let .withBotApp(botAppStart): + if let navigationController = controller?.navigationController as? NavigationController { + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(peer), botAppStart: botAppStart)) + } default: break } @@ -92,15 +100,13 @@ func handleTextLinkActionImpl(context: AccountContext, peerId: EnginePeer.Id?, n let sourceLocation = InstantPageSourceLocation(userLocation: peerId.flatMap(MediaResourceUserLocation.peer) ?? .other, peerType: .group) let browserController = context.sharedContext.makeInstantPageController(context: context, webPage: webPage, anchor: anchor, sourceLocation: sourceLocation) (controller.navigationController as? NavigationController)?.pushViewController(browserController, animated: true) -// case let .join(link): -// controller.present(JoinLinkPreviewController(context: context, link: link, navigateToPeer: { peer, peekData in -// openResolvedPeerImpl(peer, .chat(textInputState: nil, subject: nil, peekData: peekData)) -// }, parentNavigationController: controller.navigationController as? NavigationController), in: .window(.root)) case .boost, .chatFolder, .join: if let navigationController = controller.navigationController as? NavigationController { openResolvedUrlImpl(result, context: context, urlContext: peerId.flatMap { .chat(peerId: $0, message: nil, updatedPresentationData: nil) } ?? .generic, navigationController: navigationController, forceExternal: false, openPeer: { peer, navigateToPeer in openResolvedPeerImpl(peer, navigateToPeer) - }, sendFile: nil, sendSticker: nil, sendEmoji: nil, joinVoiceChat: nil, present: { c, a in }, dismissInput: {}, contentContext: nil, progress: Promise(), completion: nil) + }, sendFile: nil, sendSticker: nil, sendEmoji: nil, joinVoiceChat: nil, present: { c, a in + controller.present(c, in: .window(.root), with: a) + }, dismissInput: {}, contentContext: nil, progress: Promise(), completion: nil) } default: break