diff --git a/Telegram/Telegram-iOS/en.lproj/Localizable.strings b/Telegram/Telegram-iOS/en.lproj/Localizable.strings index c232c208da..e79a4de7cc 100644 --- a/Telegram/Telegram-iOS/en.lproj/Localizable.strings +++ b/Telegram/Telegram-iOS/en.lproj/Localizable.strings @@ -3703,6 +3703,18 @@ Unused sets are archived when you add more."; "NotificationsSound.Pulse" = "Pulse"; "NotificationsSound.Synth" = "Synth"; +"NotificationsSound.Rebound" = "Rebound"; +"NotificationsSound.Antic" = "Antic"; +"NotificationsSound.Cheers" = "Cheers"; +"NotificationsSound.Droplet" = "Droplet"; +"NotificationsSound.Handoff" = "Handoff"; +"NotificationsSound.Milestone" = "Milestone"; +"NotificationsSound.Passage" = "Passage"; +"NotificationsSound.Portal" = "Portal"; +"NotificationsSound.Rattle" = "Rattle"; +"NotificationsSound.Slide" = "Slide"; +"NotificationsSound.Welcome" = "Welcome"; + "NotificationsSound.Tritone" = "Tri-tone"; "NotificationsSound.Tremolo" = "Tremolo"; "NotificationsSound.Alert" = "Alert"; @@ -9813,10 +9825,6 @@ Sorry for the inconvenience."; "Premium.Stories.Format.Title" = "Links and Formatting"; "Premium.Stories.Format.Text" = "Add links and formatting in captions to your stories."; -"Premium.MaxExpiringStoriesText" = "You can post **%@** stories in **24** hours. Subscribe to **Telegram Premium** to increase this limit to **%@**."; -"Premium.MaxExpiringStoriesNoPremiumText" = "You have reached the limit of **%@** stories per **24** hours."; -"Premium.MaxExpiringStoriesFinalText" = "You have reached the limit of **%@** stories per **24** hours."; - "Premium.MaxStoriesWeeklyText" = "You can post **%@** stories in a week. Upgrade to **Telegram Premium** to increase this limit to **%@**."; "Premium.MaxStoriesWeeklyNoPremiumText" = "You have reached the limit of **%@** stories per week."; "Premium.MaxStoriesWeeklyFinalText" = "You have reached the limit of **%@** stories per week."; @@ -13565,6 +13573,7 @@ Sorry for the inconvenience."; "FolderLinkPreview.ToastFolderAddedTitleV2" = "Folder {folder} Added"; "Stars.Purchase.UpgradeStarGiftInfo" = "Buy Stars to upgrade your gift into a unique collectible."; +"Stars.Purchase.TransferStarGiftInfo" = "Buy Stars to transfer ownership of your unique collectible."; "Gift.Send.Upgrade" = "Make Unique for %@"; "Gift.Send.Upgrade.Info" = "Enable this to let %1$@ turn your gift into a unique collectible. [Learn More >]()"; @@ -13994,9 +14003,6 @@ Sorry for the inconvenience."; "Share.VideoStartAt" = "Start at %@"; "SendStarReactions.SubtitleFrom" = "from %@"; -"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned."; -"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()"; - "Notification.PaidMessageRefund.Stars_1" = "%@ Star"; "Notification.PaidMessageRefund.Stars_any" = "%@ Stars"; "Notification.PaidMessageRefund" = "%1$@ refunded you %2$@"; @@ -14006,3 +14012,20 @@ Sorry for the inconvenience."; "Notification.PaidMessagePriceChanged.Stars_any" = "%@ Stars"; "Notification.PaidMessagePriceChanged" = "%1$@ changed price to %2$@ per message"; "Notification.PaidMessagePriceChangedYou" = "You changed price to %1$@ per message"; + +"Premium.MaxExpiringStoriesTextNumberFormat_1" = "**%d** story"; +"Premium.MaxExpiringStoriesTextNumberFormat_any" = "**%d** stories"; +"Premium.MaxExpiringStoriesTextPremiumNumberFormat_1" = "**%d** story"; +"Premium.MaxExpiringStoriesTextPremiumNumberFormat_any" = "**%d** stories"; +"Premium.MaxExpiringStoriesTextFormat" = "You can post %@ in **24** hours. Subscribe to **Telegram Premium** to increase this limit to **%@**."; + +"Premium.MaxExpiringStoriesNoPremiumTextNumberFormat_1" = "**%d** story"; +"Premium.MaxExpiringStoriesNoPremiumTextNumberFormat_any" = "**%d** stories"; +"Premium.MaxExpiringStoriesNoPremiumTextFormat" = "You have reached the limit of %@ per **24** hours."; + +"Premium.MaxExpiringStoriesFinalTextNumberFormat_1" = "**%d** story"; +"Premium.MaxExpiringStoriesFinalTextNumberFormat_any" = "**%d** stories"; +"Premium.MaxExpiringStoriesFinalTextFormat" = "You have reached the limit of %@ stories per **24** hours."; + +"Stars.AccountRevenue.Proceeds.Info" = "Stars from your total balance can be withdrawn as rewards 21 days after they are earned."; +"Stars.AccountRevenue.Withdraw.Info" = "You can collect rewards for Stars using Fragment. You cannot withdraw less than 1000 stars. [Learn More >]()"; diff --git a/submodules/AccountContext/Sources/Premium.swift b/submodules/AccountContext/Sources/Premium.swift index cccec77d59..790a21b077 100644 --- a/submodules/AccountContext/Sources/Premium.swift +++ b/submodules/AccountContext/Sources/Premium.swift @@ -137,6 +137,7 @@ public enum StarsPurchasePurpose: Equatable { case unlockMedia(requiredStars: Int64) case starGift(peerId: EnginePeer.Id, requiredStars: Int64) case upgradeStarGift(requiredStars: Int64) + case transferStarGift(requiredStars: Int64) case sendMessage(peerId: EnginePeer.Id, requiredStars: Int64) } diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index 3c2c57a158..8fb4bff944 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -4098,7 +4098,7 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { strongSelf.secretIconNode = iconNode } iconNode.image = currentSecretIconImage - transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size)) + transition.updateFrame(node: iconNode, frame: CGRect(origin: CGPoint(x: contentRect.origin.x + titleLeftOffset, y: contentRect.origin.y + floor((titleLayout.size.height - currentSecretIconImage.size.height) / 2.0)), size: currentSecretIconImage.size)) titleOffset += currentSecretIconImage.size.width + 3.0 } else if let secretIconNode = strongSelf.secretIconNode { strongSelf.secretIconNode = nil diff --git a/submodules/Display/Source/TextAlertController.swift b/submodules/Display/Source/TextAlertController.swift index c8ddef511c..291446f04b 100644 --- a/submodules/Display/Source/TextAlertController.swift +++ b/submodules/Display/Source/TextAlertController.swift @@ -129,7 +129,7 @@ public final class TextAlertContentActionNode: HighlightableButtonNode { if let range = attributedString.string.range(of: "$") { attributedString.addAttribute(.attachment, value: UIImage(bundleImageName: "Item List/PremiumIcon")!, range: NSRange(range, in: attributedString.string)) attributedString.addAttribute(.foregroundColor, value: color, range: NSRange(range, in: attributedString.string)) - attributedString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: attributedString.string)) + attributedString.addAttribute(.baselineOffset, value: 2.0, range: NSRange(range, in: attributedString.string)) } self.setAttributedTitle(attributedString, for: []) diff --git a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift index 8dc211102b..fe3759663f 100644 --- a/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift +++ b/submodules/InAppPurchaseManager/Sources/InAppPurchaseManager.swift @@ -234,7 +234,7 @@ public final class InAppPurchaseManager: NSObject { private let disposableSet = DisposableDict() private var lastRequestTimestamp: Double? - + public init(engine: SomeTelegramEngine) { self.engine = engine diff --git a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift index cba245039f..59dd08228d 100644 --- a/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift +++ b/submodules/PremiumUI/Sources/EmojiHeaderComponent.swift @@ -64,6 +64,7 @@ class EmojiHeaderComponent: Component { } weak var animateFrom: UIView? + var sourceRect: CGRect? weak var containerView: UIView? let statusView: ComponentHostView @@ -116,8 +117,13 @@ class EmojiHeaderComponent: Component { let initialPosition = self.statusView.center let targetPosition = self.statusView.superview!.convert(self.statusView.center, to: containerView) - let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: 0.0, dy: 0.0) + var sourceOffset: CGPoint = .zero + if let sourceRect = self.sourceRect { + sourceOffset = CGPoint(x: sourceRect.center.x - animateFrom.frame.width / 2.0, y: 0.0) + } + let sourcePosition = animateFrom.superview!.convert(animateFrom.center, to: containerView).offsetBy(dx: sourceOffset.x, dy: sourceOffset.y) + containerView.addSubview(self.statusView) self.statusView.center = targetPosition @@ -127,6 +133,7 @@ class EmojiHeaderComponent: Component { self.statusView.layer.animatePosition(from: sourcePosition, to: targetPosition, duration: 0.55, timingFunction: kCAMediaTimingFunctionSpring) Queue.mainQueue().after(0.55, { + self.statusView.layer.removeAllAnimations() self.addSubview(self.statusView) self.statusView.center = initialPosition }) diff --git a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift index 76082e2966..c96c0668d6 100644 --- a/submodules/PremiumUI/Sources/PremiumIntroScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumIntroScreen.swift @@ -3865,6 +3865,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { } public weak var sourceView: UIView? + public var sourceRect: CGRect? public weak var containerView: UIView? public var animationColor: UIColor? @@ -4046,6 +4047,7 @@ public final class PremiumIntroScreen: ViewControllerComponentContainer { if let sourceView = self.sourceView { view.animateFrom = sourceView + view.sourceRect = self.sourceRect view.containerView = self.containerView view.animateIn() diff --git a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift index b2ab0fa0d4..a313b56cbc 100644 --- a/submodules/PremiumUI/Sources/PremiumLimitScreen.swift +++ b/submodules/PremiumUI/Sources/PremiumLimitScreen.swift @@ -1088,7 +1088,14 @@ private final class LimitSheetContent: CombinedComponent { let premiumLimit = state.premiumLimits.maxExpiringStoriesCount iconName = "Premium/Stories" badgeText = "\(limit)" - string = component.count >= premiumLimit ? strings.Premium_MaxExpiringStoriesFinalText("\(premiumLimit)").string : strings.Premium_MaxExpiringStoriesText("\(limit)", "\(premiumLimit)").string + if component.count >= premiumLimit { + let limitNumberString = strings.Premium_MaxExpiringStoriesFinalTextNumberFormat(Int32(premiumLimit)) + string = strings.Premium_MaxExpiringStoriesFinalTextFormat(limitNumberString).string + } else { + let limitNumberString = strings.Premium_MaxExpiringStoriesTextNumberFormat(Int32(limit)) + let premiumLimitNumberString = strings.Premium_MaxExpiringStoriesTextPremiumNumberFormat(Int32(premiumLimit)) + string = strings.Premium_MaxExpiringStoriesTextFormat(limitNumberString, premiumLimitNumberString).string + } defaultValue = "" premiumValue = component.count >= premiumLimit ? "" : "\(premiumLimit)" badgePosition = max(0.32, CGFloat(component.count) / CGFloat(premiumLimit)) @@ -1096,7 +1103,8 @@ private final class LimitSheetContent: CombinedComponent { if isPremiumDisabled { badgeText = "\(limit)" - string = strings.Premium_MaxExpiringStoriesNoPremiumText("\(limit)").string + let numberString = strings.Premium_MaxExpiringStoriesNoPremiumTextNumberFormat(Int32(limit)) + string = strings.Premium_MaxExpiringStoriesNoPremiumTextFormat(numberString).string } buttonAnimationName = nil case .storiesWeekly: diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index e4722af322..13c30a2bb6 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1038,7 +1038,6 @@ public final class StarsContext { } } - init(account: Account) { self.impl = QueueLocalObject(queue: Queue.mainQueue(), generate: { return StarsContextImpl(account: account) diff --git a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift index 69c7260435..a2c2a1dabb 100644 --- a/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift +++ b/submodules/TelegramPresentationData/Sources/PresentationThemeEssentialGraphics.swift @@ -175,7 +175,7 @@ public final class PrincipalThemeEssentialGraphics { public let outgoingDateAndStatusStarsIcon: UIImage public let mediaStarsIcon: UIImage public let freeStarsIcon: UIImage - + public let incomingDateAndStatusPinnedIcon: UIImage public let outgoingDateAndStatusPinnedIcon: UIImage public let mediaPinnedIcon: UIImage @@ -369,7 +369,7 @@ public final class PrincipalThemeEssentialGraphics { self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)! self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)! self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)! - + let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")! self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)! self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)! @@ -496,7 +496,7 @@ public final class PrincipalThemeEssentialGraphics { self.outgoingDateAndStatusStarsIcon = generateTintedImage(image: starsImage, color: theme.message.outgoing.secondaryTextColor)! self.mediaStarsIcon = generateTintedImage(image: starsImage, color: .white)! self.freeStarsIcon = generateTintedImage(image: starsImage, color: serviceColor.primaryText)! - + let pinnedImage = UIImage(bundleImageName: "Chat/Message/Pinned")! self.incomingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.incoming.secondaryTextColor)! self.outgoingDateAndStatusPinnedIcon = generateTintedImage(image: pinnedImage, color: theme.message.outgoing.secondaryTextColor)! diff --git a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift index 22ee8094e3..0a6f696f34 100644 --- a/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatEmptyNode/Sources/ChatEmptyNode.swift @@ -1899,7 +1899,7 @@ public final class ChatEmptyNode: ASDisplayNode { default: self.isUserInteractionEnabled = false } - + let displayRect = CGRect(origin: CGPoint(x: 0.0, y: insets.top), size: CGSize(width: size.width, height: size.height - insets.top - insets.bottom)) var contentSize = CGSize() diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift index 5d7a6a9ae0..f77630f42f 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageDateAndStatusNode/Sources/ChatMessageDateAndStatusNode.swift @@ -267,7 +267,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { private var replyCountNode: TextNode? private var starsIcon: ASImageNode? private var starsCountNode: TextNode? - + private var type: ChatMessageDateAndStatusType? private var theme: ChatPresentationThemeData? private var layoutSize: CGSize? @@ -322,13 +322,13 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var currentImpressionIcon = self.impressionIcon var currentRepliesIcon = self.repliesIcon var currentStarsIcon = self.starsIcon - + let currentType = self.type let currentTheme = self.theme let makeReplyCountLayout = TextNode.asyncLayout(self.replyCountNode) let makeStarsCountLayout = TextNode.asyncLayout(self.starsCountNode) - + let reactionButtonsContainer = self.reactionButtonsContainer return { [weak self] arguments in @@ -345,7 +345,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var impressionImage: UIImage? var repliesImage: UIImage? var starsImage: UIImage? - + let themeUpdated = arguments.presentationData.theme != currentTheme || arguments.type != currentType let graphics = PresentationResourcesChat.principalGraphics(theme: arguments.presentationData.theme.theme, wallpaper: arguments.presentationData.theme.wallpaper, bubbleCorners: arguments.presentationData.chatBubbleCorners) @@ -693,7 +693,7 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { var replyCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? var starsCountLayoutAndApply: (TextNodeLayout, () -> TextNode)? - + let reactionSize: CGFloat = 8.0 let reactionSpacing: CGFloat = 2.0 let reactionTrailingSpacing: CGFloat = 6.0 @@ -712,6 +712,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { let layoutAndApply = makeReplyCountLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: countString, font: dateFont, textColor: dateColor), backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: CGSize(width: 100.0, height: 100.0))) reactionInset += 14.0 + layoutAndApply.0.size.width + 4.0 + if arguments.starsCount != nil { + reactionInset += 3.0 + } replyCountLayoutAndApply = layoutAndApply } else if arguments.isPinned { reactionInset += 12.0 @@ -1283,6 +1286,9 @@ public class ChatMessageDateAndStatusNode: ASDisplayNode { let replyCountFrame = CGRect(origin: CGPoint(x: reactionOffset + 4.0, y: backgroundInsets.top + 1.0 + offset + verticalInset), size: layout.size) animation.animator.updateFrame(layer: node.layer, frame: replyCountFrame, completion: nil) reactionOffset += 4.0 + layout.size.width + if currentStarsIcon != nil { + reactionOffset += 8.0 + } } else if let replyCountNode = strongSelf.replyCountNode { strongSelf.replyCountNode = nil if animation.isAnimated { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift index 151913193d..75270b57dc 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController/Sources/ChatMessagePaymentAlertController.swift @@ -317,18 +317,20 @@ private final class ChatMessagePaymentAlertContentNode: AlertContentNode, ASGest } } -private class ChatMessagePaymentAlertController: AlertController { +public class ChatMessagePaymentAlertController: AlertController { private let context: AccountContext? private let presentationData: PresentationData private weak var parentNavigationController: NavigationController? - + private let showBalance: Bool + private let balance = ComponentView() - init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?) { + public init(context: AccountContext?, presentationData: PresentationData, contentNode: AlertContentNode, navigationController: NavigationController?, showBalance: Bool = true) { self.context = context self.presentationData = presentationData self.parentNavigationController = navigationController - + self.showBalance = showBalance + super.init(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode) self.willDismiss = { [weak self] in @@ -350,16 +352,16 @@ private class ChatMessagePaymentAlertController: AlertController { } } - override func dismissAnimated() { + public override func dismissAnimated() { super.dismissAnimated() self.animateOut() } - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + public override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - if let context = self.context, let _ = self.parentNavigationController { + if let context = self.context, let _ = self.parentNavigationController, self.showBalance { let insets = layout.insets(options: .statusBar) let balanceSize = self.balance.update( transition: .immediate, diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift index db8ad96529..7a584de5bf 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiKeyboardItemLayer.swift @@ -177,7 +177,9 @@ public final class EmojiKeyboardItemLayer: MultiAnimationRenderTarget { switch content { case let .animation(animationData): - let animationDataResource = animationData.resource._parse() + guard let animationDataResource = animationData.resource._parse() else { + return + } let loadAnimation: () -> Void = { [weak self] in guard let strongSelf = self else { diff --git a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift index 6811ddf78d..7de85bc70f 100644 --- a/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift +++ b/submodules/TelegramUI/Components/EntityKeyboard/Sources/EmojiPagerContentComponent.swift @@ -65,12 +65,15 @@ public final class EntityKeyboardAnimationData: Equatable { case stickerPackThumbnail(id: Int64, accessHash: Int64, info: StickerPackCollectionInfo.Accessor) case file(PartialMediaReference?, TelegramMediaFile.Accessor) - func _parse() -> MediaResourceReference { + func _parse() -> MediaResourceReference? { switch self { case let .resource(resource): return resource case let .stickerPackThumbnail(id, accessHash, info): - return .stickerPackThumbnail(stickerPack: .id(id: id, accessHash: accessHash), resource: info._parse().thumbnail!.resource) + guard let thumbnail = info._parse().thumbnail else { + return nil + } + return .stickerPackThumbnail(stickerPack: .id(id: id, accessHash: accessHash), resource: thumbnail.resource) case let .file(partialReference, file): let file = file._parse() if let partialReference { @@ -1740,7 +1743,9 @@ public final class EmojiPagerContentComponent: Component { }) } - component.animationRenderer.setFrameIndex(itemId: animationData.resource._parse().resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder) + if let resource = animationData.resource._parse() { + component.animationRenderer.setFrameIndex(itemId: resource.resource.id.stringRepresentation, size: itemLayer.pixelSize, frameIndex: sourceItem.frameIndex, placeholder: sourceItem.placeholder) + } } else { let distance = itemLayer.position.y - itemLayout.frame(groupIndex: 0, itemIndex: 0).midY let maxDistance = self.bounds.height diff --git a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift index 5f3287cf6f..6c8266f75c 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftOptionsScreen/Sources/GiftOptionsScreen.swift @@ -216,6 +216,8 @@ final class GiftOptionsScreenComponent: Component { private var starsStateDisposable: Disposable? private var starsState: StarsContext.State? + private let optionsPromise = Promise<[StarsTopUpOption]?>(nil) + private var component: GiftOptionsScreenComponent? private(set) weak var state: State? private var environment: EnvironmentType? @@ -508,59 +510,94 @@ final class GiftOptionsScreenComponent: Component { gift: transferGift, peer: peer, transferStars: gift.transferStars ?? 0, - commit: { [weak controller] in - let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id) - |> deliverOnMainQueue).start() - - guard let controller, let navigationController = controller.navigationController as? NavigationController else { - return - } - - if peer.id.namespace == Namespaces.Peer.CloudChannel { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } - var foundController = false - for controller in controllers.reversed() { - if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId { - foundController = true - break - } + navigationController: controller.navigationController as? NavigationController, + commit: { [weak self, weak controller] in + let proceed: (Bool) -> Void = { waitForTopUp in + if waitForTopUp, let starsContext = context.starsContext { + let _ = (starsContext.onUpdate + |> deliverOnMainQueue).start(next: { + let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id) + |> deliverOnMainQueue).start() + }) + } else { + let _ = (context.engine.payments.transferStarGift(prepaid: gift.transferStars == 0, reference: reference, peerId: peer.id) + |> deliverOnMainQueue).start() } - if !foundController { - if let controller = context.sharedContext.makePeerInfoController( - context: context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .gifts, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - controllers.append(controller) - } + + guard let controller, let navigationController = controller.navigationController as? NavigationController else { + return } - navigationController.setViewControllers(controllers, animated: true) - } else { - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + + if peer.id.namespace == Namespaces.Peer.CloudChannel { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) } + var foundController = false + for controller in controllers.reversed() { + if let controller = controller as? PeerInfoScreen, controller.peerId == component.peerId { + foundController = true + break + } + } + if !foundController { + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .gifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + controllers.append(controller) + } + } + navigationController.setViewControllers(controllers, animated: true) + } else { + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is GiftSetupScreen) && !($0 is GiftOptionsScreenProtocol) && !($0 is PeerInfoScreen) && !($0 is ContactSelectionController) } + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: component.peerId) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) chatController.hintPlayNextOutgoingGift() - foundController = true - break + controllers.append(chatController) } + navigationController.setViewControllers(controllers, animated: true) } - if !foundController { - let chatController = component.context.sharedContext.makeChatController(context: component.context, chatLocation: .peer(id: component.peerId), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) + if let completion = component.completion { + completion() } - navigationController.setViewControllers(controllers, animated: true) } - - if let completion = component.completion { - completion() + + if let self, let transferStars = gift.transferStars, transferStars > 0, let starsContext = context.starsContext, let starsState = self.starsState { + if starsState.balance < StarsAmount(value: transferStars, nanos: 0) { + let _ = (self.optionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak controller] options in + let purchaseController = context.sharedContext.makeStarsPurchaseScreen( + context: context, + starsContext: starsContext, + options: options ?? [], + purpose: .transferStarGift(requiredStars: transferStars), + completion: { stars in + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + proceed(true) + } + ) + controller?.push(purchaseController) + }) + } else { + proceed(false) + } + } else { + proceed(false) } } ) @@ -590,6 +627,11 @@ final class GiftOptionsScreenComponent: Component { self.state?.updated() } }) + + if let state = component.starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) { + self.optionsPromise.set(component.context.engine.payments.starsTopUpOptions() + |> map(Optional.init)) + } } self.component = component @@ -1241,7 +1283,6 @@ final class GiftOptionsScreenComponent: Component { continue } let starsGiftOption = premiumOptions.first(where: { $0.currency == "XTR" && $0.months == option.months }) - premiumProducts.append( PremiumGiftProduct( giftOption: CachedPremiumGiftOption( diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD index ace4e1f471..66375077f3 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/BUILD @@ -49,6 +49,7 @@ swift_library( "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/PremiumLockButtonSubtitleComponent", "//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent", + "//submodules/TelegramUI/Components/Chat/ChatMessagePaymentAlertController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift index ffa0d3ffb8..91ccd670ac 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftTransferAlertController.swift @@ -12,6 +12,7 @@ import AppBundle import AvatarNode import Markdown import GiftItemComponent +import ChatMessagePaymentAlertController private final class GiftTransferAlertContentNode: AlertContentNode { private let context: AccountContext @@ -251,7 +252,14 @@ private final class GiftTransferAlertContentNode: AlertContentNode { } } -public func giftTransferAlertController(context: AccountContext, gift: StarGift.UniqueGift, peer: EnginePeer, transferStars: Int64, commit: @escaping () -> Void) -> AlertController { +public func giftTransferAlertController( + context: AccountContext, + gift: StarGift.UniqueGift, + peer: EnginePeer, + transferStars: Int64, + navigationController: NavigationController?, + commit: @escaping () -> Void +) -> AlertController { let presentationData = context.sharedContext.currentPresentationData.with { $0 } let strings = presentationData.strings @@ -267,7 +275,6 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift. } var dismissImpl: ((Bool) -> Void)? - var contentNode: GiftTransferAlertContentNode? let actions: [TextAlertAction] = [TextAlertAction(type: .defaultAction, title: buttonText, action: { dismissImpl?(true) commit() @@ -275,9 +282,9 @@ public func giftTransferAlertController(context: AccountContext, gift: StarGift. dismissImpl?(true) })] - contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions) + let contentNode = GiftTransferAlertContentNode(context: context, theme: AlertControllerTheme(presentationData: presentationData), ptheme: presentationData.theme, strings: strings, gift: gift, peer: peer, title: title, text: text, actions: actions) - let controller = AlertController(theme: AlertControllerTheme(presentationData: presentationData), contentNode: contentNode!) + let controller = ChatMessagePaymentAlertController(context: context, presentationData: presentationData, contentNode: contentNode, navigationController: navigationController, showBalance: transferStars > 0) dismissImpl = { [weak controller] animated in if animated { controller?.dismissAnimated() diff --git a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift index d1ecd50d04..469950558b 100644 --- a/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift +++ b/submodules/TelegramUI/Components/Gifts/GiftViewScreen/Sources/GiftViewScreen.swift @@ -147,13 +147,7 @@ private final class GiftViewSheetContent: CombinedComponent { var keepOriginalInfo = false - private var optionsDisposable: Disposable? - private(set) var options: [StarsTopUpOption] = [] { - didSet { - self.optionsPromise.set(self.options) - } - } - private let optionsPromise = ValuePromise<[StarsTopUpOption]?>(nil) + private let optionsPromise = Promise<[StarsTopUpOption]?>(nil) init( context: AccountContext, @@ -269,13 +263,8 @@ private final class GiftViewSheetContent: CombinedComponent { } if let starsContext = context.starsContext, let state = starsContext.currentState, state.balance < StarsAmount(value: 100, nanos: 0) { - self.optionsDisposable = (context.engine.payments.starsTopUpOptions() - |> deliverOnMainQueue).start(next: { [weak self] options in - guard let self else { - return - } - self.options = options - }) + self.optionsPromise.set(context.engine.payments.starsTopUpOptions() + |> map(Optional.init)) } } @@ -351,8 +340,12 @@ private final class GiftViewSheetContent: CombinedComponent { self.inProgress = true self.updated() + if let controller = self.getController() as? GiftViewScreen { + controller.showBalance = false + } + self.upgradeDisposable = (self.upgradeGift(formId, self.keepOriginalInfo) - |> deliverOnMainQueue).start(next: { [weak self] result in + |> deliverOnMainQueue).start(next: { [weak self, weak starsContext] result in guard let self, let controller = self.getController() as? GiftViewScreen else { return } @@ -363,6 +356,10 @@ private final class GiftViewSheetContent: CombinedComponent { controller.subject = self.subject controller.animateSuccess() self.updated(transition: .spring(duration: 0.4)) + + Queue.mainQueue().after(0.5) { + starsContext?.load(force: true) + } }) } @@ -382,10 +379,13 @@ private final class GiftViewSheetContent: CombinedComponent { starsContext: starsContext, options: options ?? [], purpose: .upgradeStarGift(requiredStars: price), - completion: { [weak starsContext] stars in - guard let starsContext else { + completion: { [weak self, weak starsContext] stars in + guard let self, let starsContext else { return } + self.inProgress = true + self.updated() + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) let _ = (starsContext.onUpdate |> deliverOnMainQueue).start(next: { @@ -2230,9 +2230,8 @@ private final class GiftViewSheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - 16.0 - buttons.size.width / 2.0, y: 28.0)) ) - let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom) - - return contentSize + let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom + return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset) } } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift index 74e3de47ae..09faa26854 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoCoverComponent.swift @@ -111,6 +111,7 @@ public final class PeerInfoCoverComponent: Component { public let files: [Int64: TelegramMediaFile] public let isDark: Bool public let avatarCenter: CGPoint + public let avatarSize: CGSize public let avatarScale: CGFloat public let defaultHeight: CGFloat public let gradientOnTop: Bool @@ -124,6 +125,7 @@ public final class PeerInfoCoverComponent: Component { files: [Int64: TelegramMediaFile], isDark: Bool, avatarCenter: CGPoint, + avatarSize: CGSize = CGSize(width: 100.0, height: 100.0), avatarScale: CGFloat, defaultHeight: CGFloat, gradientOnTop: Bool = false, @@ -136,6 +138,7 @@ public final class PeerInfoCoverComponent: Component { self.files = files self.isDark = isDark self.avatarCenter = avatarCenter + self.avatarSize = avatarSize self.avatarScale = avatarScale self.defaultHeight = defaultHeight self.gradientOnTop = gradientOnTop @@ -160,6 +163,9 @@ public final class PeerInfoCoverComponent: Component { if lhs.avatarCenter != rhs.avatarCenter { return false } + if lhs.avatarSize != rhs.avatarSize { + return false + } if lhs.avatarScale != rhs.avatarScale { return false } @@ -492,7 +498,7 @@ public final class PeerInfoCoverComponent: Component { transition.containedViewLayoutTransition.updateFrameAdditive(view: self.backgroundPatternContainer, frame: backgroundPatternContainerFrame) transition.setAlpha(view: self.backgroundPatternContainer, alpha: component.patternTransitionFraction) - var baseDistance: CGFloat = 72.0 + var baseDistance: CGFloat = component.avatarSize.width / 2.0 + 22.0 var baseRowDistance: CGFloat = 28.0 var baseItemSize: CGFloat = 26.0 if availableSize.width <= 60.0 { @@ -516,7 +522,7 @@ public final class PeerInfoCoverComponent: Component { let baseItemDistance: CGFloat = baseDistance + CGFloat(row) * baseRowDistance let itemDistanceFraction = max(0.0, min(1.0, baseItemDistance / (baseDistance * 2.0))) - let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false) + let itemScaleFraction = patternScaleValueAt(fraction: component.avatarTransitionFraction * 1.6, t: itemDistanceFraction, reverse: false) let itemDistance = baseItemDistance * (1.0 - itemScaleFraction) + 20.0 * itemScaleFraction var itemAngle: CGFloat diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift index 776c0ea55e..66f1bba085 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoCoverComponent/Sources/PeerInfoGiftsCoverComponent.swift @@ -219,14 +219,14 @@ public final class PeerInfoGiftsCoverComponent: Component { } excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.statusBarHeight), size: component.topLeftButtonsSize)) excludeRects.append(CGRect(origin: CGPoint(x: availableSize.width - component.topRightButtonsSize.width, y: component.statusBarHeight), size: component.topRightButtonsSize)) - excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + 56.0), size: CGSize(width: component.titleWidth, height: 72.0))) + excludeRects.append(CGRect(origin: CGPoint(x: floor((availableSize.width - component.titleWidth) / 2.0), y: avatarCenter.y + component.avatarSize.height / 2.0 + 6.0), size: CGSize(width: component.titleWidth, height: 100.0))) if component.bottomHeight > 0.0 { excludeRects.append(CGRect(origin: CGPoint(x: 0.0, y: component.defaultHeight - component.bottomHeight), size: CGSize(width: availableSize.width, height: component.bottomHeight))) } let positionGenerator = PositionGenerator( containerSize: CGSize(width: availableSize.width, height: component.defaultHeight), - centerFrame: CGSize(width: 100, height: 100).centered(around: avatarCenter), + centerFrame: component.avatarSize.centered(around: avatarCenter), exclusionZones: excludeRects, minimumDistance: 42.0, edgePadding: 5.0, @@ -304,8 +304,8 @@ public final class PeerInfoGiftsCoverComponent: Component { } iconLayer.glowing = component.hasBackground - let itemDistanceFraction = max(0.0, min(1.0, iconPosition.distance / 100.0)) - let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.56), t: itemDistanceFraction, reverse: false) + let itemDistanceFraction = max(0.0, min(0.5, (iconPosition.distance - component.avatarSize.width / 2.0) / 144.0)) + let itemScaleFraction = patternScaleValueAt(fraction: min(1.0, component.avatarTransitionFraction * 1.33), t: itemDistanceFraction, reverse: false) func interpolatePosition(from: PositionGenerator.Position, to: PositionGenerator.Position, t: CGFloat) -> PositionGenerator.Position { let clampedT = max(0, min(1, t)) @@ -316,13 +316,16 @@ public final class PeerInfoGiftsCoverComponent: Component { return PositionGenerator.Position(distance: interpolatedDistance, angle: interpolatedAngle, scale: from.scale) } - let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + .pi * 0.18, scale: iconPosition.scale) + let toAngle: CGFloat = .pi * 0.18 + let centerPosition = PositionGenerator.Position(distance: 0.0, angle: iconPosition.angle + toAngle, scale: iconPosition.scale) let effectivePosition = interpolatePosition(from: iconPosition, to: centerPosition, t: itemScaleFraction) + let effectiveAngle = toAngle * itemScaleFraction - let position = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter) + let absolutePosition = getAbsolutePosition(position: effectivePosition, centerPoint: component.avatarCenter) iconTransition.setBounds(layer: iconLayer, bounds: CGRect(origin: .zero, size: iconSize)) - iconTransition.setPosition(layer: iconLayer, position: position) + iconTransition.setPosition(layer: iconLayer, position: absolutePosition) + iconLayer.updateRotation(effectiveAngle, transition: iconTransition) iconTransition.setScale(layer: iconLayer, scale: iconPosition.scale * (1.0 - itemScaleFraction)) iconTransition.setAlpha(layer: iconLayer, alpha: 1.0 - itemScaleFraction) @@ -585,7 +588,12 @@ private class GiftIconLayer: SimpleLayer { override func layoutSublayers() { self.shadowLayer.frame = CGRect(origin: .zero, size: self.bounds.size).insetBy(dx: -8.0, dy: -8.0) - self.animationLayer.frame = CGRect(origin: .zero, size: self.bounds.size) + self.animationLayer.bounds = CGRect(origin: .zero, size: self.bounds.size) + self.animationLayer.position = CGPoint(x: self.bounds.width / 2.0, y: self.bounds.height / 2.0) + } + + func updateRotation(_ angle: CGFloat, transition: ComponentTransition) { + self.animationLayer.transform = CATransform3DMakeRotation(angle, 0.0, 0.0, 1.0) } func startAnimations(index: Int) { @@ -719,7 +727,6 @@ private struct PositionGenerator { let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2) let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next()) - // Get the absolute position to check boundaries and collisions let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint) if absolutePosition.x - itemSize.width/2 < self.edgePadding || @@ -766,9 +773,7 @@ private struct PositionGenerator { let angleOffset: CGFloat = placeOnLeftSide ? .pi/2 : -(.pi/2) let angle = angleOffset + angleRange * CGFloat(self.lokiRng.next()) - // Get the absolute position to check boundaries and collisions let absolutePosition = getAbsolutePosition(distance: distance, angle: angle, centerPoint: centerPoint) - if absolutePosition.x - itemSize.width/2 < self.edgePadding || absolutePosition.x + itemSize.width/2 > self.containerSize.width - self.edgePadding || absolutePosition.y - itemSize.height/2 < self.edgePadding || diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift index 3b08ca63bd..98202da6c3 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNavigationButtonContainerNode.swift @@ -74,6 +74,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { func update(size: CGSize, presentationData: PresentationData, leftButtons: [PeerInfoHeaderNavigationButtonSpec], rightButtons: [PeerInfoHeaderNavigationButtonSpec], expandFraction: CGFloat, shouldAnimateIn: Bool, transition: ContainedViewLayoutTransition) { let sideInset: CGFloat = 24.0 + let expandedSideInset: CGFloat = 16.0 let maximumExpandOffset: CGFloat = 14.0 let expandOffset: CGFloat = -expandFraction * maximumExpandOffset @@ -188,7 +189,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { self.currentRightButtons = rightButtons var nextRegularButtonOrigin = size.width - sideInset - 8.0 - var nextExpandedButtonOrigin = size.width - sideInset - 8.0 + var nextExpandedButtonOrigin = size.width - expandedSideInset for spec in rightButtons.reversed() { let buttonNode: PeerInfoHeaderNavigationButton var wasAdded = false @@ -257,7 +258,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { for key in removeKeys { if let buttonNode = self.rightButtonNodes.removeValue(forKey: key) { if key == .moreSearchSort || key == .searchWithTags || key == .standaloneSearch { - buttonNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in + buttonNode.layer.animateAlpha(from: buttonNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak buttonNode] _ in buttonNode?.removeFromSupernode() }) buttonNode.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) @@ -268,7 +269,7 @@ final class PeerInfoHeaderNavigationButtonContainerNode: SparseNode { } } else { var nextRegularButtonOrigin = size.width - sideInset - 8.0 - var nextExpandedButtonOrigin = size.width - sideInset - 8.0 + var nextExpandedButtonOrigin = size.width - expandedSideInset for spec in rightButtons.reversed() { var key = spec.key diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift index b0aedadc45..0033cb9aa8 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoHeaderNode.swift @@ -2289,6 +2289,9 @@ final class PeerInfoHeaderNode: ASDisplayNode { } if !buttonKeys.isEmpty { backgroundDefaultHeight = 327.0 + if metrics.isTablet { + backgroundDefaultHeight += 60.0 + } } hasBackground = true } else if let peer { @@ -2308,6 +2311,7 @@ final class PeerInfoHeaderNode: ASDisplayNode { files: [:], isDark: presentationData.theme.overallDarkAppearance, avatarCenter: apparentAvatarFrame.center.offsetBy(dx: bannerInset, dy: 0.0), + avatarSize: apparentAvatarFrame.size, avatarScale: avatarScale, defaultHeight: backgroundDefaultHeight, gradientCenter: CGPoint(x: 0.5, y: buttonKeys.isEmpty ? 0.5 : 0.45), diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift index a8e93576fc..66026f44ab 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoVisualMediaPaneNode/Sources/PeerInfoGiftsPaneNode.swift @@ -379,7 +379,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let optionSpacing: CGFloat = 10.0 let itemsSideInset = params.sideInset + 16.0 - let defaultItemsInRow = params.size.width > params.size.height ? 5 : 3 + let defaultItemsInRow = params.size.width > params.size.height || params.size.width > 414.0 ? 5 : 3 let itemsInRow = max(1, min(starsProducts.count, defaultItemsInRow)) let defaultOptionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(defaultItemsInRow - 1)) / CGFloat(defaultItemsInRow) let optionWidth = (params.size.width - itemsSideInset * 2.0 - optionSpacing * CGFloat(itemsInRow - 1)) / CGFloat(itemsInRow) @@ -613,7 +613,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr } var bottomScrollInset: CGFloat = 0.0 - var contentHeight = ceil(CGFloat(starsProducts.count) / 3.0) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0 + var contentHeight = ceil(CGFloat(starsProducts.count) / CGFloat(defaultItemsInRow)) * (starsOptionSize.height + optionSpacing) - optionSpacing + topInset + 16.0 let size = params.size let sideInset = params.sideInset @@ -677,13 +677,14 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr let buttonSideInset = sideInset + 16.0 let buttonSize = CGSize(width: size.width - buttonSideInset * 2.0, height: 50.0) - var bottomPanelHeight = max(8.0, bottomInset) + buttonSize.height + 8.0 + let effectiveBottomInset = max(8.0, bottomInset) + var bottomPanelHeight = effectiveBottomInset + buttonSize.height + 8.0 if params.visibleHeight < 110.0 { scrollOffset -= bottomPanelHeight } let panelTransition = ComponentTransition.immediate - panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - bottomInset - buttonSize.height - scrollOffset), size: buttonSize)) + panelTransition.setFrame(view: panelButton.view, frame: CGRect(origin: CGPoint(x: buttonSideInset, y: size.height - effectiveBottomInset - buttonSize.height - scrollOffset), size: buttonSize)) panelTransition.setAlpha(view: panelButton.view, alpha: panelAlpha) let _ = panelButton.updateLayout(width: buttonSize.width, transition: .immediate) @@ -754,7 +755,7 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr if panelCheckView.superview == nil { self.view.addSubview(panelCheckView) } - panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - bottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize) + panelCheckView.frame = CGRect(origin: CGPoint(x: floor((size.width - panelCheckSize.width) / 2.0), y: size.height - effectiveBottomInset - panelCheckSize.height - 11.0 - scrollOffset), size: panelCheckSize) panelTransition.setAlpha(view: panelCheckView, alpha: panelAlpha) } panelButton.isHidden = true @@ -1015,7 +1016,26 @@ public final class PeerInfoGiftsPaneNode: ASDisplayNode, PeerInfoPaneNode, UIScr guard let self else { return } - let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone() + if self.context.isPremium { + let _ = self.context.engine.accountData.setStarGiftStatus(starGift: uniqueGift, expirationDate: nil).startStandalone() + } else { + let text = strings.Gift_View_TooltipPremiumWearing + let tooltipController = UndoOverlayController( + presentationData: presentationData, + content: .premiumPaywall(title: nil, text: text, customUndoText: nil, timeout: nil, linkAction: nil), + position: .bottom, + animateInAsReplacement: false, + appearance: UndoOverlayController.Appearance(sideInset: 16.0, bottomInset: 62.0), + action: { [weak self] action in + if let self, case .info = action { + let premiumController = self.context.sharedContext.makePremiumIntroController(context: self.context, source: .messageEffects, forceDark: false, dismissed: nil) + self.parentController?.push(premiumController) + } + return false + } + ) + self.parentController?.present(tooltipController, in: .current) + } }) }))) } diff --git a/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/BUILD b/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/BUILD index 7e5550cac0..006c97027a 100644 --- a/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/BUILD @@ -13,6 +13,7 @@ swift_library( "//submodules/AsyncDisplayKit", "//submodules/Display", "//submodules/TelegramCore", + "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AccountContext", "//submodules/ComponentFlow", "//submodules/Components/MultilineTextComponent", diff --git a/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/Sources/StarsBalanceOverlayComponent.swift b/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/Sources/StarsBalanceOverlayComponent.swift index dec416f208..c304e8ca32 100644 --- a/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/Sources/StarsBalanceOverlayComponent.swift +++ b/submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent/Sources/StarsBalanceOverlayComponent.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import Display import ComponentFlow +import SwiftSignalKit import TelegramCore import AccountContext import TelegramPresentationData @@ -38,6 +39,10 @@ public final class StarsBalanceOverlayComponent: Component { private let action = ComponentView() private var component: StarsBalanceOverlayComponent? + private var state: EmptyComponentState? + + private var balance: Int64 = 0 + private var balanceDisposable: Disposable? private var cachedChevronImage: (UIImage, PresentationTheme)? @@ -53,17 +58,43 @@ public final class StarsBalanceOverlayComponent: Component { fatalError("init(coder:) has not been implemented") } + deinit { + self.balanceDisposable?.dispose() + } + @objc private func tapped() { if let component = self.component { component.action() } } + private var isUpdating = false func update(component: StarsBalanceOverlayComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + self.isUpdating = true + defer { + self.isUpdating = false + } self.component = component + if self.balanceDisposable == nil, let starsContext = component.context.starsContext { + self.balanceDisposable = (starsContext.state + |> map { state -> Int64 in + return state?.balance.value ?? 0 + } + |> distinctUntilChanged + |> deliverOnMainQueue).start(next: { [weak self] balance in + guard let self else { + return + } + self.balance = balance + if !self.isUpdating { + self.state?.updated() + } + }) + } + let presentationData = component.context.sharedContext.currentPresentationData.with { $0 } - let balance = presentationStringsFormattedNumber(Int32(component.context.starsContext?.currentState?.balance.value ?? 0), presentationData.dateTimeFormat.groupingSeparator) + let balance = presentationStringsFormattedNumber(Int32(self.balance), presentationData.dateTimeFormat.groupingSeparator) let attributedText = parseMarkdownIntoAttributedString( presentationData.strings.StarsBalance_YourBalance("**⭐️\(balance)**").string, @@ -121,23 +152,27 @@ public final class StarsBalanceOverlayComponent: Component { if let textView = self.text.view { if textView.superview == nil { - self.addSubview(textView) + self.backgroundView.addSubview(textView) } textView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - textSize.width) / 2.0), y: 10.0), size: textSize) } if let actionView = self.action.view { if actionView.superview == nil { - self.addSubview(actionView) + self.backgroundView.addSubview(actionView) } actionView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - actionSize.width) / 2.0), y: 29.0), size: actionSize) } self.backgroundView.updateColor(color: component.theme.rootController.navigationBar.opaqueBackgroundColor, transition: .immediate) self.backgroundView.update(size: size, cornerRadius: size.height / 2.0, transition: .immediate) - transition.setFrame(view: self.backgroundView, frame: CGRect(origin: .zero, size: size)) + transition.setFrame(view: self.backgroundView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - size.width) / 2.0), y: 0.0), size: size)) - return size + return CGSize(width: availableSize.width, height: size.height) + } + + public override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { + return self.backgroundView.frame.contains(point) } } diff --git a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift index cf2e01e40e..96ebd273a3 100644 --- a/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsPurchaseScreen/Sources/StarsPurchaseScreen.swift @@ -241,6 +241,8 @@ private final class StarsPurchaseScreenContentComponent: CombinedComponent { textString = strings.Stars_Purchase_StarGiftInfo(component.peers.first?.value.compactDisplayTitle ?? "").string case .upgradeStarGift: textString = strings.Stars_Purchase_UpgradeStarGiftInfo + case .transferStarGift: + textString = strings.Stars_Purchase_TransferStarGiftInfo case let .sendMessage(peerId, _): if peerId.namespace == Namespaces.Peer.CloudUser { textString = strings.Stars_Purchase_SendMessageInfo(component.peers.first?.value.compactDisplayTitle ?? "").string @@ -828,7 +830,7 @@ private final class StarsPurchaseScreenComponent: CombinedComponent { titleText = strings.Stars_Purchase_GetStars case .gift: titleText = strings.Stars_Purchase_GiftStars - case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .sendMessage(_, requiredStars): + case let .topUp(requiredStars, _), let .transfer(_, requiredStars), let .reactions(_, requiredStars), let .subscription(_, requiredStars, _), let .unlockMedia(requiredStars), let .starGift(_, requiredStars), let .upgradeStarGift(requiredStars), let .transferStarGift(requiredStars), let .sendMessage(_, requiredStars): titleText = strings.Stars_Purchase_StarsNeeded(Int32(requiredStars)) } @@ -1274,6 +1276,8 @@ private extension StarsPurchasePurpose { return requiredStars case let .upgradeStarGift(requiredStars): return requiredStars + case let .transferStarGift(requiredStars): + return requiredStars case let .sendMessage(_, requiredStars): return requiredStars default: diff --git a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift index b4066cab07..96093e6a99 100644 --- a/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsTransactionScreen/Sources/StarsTransactionScreen.swift @@ -1530,15 +1530,15 @@ private final class StarsTransactionSheetContent: CombinedComponent { .position(CGPoint(x: buttonFrame.midX, y: buttonFrame.midY)) ) originY += button.size.height + originY += 7.0 } context.add(closeButton .position(CGPoint(x: context.availableSize.width - environment.safeInsets.left - closeButton.size.width, y: 28.0)) ) - let contentSize = CGSize(width: context.availableSize.width, height: originY + 5.0 + environment.safeInsets.bottom) - - return contentSize + let effectiveBottomInset: CGFloat = environment.metrics.isTablet ? 0.0 : environment.safeInsets.bottom + return CGSize(width: context.availableSize.width, height: originY + 5.0 + effectiveBottomInset) } } } diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 0b6ab11965..5185dd0941 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -358,6 +358,7 @@ public final class AccountContextImpl: AccountContext { let _ = currentAppConfiguration.swap(value) }) + let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode self.currentCountriesConfiguration = Atomic(value: CountriesConfiguration(countries: loadCountryCodes())) if !temp { let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift index 3477609073..583e2babda 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerOpenWebApp.swift @@ -13,6 +13,7 @@ import PresentationDataUtils import UndoUI import UrlHandling import TelegramPresentationData +import ChatInterfaceState func openWebAppImpl( context: AccountContext, @@ -182,7 +183,7 @@ func openWebAppImpl( var isInline = false var botId = botPeer.id var botName = botName - var botAddress = "" + var botAddress = botPeer.addressName ?? "" var botVerified = botPeer.isVerified if case let .inline(bot) = source { isInline = true @@ -367,8 +368,20 @@ public extension ChatControllerImpl { } } } + let inputString = "@\(botAddress) \(query)" if let chatController { - chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, "@\(botAddress) \(query)", nil) + chatController.controllerInteraction?.activateSwitchInline(selectedPeer?.id ?? peerId, inputString, nil) + } else if let selectedPeer, let navigationController = context.sharedContext.mainWindow?.viewController as? NavigationController { + let textInputState = ChatTextInputState(inputText: NSAttributedString(string: inputString)) + let _ = (ChatInterfaceState.update(engine: context.engine, peerId: selectedPeer.id, threadId: nil, { currentState in + return currentState.withUpdatedComposeInputState(textInputState) + }) + |> deliverOnMainQueue).startStandalone(completed: { [weak navigationController] in + guard let navigationController else { + return + } + context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: context, chatLocation: .peer(selectedPeer), subject: nil, updateTextInputState: textInputState, peekData: nil)) + }) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 957f6e77fe..31f5b3877a 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -5795,7 +5795,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if case let .known(value) = cachedData.businessIntro { businessIntro = value } - sendPaidMessageStars = cachedData.sendPaidMessageStars + if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat { + } else { + sendPaidMessageStars = cachedData.sendPaidMessageStars + } } else if let cachedData = peerView.cachedData as? CachedGroupData { var invitedBy: Peer? if let invitedByPeerId = cachedData.invitedBy { diff --git a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift index fbc2f26eb0..adedeb9680 100644 --- a/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatReportPeerTitlePanelNode.swift @@ -3,6 +3,7 @@ import UIKit import Display import AsyncDisplayKit import Postbox +import SwiftSignalKit import TelegramCore import TelegramPresentationData import LocalizedPeerData @@ -14,6 +15,7 @@ import TextNodeWithEntities import AnimationCache import MultiAnimationRenderer import AccountContext +import PremiumUI private enum ChatReportPeerTitleButton: Equatable { case block @@ -344,7 +346,7 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { private let context: AccountContext private let animationCache: AnimationCache private let animationRenderer: MultiAnimationRenderer - + private let separatorNode: ASDisplayNode private let closeButton: HighlightableButtonNode @@ -354,12 +356,17 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { private let emojiSeparatorNode: ASDisplayNode private var theme: PresentationTheme? + private var presentationInterfaceState: ChatPresentationInterfaceState? private var inviteInfoNode: ChatInfoTitlePanelInviteInfoNode? private var peerNearbyInfoNode: ChatInfoTitlePanelPeerNearbyInfoNode? private var cachedChevronImage: (UIImage, PresentationTheme)? + private var emojiStatusPackDisposable = MetaDisposable() + private var emojiStatusFileId: Int64? + private var emojiStatusFileAndPackTitle = Promise<(TelegramMediaFile, LoadedStickerPack)?>() + private var tapGestureRecognizer: UITapGestureRecognizer? init(context: AccountContext, animationCache: AnimationCache, animationRenderer: MultiAnimationRenderer) { @@ -391,6 +398,10 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { self.addSubnode(self.closeButton) } + deinit { + self.emojiStatusPackDisposable.dispose() + } + override func didLoad() { super.didLoad() @@ -405,27 +416,33 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { } private func openPremiumEmojiStatusDemo() { - guard let navigationController = self.interfaceInteraction?.getNavigationController() else { + guard let navigationController = self.interfaceInteraction?.getNavigationController(), let peerId = self.presentationInterfaceState?.chatLocation.peerId, let emojiStatus = self.presentationInterfaceState?.renderedPeer?.peer?.emojiStatus, case let .emoji(fileId) = emojiStatus.content else { return } - if self.context.isPremium { - let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil) - navigationController.pushViewController(controller) - } else { - var replaceImpl: ((ViewController) -> Void)? - let controller = self.context.sharedContext.makePremiumDemoController(context: self.context, subject: .emojiStatus, forceDark: false, action: { [weak self] in - guard let self else { - return - } - let controller = context.sharedContext.makePremiumIntroController(context: self.context, source: .animatedEmoji, forceDark: false, dismissed: nil) - replaceImpl?(controller) - }, dismissed: nil) - replaceImpl = { [weak controller] c in - controller?.replace(with: c) + let source: Signal = self.emojiStatusFileAndPackTitle.get() + |> take(1) + |> mapToSignal { emojiStatusFileAndPack -> Signal in + if let (file, pack) = emojiStatusFileAndPack { + return .single(.emojiStatus(peerId, fileId, file, pack)) + } else { + return .complete() } - navigationController.pushViewController(controller) } + + let _ = (source + |> deliverOnMainQueue).startStandalone(next: { [weak self, weak navigationController] source in + guard let self, let navigationController else { + return + } + let controller = PremiumIntroScreen(context: self.context, source: source) + if let textView = self.emojiStatusTextNode?.view { + controller.sourceView = textView + controller.sourceRect = CGRect(origin: .zero, size: CGSize(width: textView.frame.height, height: textView.frame.height)) + } + controller.containerView = navigationController.view + navigationController.pushViewController(controller) + }) } override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> LayoutResult { @@ -436,7 +453,8 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { self.separatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor self.emojiSeparatorNode.backgroundColor = interfaceState.theme.rootController.navigationBar.separatorColor } - + self.presentationInterfaceState = interfaceState + var panelHeight: CGFloat = 40.0 let contentRightInset: CGFloat = 14.0 + rightInset @@ -583,7 +601,43 @@ final class ChatReportPeerTitlePanelNode: ChatTitleAccessoryPanelNode { } } - if let emojiStatus = emojiStatus, case .emoji = emojiStatus.content { + if let emojiStatus = emojiStatus, case let .emoji(fileId) = emojiStatus.content { + if self.emojiStatusFileId != fileId { + self.emojiStatusFileId = fileId + + let emojiFileAndPack = self.context.engine.stickers.resolveInlineStickers(fileIds: [fileId]) + |> mapToSignal { result in + if let emojiFile = result.first?.value { + for attribute in emojiFile.attributes { + if case let .CustomEmoji(_, _, _, packReference) = attribute, let packReference = packReference { + return self.context.engine.stickers.loadedStickerPack(reference: packReference, forceActualized: false) + |> filter { result in + if case .result = result { + return true + } else { + return false + } + } + |> mapToSignal { result -> Signal<(TelegramMediaFile, LoadedStickerPack)?, NoError> in + if case let .result(_, items, _) = result { + return .single(items.first.flatMap { ($0.file._parse(), result) }) + } else { + return .complete() + } + } + } + } + } + return .complete() + } + self.emojiStatusPackDisposable.set(emojiFileAndPack.startStrict(next: { [weak self] fileAndPackTitle in + guard let self else { + return + } + self.emojiStatusFileAndPackTitle.set(.single(fileAndPackTitle)) + })) + } + self.emojiSeparatorNode.isHidden = false transition.updateFrame(node: self.emojiSeparatorNode, frame: CGRect(origin: CGPoint(x: leftInset + 12.0, y: 40.0), size: CGSize(width: width - leftInset - rightInset - 24.0, height: UIScreenPixel))) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 16a7303a7c..0e2dedb349 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -972,6 +972,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { if groupCallController.view.superview == nil { (mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController) } + } else if let streamController = self.streamController { + mainWindow.hostView.containerView.endEditing(true) + if streamController.view.superview == nil { + (mainWindow.viewController as? NavigationController)?.pushViewController(streamController) + } } } } else { @@ -1358,8 +1363,23 @@ public final class SharedAccountContextImpl: SharedAccountContext { let streamController = MediaStreamComponentController(call: group) streamController.navigationPresentation = .flatModal streamController.parentNavigationController = navigationController + + let thisCallIsOnScreenPromise = ValuePromise(false, ignoreRepeated: true) + streamController.onViewDidAppear = { + thisCallIsOnScreenPromise.set(true) + } + streamController.onViewDidDisappear = { + thisCallIsOnScreenPromise.set(false) + } + self.streamController = streamController + self.mainWindow?.hostView.containerView.endEditing(true) + + thisCallIsOnScreenPromise.set(true) + self.hasGroupCallOnScreenPromise.set(thisCallIsOnScreenPromise.get()) + beginDisplayingCallStatusBar.set(.single(Void())) + navigationController.pushViewController(streamController) } } @@ -1787,6 +1807,11 @@ public final class SharedAccountContextImpl: SharedAccountContext { mainWindow.hostView.containerView.endEditing(true) (mainWindow.viewController as? NavigationController)?.pushViewController(groupCallController) } + } else if let streamController = self.streamController { + if streamController.isNodeLoaded && streamController.view.superview == nil { + mainWindow.hostView.containerView.endEditing(true) + (mainWindow.viewController as? NavigationController)?.pushViewController(streamController) + } } } @@ -2877,62 +2902,102 @@ public final class SharedAccountContextImpl: SharedAccountContext { } } + let optionsPromise = Promise<[StarsTopUpOption]?>(nil) + if let state = context.starsContext?.currentState, state.balance < StarsAmount(value: 100, nanos: 0) { + optionsPromise.set(context.engine.payments.starsTopUpOptions() + |> map(Optional.init)) + } + presentTransferAlertImpl = { [weak controller] peer in guard let controller, case let .starGiftTransfer(_, _, gift, transferStars, _, _) = source else { return } - let alertController = giftTransferAlertController(context: context, gift: gift, peer: peer, transferStars: transferStars, commit: { [weak controller] in - completion?([peer.id]) - - guard let controller, let navigationController = controller.navigationController as? NavigationController else { - return - } - var controllers = navigationController.viewControllers - controllers = controllers.filter { !($0 is ContactSelectionController) } - - if !isChannelGift { - if peer.id.namespace == Namespaces.Peer.CloudChannel { - if let controller = context.sharedContext.makePeerInfoController( - context: context, - updatedPresentationData: nil, - peer: peer._asPeer(), - mode: .gifts, - avatarInitiallyExpanded: false, - fromChat: false, - requestsContext: nil - ) { - controllers.append(controller) + let alertController = giftTransferAlertController( + context: context, + gift: gift, + peer: peer, + transferStars: transferStars, + navigationController: controller.navigationController as? NavigationController, + commit: { [weak controller] in + let proceed: (Bool) -> Void = { waitForTopUp in + completion?([peer.id]) + + guard let controller, let navigationController = controller.navigationController as? NavigationController else { + return } - } else { - var foundController = false - for controller in controllers.reversed() { - if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { - chatController.hintPlayNextOutgoingGift() - foundController = true - break + var controllers = navigationController.viewControllers + controllers = controllers.filter { !($0 is ContactSelectionController) } + + if !isChannelGift { + if peer.id.namespace == Namespaces.Peer.CloudChannel { + if let controller = context.sharedContext.makePeerInfoController( + context: context, + updatedPresentationData: nil, + peer: peer._asPeer(), + mode: .gifts, + avatarInitiallyExpanded: false, + fromChat: false, + requestsContext: nil + ) { + controllers.append(controller) + } + } else { + var foundController = false + for controller in controllers.reversed() { + if let chatController = controller as? ChatController, case .peer(id: peer.id) = chatController.chatLocation { + chatController.hintPlayNextOutgoingGift() + foundController = true + break + } + } + if !foundController { + let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) + chatController.hintPlayNextOutgoingGift() + controllers.append(chatController) + } } } - if !foundController { - let chatController = context.sharedContext.makeChatController(context: context, chatLocation: .peer(id: peer.id), subject: nil, botStart: nil, mode: .standard(.default), params: nil) - chatController.hintPlayNextOutgoingGift() - controllers.append(chatController) + navigationController.setViewControllers(controllers, animated: true) + + Queue.mainQueue().after(0.3) { + let tooltipController = UndoOverlayController( + presentationData: presentationData, + content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string), + elevatedLayout: false, + action: { _ in return true } + ) + if let lastController = controllers.last as? ViewController { + lastController.present(tooltipController, in: .window(.root)) + } } } - } - navigationController.setViewControllers(controllers, animated: true) - - Queue.mainQueue().after(0.3) { - let tooltipController = UndoOverlayController( - presentationData: presentationData, - content: .forward(savedMessages: false, text: presentationData.strings.Gift_Transfer_Success("\(gift.title) #\(presentationStringsFormattedNumber(gift.number, presentationData.dateTimeFormat.groupingSeparator))", peer.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder)).string), - elevatedLayout: false, - action: { _ in return true } - ) - if let lastController = controllers.last as? ViewController { - lastController.present(tooltipController, in: .window(.root)) + + if transferStars > 0, let starsContext = context.starsContext, let starsState = starsContext.currentState { + if starsState.balance < StarsAmount(value: transferStars, nanos: 0) { + let _ = (optionsPromise.get() + |> filter { $0 != nil } + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak controller] options in + let purchaseController = context.sharedContext.makeStarsPurchaseScreen( + context: context, + starsContext: starsContext, + options: options ?? [], + purpose: .transferStarGift(requiredStars: transferStars), + completion: { stars in + starsContext.add(balance: StarsAmount(value: stars, nanos: 0)) + proceed(true) + } + ) + controller?.push(purchaseController) + }) + } else { + proceed(false) + } + } else { + proceed(false) } } - }) + ) controller.present(alertController, in: .window(.root)) } diff --git a/submodules/WebUI/Sources/WebAppController.swift b/submodules/WebUI/Sources/WebAppController.swift index ccef0a2cde..21a1fe7c2b 100644 --- a/submodules/WebUI/Sources/WebAppController.swift +++ b/submodules/WebUI/Sources/WebAppController.swift @@ -2232,6 +2232,9 @@ public final class WebAppController: ViewController, AttachmentContainable { self.webView?.sendEvent(name: "fullscreen_changed", data: paramsString) controller.isFullscreen = isFullscreen + if isFullscreen { + controller.requestAttachmentMenuExpansion() + } if let (layout, _) = self.validLayout, case .regular = layout.metrics.widthClass { if let snapshotView = self.webView?.snapshotView(afterScreenUpdates: false) { diff --git a/versions.json b/versions.json index 26fd2ba0c2..9146b480e6 100644 --- a/versions.json +++ b/versions.json @@ -1,5 +1,5 @@ { - "app": "11.8.1", + "app": "11.8.2", "xcode": "16.2", "bazel": "7.3.1:981f82a470bad1349322b6f51c9c6ffa0aa291dab1014fac411543c12e661dff", "macos": "15"