diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index d542bc63f3..4c3fc5ab92 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -13223,3 +13223,6 @@ Sorry for the inconvenience."; "Gift.Send.SendShort" = "Send"; "Premium.EmojiStatus.Proceed" = "Unlock Emoji Statuses"; + +"Stars.Transaction.Subscription.CancelledByBot" = "Your subscription was cancelled by the bot."; +"Stars.Transaction.Subscription.CancelledByBusiness" = "Your subscription was cancelled by the business."; diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 27c51e0721..4574ddf359 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -568,6 +568,9 @@ private extension StarsContext.State.Subscription { if (apiFlags & (1 << 2)) != 0 { flags.insert(.missingBalance) } + if (apiFlags & (1 << 7)) != 0 { + flags.insert(.isCancelledByBot) + } self.init(flags: flags, id: id, peer: EnginePeer(peer), untilDate: untilDate, pricing: StarsSubscriptionPricing(apiStarsSubscriptionPricing: pricing), inviteHash: inviteHash, title: title, photo: photo.flatMap(TelegramMediaWebFile.init), invoiceSlug: invoiceSlug) } } @@ -719,6 +722,7 @@ public final class StarsContext { public static let isCancelled = Flags(rawValue: 1 << 0) public static let canRefulfill = Flags(rawValue: 1 << 1) public static let missingBalance = Flags(rawValue: 1 << 2) + public static let isCancelledByBot = Flags(rawValue: 1 << 3) } public let flags: Flags diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 2edecb817a..a2f557bdc1 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -149,6 +149,8 @@ private func contentNodeMessagesAndClassesForItem(_ item: ChatMessageItem) -> ([ messageWithCaptionToAdd = (message, itemAttributes) } result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) + } else if let _ = media as? TelegramMediaWebFile { + result.append((message, ChatMessageMediaBubbleContentNode.self, itemAttributes, BubbleItemAttributes(isAttachment: false, neighborType: .media, neighborSpacing: .default))) } else if let story = media as? TelegramMediaStory { if story.isMention { if let storyItem = message.associatedStories[story.storyId], storyItem.data.isEmpty { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift index 876b9f08ef..1b5d919769 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInteractiveMediaNode/Sources/ChatMessageInteractiveMediaNode.swift @@ -1920,6 +1920,8 @@ public final class ChatMessageInteractiveMediaNode: ASDisplayNode, GalleryItemTr imageDimensions = dimensions.cgSize } else if let image = media as? TelegramMediaWebFile, let dimensions = image.dimensions { imageDimensions = dimensions.cgSize + } else if let file = media as? TelegramMediaWebFile, let dimensions = file.dimensions { + imageDimensions = dimensions.cgSize } if let imageDimensions = imageDimensions { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift index e6e5362a0c..ce19c74bb7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageMediaBubbleContentNode/Sources/ChatMessageMediaBubbleContentNode.swift @@ -183,8 +183,7 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else if let invoice = media as? TelegramMediaInvoice { selectedMedia = invoice extendedMedia = invoice.extendedMedia - } - else if let paidContent = media as? TelegramMediaPaidContent { + } else if let paidContent = media as? TelegramMediaPaidContent { selectedMedia = paidContent if case let .mosaic(_, _, index) = preparePosition, let index { extendedMedia = paidContent.extendedMedia[index] @@ -192,6 +191,11 @@ public class ChatMessageMediaBubbleContentNode: ChatMessageBubbleContentNode { } else { extendedMedia = paidContent.extendedMedia.first } + } else if let webFile = media as? TelegramMediaWebFile { + selectedMedia = webFile + if item.presentationData.isPreview { + automaticDownload = .full + } } } } diff --git a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift index 5a01234637..976a5cde0a 100644 --- a/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsAvatarComponent/Sources/StarsAvatarComponent.swift @@ -15,18 +15,20 @@ import MultilineTextComponent public final class StarsAvatarComponent: Component { let context: AccountContext let theme: PresentationTheme - let peer: StarsContext.State.Transaction.Peer + let peer: StarsContext.State.Transaction.Peer? let photo: TelegramMediaWebFile? let media: [Media] let backgroundColor: UIColor + let size: CGSize? - public init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer, photo: TelegramMediaWebFile?, media: [Media], backgroundColor: UIColor) { + public init(context: AccountContext, theme: PresentationTheme, peer: StarsContext.State.Transaction.Peer?, photo: TelegramMediaWebFile?, media: [Media], backgroundColor: UIColor, size: CGSize? = nil) { self.context = context self.theme = theme self.peer = peer self.photo = photo self.media = media self.backgroundColor = backgroundColor + self.size = size } public static func ==(lhs: StarsAvatarComponent, rhs: StarsAvatarComponent) -> Bool { @@ -48,6 +50,9 @@ public final class StarsAvatarComponent: Component { if lhs.backgroundColor != rhs.backgroundColor { return false } + if lhs.size != rhs.size { + return false + } return true } @@ -88,119 +93,126 @@ public final class StarsAvatarComponent: Component { self.component = component self.state = state - let size = CGSize(width: 40.0, height: 40.0) + let size = component.size ?? CGSize(width: 40.0, height: 40.0) var iconInset: CGFloat = 3.0 var iconOffset: CGFloat = 0.0 var dimensions = size - switch component.peer { - case let .peer(peer): - if !component.media.isEmpty { - let imageNode: TransformImageNode + var didSetup = false + if !component.media.isEmpty { + let imageNode: TransformImageNode + var isFirstTime = false + if let current = self.imageNode { + imageNode = current + } else { + isFirstTime = true + imageNode = TransformImageNode() + imageNode.contentAnimations = [.subsequentUpdates] + self.addSubview(imageNode.view) + self.imageNode = imageNode + } + + if let image = component.media.first as? TelegramMediaImage { + if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { + dimensions = imageDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { + imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) + self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) + } + } else if let file = component.media.first as? TelegramMediaFile { + if let videoDimensions = file.dimensions { + dimensions = videoDimensions.cgSize.aspectFilled(size) + } + if isFirstTime { + imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), autoFetchFullSizeThumbnail: true)) + } + } + + var imageFrame = CGRect(origin: .zero, size: size) + if component.media.count > 1 { + imageFrame = imageFrame.insetBy(dx: 2.0, dy: 2.0).offsetBy(dx: -2.0, dy: 2.0) + } + imageNode.frame = imageFrame + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: dimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + + if component.media.count > 1 { + let secondImageNode: TransformImageNode + let imageFrameNode: UIView + var secondDimensions = size var isFirstTime = false - if let current = self.imageNode { - imageNode = current + if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { + secondImageNode = current + imageFrameNode = currentFrame } else { isFirstTime = true - imageNode = TransformImageNode() - imageNode.contentAnimations = [.subsequentUpdates] - self.addSubview(imageNode.view) - self.imageNode = imageNode + secondImageNode = TransformImageNode() + secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] + self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) + self.secondImageNode = secondImageNode + + imageFrameNode = UIView() + imageFrameNode.layer.cornerRadius = 8.0 + self.insertSubview(imageFrameNode, belowSubview: imageNode.view) + self.imageFrameNode = imageFrameNode } - if let image = component.media.first as? TelegramMediaImage { + if let image = component.media[1] as? TelegramMediaImage { if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - dimensions = imageDimensions.cgSize.aspectFilled(size) + secondDimensions = imageDimensions.cgSize.aspectFilled(size) } if isFirstTime { - imageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) + secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) } - } else if let file = component.media.first as? TelegramMediaFile { + } else if let file = component.media[1] as? TelegramMediaFile { if let videoDimensions = file.dimensions { - dimensions = videoDimensions.cgSize.aspectFilled(size) + secondDimensions = videoDimensions.cgSize.aspectFilled(size) } if isFirstTime { - imageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), autoFetchFullSizeThumbnail: true)) + secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) } } - - var imageFrame = CGRect(origin: .zero, size: size) - if component.media.count > 1 { - imageFrame = imageFrame.insetBy(dx: 2.0, dy: 2.0).offsetBy(dx: -2.0, dy: 2.0) - } - imageNode.frame = imageFrame - imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: dimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() - if component.media.count > 1 { - let secondImageNode: TransformImageNode - let imageFrameNode: UIView - var secondDimensions = size - var isFirstTime = false - if let current = self.secondImageNode, let currentFrame = self.imageFrameNode { - secondImageNode = current - imageFrameNode = currentFrame - } else { - isFirstTime = true - secondImageNode = TransformImageNode() - secondImageNode.contentAnimations = [.firstUpdate, .subsequentUpdates] - self.insertSubview(secondImageNode.view, belowSubview: imageNode.view) - self.secondImageNode = secondImageNode - - imageFrameNode = UIView() - imageFrameNode.layer.cornerRadius = 8.0 - self.insertSubview(imageFrameNode, belowSubview: imageNode.view) - self.imageFrameNode = imageFrameNode - } - - if let image = component.media[1] as? TelegramMediaImage { - if let imageDimensions = largestImageRepresentation(image.representations)?.dimensions { - secondDimensions = imageDimensions.cgSize.aspectFilled(size) - } - if isFirstTime { - secondImageNode.setSignal(chatMessagePhotoThumbnail(account: component.context.account, userLocation: .other, photoReference: .standalone(media: image), onlyFullSize: false, blurred: false)) - self.fetchDisposable.add(chatMessagePhotoInteractiveFetched(context: component.context, userLocation: .other, photoReference: .standalone(media: image), displayAtSize: nil, storeToDownloadsPeerId: nil).startStrict()) - } - } else if let file = component.media[1] as? TelegramMediaFile { - if let videoDimensions = file.dimensions { - secondDimensions = videoDimensions.cgSize.aspectFilled(size) - } - if isFirstTime { - secondImageNode.setSignal(mediaGridMessageVideo(postbox: component.context.account.postbox, userLocation: .other, videoReference: .standalone(media: file), useLargeThumbnail: true, autoFetchFullSizeThumbnail: true)) - } - } - - imageFrameNode.backgroundColor = component.backgroundColor - secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: secondDimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() - secondImageNode.frame = imageFrame.offsetBy(dx: 4.0, dy: -4.0) - imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel) - } - - self.backgroundView.isHidden = true - self.iconView.isHidden = true - self.avatarNode.isHidden = true - } else if let photo = component.photo { - let imageNode: TransformImageNode - if let current = self.imageNode { - imageNode = current - } else { - imageNode = TransformImageNode() - imageNode.contentAnimations = [.subsequentUpdates] - self.addSubview(imageNode.view) - self.imageNode = imageNode - - imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) - self.fetchDisposable.add(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) - } - - imageNode.frame = CGRect(origin: .zero, size: size) - imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() - - self.backgroundView.isHidden = true - self.iconView.isHidden = true - self.avatarNode.isHidden = true + imageFrameNode.backgroundColor = component.backgroundColor + secondImageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: 8.0), imageSize: secondDimensions, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + secondImageNode.frame = imageFrame.offsetBy(dx: 4.0, dy: -4.0) + imageFrameNode.frame = imageFrame.insetBy(dx: -1.0 - UIScreenPixel, dy: -1.0 - UIScreenPixel) + } + + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = true + didSetup = true + } else if let photo = component.photo { + let imageNode: TransformImageNode + if let current = self.imageNode { + imageNode = current } else { + imageNode = TransformImageNode() + imageNode.contentAnimations = [.subsequentUpdates] + self.addSubview(imageNode.view) + self.imageNode = imageNode + + imageNode.setSignal(chatWebFileImage(account: component.context.account, file: photo)) + self.fetchDisposable.add(chatMessageWebFileInteractiveFetched(account: component.context.account, userLocation: .other, image: photo).startStrict()) + } + + imageNode.frame = CGRect(origin: .zero, size: size) + imageNode.asyncLayout()(TransformImageArguments(corners: ImageCorners(radius: floor(size.width / 5.0)), imageSize: size, boundingSize: size, intrinsicInsets: UIEdgeInsets(), emptyColor: component.theme.list.mediaPlaceholderColor))() + + self.backgroundView.isHidden = true + self.iconView.isHidden = true + self.avatarNode.isHidden = true + didSetup = true + } + + switch component.peer { + case .none: + break + case let .peer(peer): + if !didSetup { self.avatarNode.setPeer( context: component.context, theme: component.theme, diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index bead20a23f..a6e87df6d9 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -226,6 +226,7 @@ private final class StarsTransactionSheetContent: CombinedComponent { var isSubscriber = false var isSubscriptionFee = false var isBotSubscription = false + var isBusinessSubscription = false var isCancelled = false var isReaction = false var giveawayMessageId: MessageId? @@ -259,8 +260,12 @@ private final class StarsTransactionSheetContent: CombinedComponent { transactionPeer = .peer(peer) isSubscriber = true case let .subscription(subscription): - if case let .user(user) = subscription.peer, user.botInfo != nil { - isBotSubscription = true + if case let .user(user) = subscription.peer { + if user.botInfo != nil { + isBotSubscription = true + } else { + isBusinessSubscription = true + } } if let title = subscription.title { titleText = title @@ -318,18 +323,30 @@ private final class StarsTransactionSheetContent: CombinedComponent { } isCancelled = true } else { - if subscription.flags.contains(.isCancelled) { + if subscription.flags.contains(.isCancelledByBot) { + if case let .user(user) = subscription.peer, user.botInfo == nil { + statusText = strings.Stars_Transaction_Subscription_CancelledByBusiness + } else { + statusText = strings.Stars_Transaction_Subscription_CancelledByBot + } + statusIsDestructive = true + buttonText = strings.Common_OK + isCancelled = true + } else if subscription.flags.contains(.isCancelled) { statusText = strings.Stars_Transaction_Subscription_Cancelled statusIsDestructive = true if date > Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) { buttonText = strings.Stars_Transaction_Subscription_Renew } else { - if let _ = subscription.inviteHash, !isKicked { + if let _ = subscription.invoiceSlug { + buttonText = strings.Stars_Transaction_Subscription_Renew + } else if let _ = subscription.inviteHash, !isKicked { buttonText = strings.Stars_Transaction_Subscription_JoinAgainChannel } else { buttonText = strings.Common_OK } } + isCancelled = true } else { statusText = strings.Stars_Transaction_Subscription_Active(stringForMediumDate(timestamp: subscription.untilDate, strings: strings, dateTimeFormat: dateTimeFormat, withTime: false)).string cancelButtonText = strings.Stars_Transaction_Subscription_Cancel @@ -710,6 +727,8 @@ private final class StarsTransactionSheetContent: CombinedComponent { if isBotSubscription { //TODO:localize title = "Bot" + } else if isBusinessSubscription { + title = "Business" } else { title = strings.Stars_Transaction_Subscription_Subscription } diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift index 1c5efbbcf8..ce2cb7823a 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionsScreen/Sources/StarsTransactionsScreen.swift @@ -708,10 +708,10 @@ final class StarsTransactionsScreenComponent: Component { )) )) var nameGroupComponent: AnyComponent - if let _ = subscription.photo { + if let photo = subscription.photo { nameGroupComponent = AnyComponent( HStack([ - AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(RoundedRectangle(color: .lightGray, cornerRadius: 3.0, size: CGSize(width: 19.0, height: 19.0)))), + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(StarsAvatarComponent(context: component.context, theme: environment.theme, peer: nil, photo: photo, media: [], backgroundColor: .clear, size: CGSize(width: 19.0, height: 19.0)))), AnyComponentWithIdentity(id: AnyHashable(1), component: nameComponent) ], spacing: 6.0) ) @@ -1055,6 +1055,10 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { let _ = self.context.engine.payments.fulfillStarsSubscription(peerId: context.account.peerId, subscriptionId: subscription.id).startStandalone() updated = true } + if let _ = subscription.inviteHash, !subscription.flags.contains(.isCancelledByBot) { + let _ = self.context.engine.payments.fulfillStarsSubscription(peerId: context.account.peerId, subscriptionId: subscription.id).startStandalone() + updated = true + } if !updated { if subscription.flags.contains(.isCancelled) { self.subscriptionsContext.updateSubscription(id: subscription.id, cancel: false) @@ -1065,6 +1069,8 @@ public final class StarsTransactionsScreen: ViewControllerComponentContainer { } else { if let inviteHash = subscription.inviteHash { self.context.sharedContext.handleTextLinkAction(context: self.context, peerId: nil, navigateDisposable: self.navigateDisposable, controller: self, action: .tap, itemLink: .url(url: "https://t.me/+\(inviteHash)", concealed: false)) + } else if let invoiceSlug = subscription.invoiceSlug { + self.context.sharedContext.handleTextLinkAction(context: self.context, peerId: nil, navigateDisposable: self.navigateDisposable, controller: self, action: .tap, itemLink: .url(url: "https://t.me/$\(invoiceSlug)", concealed: false)) } } }) diff --git a/submodules/TelegramUI/Sources/TextLinkHandling.swift b/submodules/TelegramUI/Sources/TextLinkHandling.swift index ee253628e0..2f72735d31 100644 --- a/submodules/TelegramUI/Sources/TextLinkHandling.swift +++ b/submodules/TelegramUI/Sources/TextLinkHandling.swift @@ -100,7 +100,7 @@ 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 .boost, .chatFolder, .join: + case .boost, .chatFolder, .join, .invoice: 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, forceUpdate: false, openPeer: { peer, navigateToPeer in openResolvedPeerImpl(peer, navigateToPeer)