From 73218834ff61b7d5901e0039ea898ceeff0b3150 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Sat, 28 Jun 2025 23:42:18 +0200 Subject: [PATCH] Various improvements --- .../Sources/AccountContext.swift | 24 +- .../Sources/ServiceMessageStrings.swift | 2 +- .../Sources/TonFormat.swift | 4 +- submodules/TelegramUI/BUILD | 1 + .../ChatScheduleTimeController/BUILD | 6 + .../Sources/ChatScheduleTimeController.swift | 2 +- .../ChatScheduleTimeControllerNode.swift | 134 +++++- .../PostSuggestionsSettingsScreen.swift | 2 +- .../Stars/BalanceNeededScreen/BUILD | 1 + .../Sources/BalanceNeededScreen.swift | 99 ++-- .../Sources/StarsWithdrawalScreen.swift | 39 +- .../SuggestedPostApproveAlert/BUILD | 26 ++ .../Sources/SuggestedPostApproveAlert.swift | 441 ++++++++++++++++++ .../Resources/Animations/TonLogo.tgs | Bin 0 -> 47932 bytes .../TelegramUI/Sources/ChatController.swift | 51 +- 15 files changed, 772 insertions(+), 60 deletions(-) create mode 100644 submodules/TelegramUI/Components/SuggestedPostApproveAlert/BUILD create mode 100644 submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift create mode 100644 submodules/TelegramUI/Resources/Animations/TonLogo.tgs diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index cee7c241f6..32a87cb876 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1501,13 +1501,15 @@ public struct StarsSubscriptionConfiguration { return StarsSubscriptionConfiguration( maxFee: 2500, usdWithdrawRate: 1200, + tonUsdRate: 0, paidMessageMaxAmount: 10000, paidMessageCommissionPermille: 850, paidMessagesAvailable: false, starGiftResaleMinAmount: 125, starGiftResaleMaxAmount: 3500, starGiftCommissionPermille: 80, - channelMessageSuggestionCommissionPermille: 850, + channelMessageSuggestionStarsCommissionPermille: 850, + channelMessageSuggestionTonCommissionPermille: 850, channelMessageSuggestionMaxStarsAmount: 10000, channelMessageSuggestionMaxTonAmount: 10000000000000 ) @@ -1515,38 +1517,44 @@ public struct StarsSubscriptionConfiguration { public let maxFee: Int64 public let usdWithdrawRate: Int64 + public let tonUsdRate: Int64 public let paidMessageMaxAmount: Int64 public let paidMessageCommissionPermille: Int32 public let paidMessagesAvailable: Bool public let starGiftResaleMinAmount: Int64 public let starGiftResaleMaxAmount: Int64 public let starGiftCommissionPermille: Int32 - public let channelMessageSuggestionCommissionPermille: Int32 + public let channelMessageSuggestionStarsCommissionPermille: Int32 + public let channelMessageSuggestionTonCommissionPermille: Int32 public let channelMessageSuggestionMaxStarsAmount: Int64 public let channelMessageSuggestionMaxTonAmount: Int64 fileprivate init( maxFee: Int64, usdWithdrawRate: Int64, + tonUsdRate: Int64, paidMessageMaxAmount: Int64, paidMessageCommissionPermille: Int32, paidMessagesAvailable: Bool, starGiftResaleMinAmount: Int64, starGiftResaleMaxAmount: Int64, starGiftCommissionPermille: Int32, - channelMessageSuggestionCommissionPermille: Int32, + channelMessageSuggestionStarsCommissionPermille: Int32, + channelMessageSuggestionTonCommissionPermille: Int32, channelMessageSuggestionMaxStarsAmount: Int64, channelMessageSuggestionMaxTonAmount: Int64 ) { self.maxFee = maxFee self.usdWithdrawRate = usdWithdrawRate + self.tonUsdRate = tonUsdRate self.paidMessageMaxAmount = paidMessageMaxAmount self.paidMessageCommissionPermille = paidMessageCommissionPermille self.paidMessagesAvailable = paidMessagesAvailable self.starGiftResaleMinAmount = starGiftResaleMinAmount self.starGiftResaleMaxAmount = starGiftResaleMaxAmount self.starGiftCommissionPermille = starGiftCommissionPermille - self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille + self.channelMessageSuggestionStarsCommissionPermille = channelMessageSuggestionStarsCommissionPermille + self.channelMessageSuggestionTonCommissionPermille = channelMessageSuggestionTonCommissionPermille self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount } @@ -1555,6 +1563,7 @@ public struct StarsSubscriptionConfiguration { if let data = appConfiguration.data { let maxFee = (data["stars_subscription_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.maxFee let usdWithdrawRate = (data["stars_usd_withdraw_rate_x1000"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.usdWithdrawRate + let tonUsdRate = (data["ton_usd_rate"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.tonUsdRate let paidMessageMaxAmount = (data["stars_paid_message_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageMaxAmount let paidMessageCommissionPermille = (data["stars_paid_message_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.paidMessageCommissionPermille let paidMessagesAvailable = (data["stars_paid_messages_available"] as? Bool) ?? StarsSubscriptionConfiguration.defaultValue.paidMessagesAvailable @@ -1562,20 +1571,23 @@ public struct StarsSubscriptionConfiguration { let starGiftResaleMaxAmount = (data["stars_stargift_resale_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMaxAmount let starGiftCommissionPermille = (data["stars_stargift_resale_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftCommissionPermille - let channelMessageSuggestionCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionCommissionPermille + let channelMessageSuggestionStarsCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionStarsCommissionPermille + let channelMessageSuggestionTonCommissionPermille = (data["ton_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionTonCommissionPermille let channelMessageSuggestionMaxStarsAmount = (data["stars_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxStarsAmount let channelMessageSuggestionMaxTonAmount = (data["ton_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxTonAmount return StarsSubscriptionConfiguration( maxFee: maxFee, usdWithdrawRate: usdWithdrawRate, + tonUsdRate: tonUsdRate, paidMessageMaxAmount: paidMessageMaxAmount, paidMessageCommissionPermille: paidMessageCommissionPermille, paidMessagesAvailable: paidMessagesAvailable, starGiftResaleMinAmount: starGiftResaleMinAmount, starGiftResaleMaxAmount: starGiftResaleMaxAmount, starGiftCommissionPermille: starGiftCommissionPermille, - channelMessageSuggestionCommissionPermille: channelMessageSuggestionCommissionPermille, + channelMessageSuggestionStarsCommissionPermille: channelMessageSuggestionStarsCommissionPermille, + channelMessageSuggestionTonCommissionPermille: channelMessageSuggestionTonCommissionPermille, channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount, channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount ) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 11111d9eb3..b6cb987544 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1487,7 +1487,7 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, amountString = "\(amount.amount.value) Stars" } case .ton: - amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat)) TON" + amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat, maxDecimalPositions: 3)) TON" } attributedString = parseMarkdownIntoAttributedString("**\(channelName)** received **\(amountString)** for publishing this post", attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) case let .suggestedPostRefund(info): diff --git a/submodules/TelegramStringFormatting/Sources/TonFormat.swift b/submodules/TelegramStringFormatting/Sources/TonFormat.swift index 1b40073f81..b5a3151174 100644 --- a/submodules/TelegramStringFormatting/Sources/TonFormat.swift +++ b/submodules/TelegramStringFormatting/Sources/TonFormat.swift @@ -28,7 +28,7 @@ public func formatTonUsdValue(_ value: Int64, divide: Bool = true, rate: Double return "$\(formattedValue)" } -public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false) -> String { +public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDateTimeFormat, showPlus: Bool = false, maxDecimalPositions: Int = 2) -> String { var balanceText = "\(abs(value))" while balanceText.count < 10 { balanceText.insert("0", at: balanceText.startIndex) @@ -49,7 +49,7 @@ public func formatTonAmountText(_ value: Int64, dateTimeFormat: PresentationDate } if let dotIndex = balanceText.range(of: dateTimeFormat.decimalSeparator) { - if let endIndex = balanceText.index(dotIndex.upperBound, offsetBy: 2, limitedBy: balanceText.endIndex) { + if let endIndex = balanceText.index(dotIndex.upperBound, offsetBy: maxDecimalPositions, limitedBy: balanceText.endIndex) { balanceText = String(balanceText[balanceText.startIndex..? + var completion: ((Int32) -> Void)? var dismiss: (() -> Void)? var cancel: (() -> Void)? @@ -94,22 +103,43 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.contentBackgroundNode.backgroundColor = backgroundColor let title: String + var subtitle: String? var text: String? switch mode { case .scheduledMessages: title = self.presentationData.strings.Conversation_ScheduleMessage_Title case .reminders: title = self.presentationData.strings.Conversation_SetReminder_Title - case let .suggestPost(needsTime): + case let .suggestPost(needsTime, isAdmin, funds): if needsTime { //TODO:localize - title = "Time" + title = "Accept Terms" text = "Set the date and time you want\nthis message to be published." } else { //TODO:localize title = "Time" text = "Set the date and time you want\nyour message to be published." } + + //TODO:localize + if let funds, isAdmin { + var commissionValue: String + commissionValue = "\(Double(funds.commissionPermille) * 0.1)" + if commissionValue.hasSuffix(".0") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)]) + } else if commissionValue.hasSuffix(".00") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)]) + } + + switch funds.amount.currency { + case .stars: + let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0 + subtitle = "You will receive \(displayAmount) Stars (\(commissionValue)%)\nfor publishing this post" + case .ton: + let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0 + subtitle = "You will receive \(displayAmount) TON (\(commissionValue)%)\nfor publishing this post" + } + } } self.titleNode = ASTextNode() @@ -130,6 +160,19 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.textNode = nil } + if let subtitle { + let subtitleNode = ASTextNode() + subtitleNode.attributedText = NSAttributedString(string: subtitle, font: Font.regular(15.0), textColor: textColor) + subtitleNode.maximumNumberOfLines = 0 + subtitleNode.textAlignment = .center + subtitleNode.lineSpacing = 0.2 + subtitleNode.accessibilityLabel = text + subtitleNode.accessibilityTraits = [.staticText] + self.subtitleNode = subtitleNode + } else { + self.subtitleNode = nil + } + self.cancelButton = HighlightableButtonNode() self.cancelButton.setTitle(self.presentationData.strings.Common_Cancel, with: Font.regular(17.0), with: accentColor, for: .normal) self.cancelButton.accessibilityLabel = self.presentationData.strings.Common_Cancel @@ -139,7 +182,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.onlineButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false) switch mode { - case let .suggestPost(needsTime): + case let .suggestPost(needsTime, _, _): //TODO:localize if needsTime { self.onlineButton.title = "Post Now" @@ -172,6 +215,9 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel self.backgroundNode.addSubnode(self.effectNode) self.backgroundNode.addSubnode(self.contentBackgroundNode) self.contentContainerNode.addSubnode(self.titleNode) + if let subtitleNode = self.subtitleNode { + self.contentContainerNode.addSubnode(subtitleNode) + } if let textNode = self.textNode { self.contentContainerNode.addSubnode(textNode) } @@ -334,7 +380,7 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel } else { self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string } - case let .suggestPost(needsTime): + case let .suggestPost(needsTime, _, _): if needsTime { if calendar.isDateInToday(date) { self.doneButton.title = self.presentationData.strings.SuggestPost_Time_SendToday(time).string @@ -386,10 +432,14 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel let targetBounds = self.bounds self.bounds = self.bounds.offsetBy(dx: 0.0, dy: -offset) self.dimNode.position = CGPoint(x: dimPosition.x, y: dimPosition.y - offset) - transition.animateView({ - self.bounds = targetBounds - self.dimNode.position = dimPosition - }) + + transition.updateBounds(layer: self.layer, bounds: targetBounds) + transition.updatePosition(layer: self.dimNode.layer, position: dimPosition) + + if let toastView = self.toast?.view { + toastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + transition.animatePositionAdditive(layer: toastView.layer, offset: CGPoint(x: 0.0, y: -offset)) + } } func animateOut(completion: (() -> Void)? = nil) { @@ -415,6 +465,12 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel offsetCompleted = true internalCompletion() }) + + if let toastView = self.toast?.view { + toastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + }) + toastView.layer.animatePosition(from: CGPoint(), to: CGPoint(x: 0.0, y: -offset), duration: 0.3, timingFunction: kCAMediaTimingFunctionSpring, removeOnCompletion: false, additive: true) + } } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -463,6 +519,17 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel let textControlSpacing: CGFloat = -8.0 let textDoneSpacing: CGFloat = 21.0 + + let subtitleTopSpacing: CGFloat = 22.0 + let subtitleControlSpacing: CGFloat = 8.0 + + let subtitleSize = self.subtitleNode?.measure(CGSize(width: width, height: 1000.0)) + var controlOffset: CGFloat = 0.0 + if let subtitleSize { + contentHeight += subtitleSize.height + subtitleTopSpacing + subtitleControlSpacing + controlOffset += subtitleTopSpacing + subtitleControlSpacing + 20.0 + } + let textSize = self.textNode?.measure(CGSize(width: width, height: 1000.0)) if let textSize { contentHeight += textSize.height + textControlSpacing + textDoneSpacing @@ -486,6 +553,11 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel let titleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - titleSize.width) / 2.0), y: 16.0), size: titleSize) transition.updateFrame(node: self.titleNode, frame: titleFrame) + if let subtitleNode = self.subtitleNode, let subtitleSize { + let subtitleFrame = CGRect(origin: CGPoint(x: floor((contentFrame.width - subtitleSize.width) / 2.0), y: titleFrame.maxY + subtitleTopSpacing), size: subtitleSize) + transition.updateFrame(node: subtitleNode, frame: subtitleFrame) + } + let cancelSize = self.cancelButton.measure(CGSize(width: width, height: titleHeight)) let cancelFrame = CGRect(origin: CGPoint(x: 16.0, y: 16.0), size: cancelSize) transition.updateFrame(node: self.cancelButton, frame: cancelFrame) @@ -503,8 +575,52 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel let onlineButtonHeight = self.onlineButton.updateLayout(width: contentFrame.width - buttonInset * 2.0, transition: transition) transition.updateFrame(node: self.onlineButton, frame: CGRect(x: buttonInset, y: contentHeight - onlineButtonHeight - cleanInsets.bottom - 16.0, width: contentFrame.width, height: onlineButtonHeight)) - self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0), size: CGSize(width: contentFrame.width, height: pickerHeight)) + self.pickerView?.frame = CGRect(origin: CGPoint(x: 0.0, y: 54.0 + controlOffset), size: CGSize(width: contentFrame.width, height: pickerHeight)) transition.updateFrame(node: self.contentContainerNode, frame: contentContainerFrame) + + if case let .suggestPost(_, isAdmin, funds) = self.mode, isAdmin, let funds, funds.amount.currency == .stars { + let toast: ComponentView + if let current = self.toast { + toast = current + } else { + toast = ComponentView() + self.toast = toast + } + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + //TODO:localize + let playOnce = ActionSlot() + let toastSize = toast.update( + transition: ComponentTransition(transition), + component: AnyComponent(ToastContentComponent( + icon: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_infotip"), + startingPosition: .begin, + size: CGSize(width: 32.0, height: 32.0), + playOnce: playOnce + )), + content: AnyComponent(VStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .markdown(text: "Transactions in **Stars** may be reversed by the payment provider within **21** days. Only accept Stars from people you trust.", attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + maximumNumberOfLines: 0 + ))) + ], alignment: .left, spacing: 6.0)), + insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0), + iconSpacing: 12.0 + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 12.0 * 2.0, height: 1000.0) + ) + let toastFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 12.0, y: layout.insets(options: .statusBar).top + 4.0), size: toastSize) + if let toastView = toast.view { + if toastView.superview == nil { + self.view.addSubview(toastView) + playOnce.invoke(()) + } + transition.updatePosition(layer: toastView.layer, position: toastFrame.center) + transition.updateBounds(layer: toastView.layer, bounds: CGRect(origin: CGPoint(), size: toastFrame.size)) + } + } } } diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index 947cb43f32..9b525e8a41 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -503,7 +503,7 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain super.init(context: context, component: PostSuggestionsSettingsScreenComponent( context: context, usdWithdrawRate: configuration.usdWithdrawRate, - channelMessageSuggestionCommissionPermille: Int(configuration.channelMessageSuggestionCommissionPermille), + channelMessageSuggestionCommissionPermille: Int(configuration.paidMessageCommissionPermille), peer: peer, initialPrice: initialPrice, completion: completion diff --git a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD index 4c9aad1501..1f4cce9b1f 100644 --- a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/BUILD @@ -22,6 +22,7 @@ swift_library( "//submodules/Components/SheetComponent", "//submodules/TelegramUI/Components/ButtonComponent", "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/TelegramCore", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift index c8df11eb3b..c3599c66b5 100644 --- a/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift +++ b/submodules/TelegramUI/Components/Stars/BalanceNeededScreen/Sources/BalanceNeededScreen.swift @@ -12,17 +12,25 @@ import BalancedTextComponent import Markdown import TelegramStringFormatting import BundleIconComponent +import TelegramCore +import TelegramPresentationData private final class BalanceNeededSheetContentComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment + let context: AccountContext + let amount: StarsAmount let action: () -> Void let dismiss: () -> Void init( + context: AccountContext, + amount: StarsAmount, action: @escaping () -> Void, dismiss: @escaping () -> Void ) { + self.context = context + self.amount = amount self.action = action self.dismiss = dismiss } @@ -37,11 +45,13 @@ private final class BalanceNeededSheetContentComponent: Component { private let text = ComponentView() private let button = ComponentView() - private var cancelButton: ComponentView? + private let closeButton = ComponentView() private var component: BalanceNeededSheetContentComponent? private weak var state: EmptyComponentState? + private var cachedCloseImage: (UIImage, PresentationTheme)? + override init(frame: CGRect) { super.init(frame: frame) } @@ -61,61 +71,64 @@ private final class BalanceNeededSheetContentComponent: Component { let sideInset: CGFloat = 16.0 - let cancelButton: ComponentView - if let current = self.cancelButton { - cancelButton = current + let closeImage: UIImage + if let (image, theme) = self.cachedCloseImage, theme === environment.theme { + closeImage = image } else { - cancelButton = ComponentView() - self.cancelButton = cancelButton + closeImage = generateCloseButtonImage(backgroundColor: UIColor(rgb: 0x808084, alpha: 0.1), foregroundColor: environment.theme.actionSheet.inputClearButtonColor)! + self.cachedCloseImage = (closeImage, environment.theme) } - let cancelButtonSize = cancelButton.update( - transition: transition, + let closeButtonSize = self.closeButton.update( + transition: .immediate, component: AnyComponent(Button( - content: AnyComponent(Text(text: environment.strings.Common_Cancel, font: Font.regular(17.0), color: environment.theme.list.itemAccentColor)), + content: AnyComponent(Image(image: closeImage)), action: { [weak self] in guard let self, let component = self.component else { return } component.dismiss() } - ).minSize(CGSize(width: 8.0, height: 44.0))), + )), environment: {}, - containerSize: CGSize(width: 200.0, height: 100.0) + containerSize: CGSize(width: 30.0, height: 30.0) ) - if let cancelButtonView = cancelButton.view { - if cancelButtonView.superview == nil { - self.addSubview(cancelButtonView) + let closeButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - closeButtonSize.width - 16.0, y: 12.0), size: closeButtonSize) + if let closeButtonView = self.closeButton.view { + if closeButtonView.superview == nil { + self.addSubview(closeButtonView) } - transition.setFrame(view: cancelButtonView, frame: CGRect(origin: CGPoint(x: 16.0, y: 6.0), size: cancelButtonSize)) + transition.setFrame(view: closeButtonView, frame: closeButtonFrame) } var contentHeight: CGFloat = 0.0 contentHeight += 32.0 - let iconSize = self.icon.update( + let iconSize = CGSize(width: 120.0, height: 120.0) + let _ = self.icon.update( transition: transition, component: AnyComponent(LottieComponent( - content: LottieComponent.AppBundleContent(name: "StoryUpgradeSheet"), + content: LottieComponent.AppBundleContent(name: "TonLogo"), color: nil, startingPosition: .begin, - size: CGSize(width: 100.0, height: 100.0) + size: iconSize, + loop: true )), environment: {}, - containerSize: CGSize(width: 100.0, height: 100.0) + containerSize: iconSize ) if let iconView = self.icon.view { if iconView.superview == nil { self.addSubview(iconView) } - transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 42.0), size: iconSize)) + transition.setFrame(view: iconView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: 16.0), size: iconSize)) } - contentHeight += 138.0 + contentHeight += 110.0 let titleSize = self.title.update( transition: transition, component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: environment.strings.Story_UpgradeQuality_Title, font: Font.semibold(20.0), textColor: environment.theme.list.itemPrimaryTextColor)), + text: .plain(NSAttributedString(string: "\(formatTonAmountText(component.amount.value, dateTimeFormat: component.context.sharedContext.currentPresentationData.with({ $0 }).dateTimeFormat)) TON Needed", font: Font.bold(24.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0 )), @@ -131,10 +144,11 @@ private final class BalanceNeededSheetContentComponent: Component { contentHeight += titleSize.height contentHeight += 14.0 + //TODO:localize let textSize = self.text.update( transition: transition, component: AnyComponent(BalancedTextComponent( - text: .plain(NSAttributedString(string: environment.strings.Story_UpgradeQuality_Text, font: Font.regular(14.0), textColor: environment.theme.list.itemSecondaryTextColor)), + text: .plain(NSAttributedString(string: "You can add funds to your balance via the third-party platform Fragment.", font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor)), horizontalAlignment: .center, maximumNumberOfLines: 0, lineSpacing: 0.18 @@ -149,10 +163,9 @@ private final class BalanceNeededSheetContentComponent: Component { transition.setFrame(view: textView, frame: CGRect(origin: CGPoint(x: floor((availableSize.width - textSize.width) * 0.5), y: contentHeight), size: textSize)) } contentHeight += textSize.height - contentHeight += 12.0 - - contentHeight += 32.0 + contentHeight += 24.0 + //TODO:localize let buttonSize = self.button.update( transition: transition, component: AnyComponent(ButtonComponent( @@ -162,7 +175,7 @@ private final class BalanceNeededSheetContentComponent: Component { pressedColor: environment.theme.list.itemCheckColors.fillColor.withMultipliedAlpha(0.8) ), content: AnyComponentWithIdentity(id: AnyHashable(0 as Int), component: AnyComponent(MultilineTextComponent( - text: .plain(NSAttributedString(string: "Add Funds via Fragment")) + text: .plain(NSAttributedString(string: "Add Funds via Fragment", font: Font.semibold(17.0), textColor: environment.theme.list.itemCheckColors.foregroundColor)) ))), isEnabled: true, allowActionWhenDisabled: true, @@ -190,7 +203,7 @@ private final class BalanceNeededSheetContentComponent: Component { if environment.safeInsets.bottom.isZero { contentHeight += 16.0 } else { - contentHeight += environment.safeInsets.bottom + 14.0 + contentHeight += environment.safeInsets.bottom + 8.0 } return CGSize(width: availableSize.width, height: contentHeight) @@ -210,13 +223,16 @@ private final class BalanceNeededScreenComponent: Component { typealias EnvironmentType = ViewControllerComponentContainer.Environment let context: AccountContext + let amount: StarsAmount let buttonAction: (() -> Void)? init( context: AccountContext, + amount: StarsAmount, buttonAction: (() -> Void)? ) { self.context = context + self.amount = amount self.buttonAction = buttonAction } @@ -268,6 +284,8 @@ private final class BalanceNeededScreenComponent: Component { transition: transition, component: AnyComponent(SheetComponent( content: AnyComponent(BalanceNeededSheetContentComponent( + context: component.context, + amount: component.amount, action: { [weak self] in guard let self else { return @@ -291,7 +309,7 @@ private final class BalanceNeededScreenComponent: Component { }) } )), - backgroundColor: .color(environment.theme.overallDarkAppearance ? environment.theme.list.itemBlocksBackgroundColor : environment.theme.list.blocksBackgroundColor), + backgroundColor: .color(environment.theme.actionSheet.opaqueItemBackgroundColor), animateOut: self.sheetAnimateOut )), environment: { @@ -323,10 +341,12 @@ private final class BalanceNeededScreenComponent: Component { public class BalanceNeededScreen: ViewControllerComponentContainer { public init( context: AccountContext, + amount: StarsAmount, buttonAction: (() -> Void)? = nil ) { super.init(context: context, component: BalanceNeededScreenComponent( context: context, + amount: amount, buttonAction: buttonAction ), navigationBarAppearance: .none) @@ -359,3 +379,24 @@ public class BalanceNeededScreen: ViewControllerComponentContainer { self.wasDismissed?() } } + +func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor) -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 30.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + + context.setFillColor(backgroundColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + context.setLineWidth(2.0) + context.setLineCap(.round) + context.setStrokeColor(foregroundColor.cgColor) + + context.move(to: CGPoint(x: 10.0, y: 10.0)) + context.addLine(to: CGPoint(x: 20.0, y: 20.0)) + context.strokePath() + + context.move(to: CGPoint(x: 20.0, y: 10.0)) + context.addLine(to: CGPoint(x: 10.0, y: 20.0)) + context.strokePath() + }) +} diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index ba2a3c7a31..a01a1a9c65 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -229,6 +229,17 @@ private final class SheetContent: CombinedComponent { amountPlaceholder = "Price" minAmount = StarsAmount(value: 0, nanos: 0) + + if let usdWithdrawRate = withdrawConfiguration.usdWithdrawRate, let tonUsdRate = withdrawConfiguration.tonUsdRate, let amount = state.amount, amount > StarsAmount.zero { + switch state.currency { + case .stars: + let usdRate = Double(usdWithdrawRate) / 1000.0 / 100.0 + amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + case .ton: + let usdRate = Double(tonUsdRate) / 1000.0 / 1000000.0 + amountLabel = "≈\(formatTonUsdValue(amount.value, divide: false, rate: usdRate, dateTimeFormat: environment.dateTimeFormat))" + } + } } let title = title.update( @@ -634,7 +645,7 @@ private final class SheetContent: CombinedComponent { let theme = environment.theme let minimalTime: Int32 = Int32(Date().timeIntervalSince1970) + 5 * 60 + 10 - let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: minimalTime, dismissByTapOutside: true, completion: { [weak state] time in + let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false, isAdmin: false, funds: nil), style: .default, currentTime: state.timestamp, minimalTime: minimalTime, dismissByTapOutside: true, completion: { [weak state] time in guard let state else { return } @@ -789,8 +800,20 @@ private final class SheetContent: CombinedComponent { } case .ton: if let balance = state.tonBalance, amount > balance { + let needed = amount - balance + var fragmentUrl = "https://fragment.com/ads/topup" + if let data = state.context.currentAppConfiguration.with({ $0 }).data, let value = data["ton_topup_url"] as? String { + fragmentUrl = value + } controller.push(BalanceNeededScreen( - context: state.context + context: state.context, + amount: needed, + buttonAction: { [weak state] in + guard let state else { + return + } + state.context.sharedContext.applicationBindings.openUrl(fragmentUrl) + } )) return } @@ -1641,17 +1664,19 @@ func generateCloseButtonImage(backgroundColor: UIColor, foregroundColor: UIColor private struct StarsWithdrawConfiguration { static var defaultValue: StarsWithdrawConfiguration { - return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil, usdWithdrawRate: nil) + return StarsWithdrawConfiguration(minWithdrawAmount: nil, maxPaidMediaAmount: nil, usdWithdrawRate: nil, tonUsdRate: nil) } let minWithdrawAmount: Int64? let maxPaidMediaAmount: Int64? let usdWithdrawRate: Double? + let tonUsdRate: Double? - fileprivate init(minWithdrawAmount: Int64?, maxPaidMediaAmount: Int64?, usdWithdrawRate: Double?) { + fileprivate init(minWithdrawAmount: Int64?, maxPaidMediaAmount: Int64?, usdWithdrawRate: Double?, tonUsdRate: Double?) { self.minWithdrawAmount = minWithdrawAmount self.maxPaidMediaAmount = maxPaidMediaAmount self.usdWithdrawRate = usdWithdrawRate + self.tonUsdRate = tonUsdRate } static func with(appConfiguration: AppConfiguration) -> StarsWithdrawConfiguration { @@ -1668,8 +1693,12 @@ private struct StarsWithdrawConfiguration { if let value = data["stars_usd_withdraw_rate_x1000"] as? Double { usdWithdrawRate = value } + var tonUsdRate: Double? + if let value = data["ton_usd_rate"] as? Double { + tonUsdRate = value + } - return StarsWithdrawConfiguration(minWithdrawAmount: minWithdrawAmount, maxPaidMediaAmount: maxPaidMediaAmount, usdWithdrawRate: usdWithdrawRate) + return StarsWithdrawConfiguration(minWithdrawAmount: minWithdrawAmount, maxPaidMediaAmount: maxPaidMediaAmount, usdWithdrawRate: usdWithdrawRate, tonUsdRate: tonUsdRate) } else { return .defaultValue } diff --git a/submodules/TelegramUI/Components/SuggestedPostApproveAlert/BUILD b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/BUILD new file mode 100644 index 0000000000..21fa6a5202 --- /dev/null +++ b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/BUILD @@ -0,0 +1,26 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SuggestedPostApproveAlert", + module_name = "SuggestedPostApproveAlert", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/Display", + "//submodules/AsyncDisplayKit", + "//submodules/TelegramPresentationData", + "//submodules/ComponentFlow", + "//submodules/Components/ComponentDisplayAdapters", + "//submodules/Markdown", + "//submodules/TelegramUI/Components/ToastComponent", + "//submodules/TelegramUI/Components/LottieComponent", + "//submodules/Components/MultilineTextComponent", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift new file mode 100644 index 0000000000..76c1d318fc --- /dev/null +++ b/submodules/TelegramUI/Components/SuggestedPostApproveAlert/Sources/SuggestedPostApproveAlert.swift @@ -0,0 +1,441 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import Markdown +import Display +import TelegramPresentationData +import ComponentFlow +import ToastComponent +import Markdown +import LottieComponent +import MultilineTextComponent +import ComponentDisplayAdapters + +private let alertWidth: CGFloat = 270.0 + +private final class SuggestedPostApproveAlertContentNode: AlertContentNode { + private var theme: AlertControllerTheme + private let actionLayout: TextAlertContentActionLayout + + private let titleNode: ImmediateTextNode? + private let textNode: ImmediateTextNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + private var validLayout: CGSize? + + private let _dismissOnOutsideTap: Bool + override public var dismissOnOutsideTap: Bool { + return self._dismissOnOutsideTap + } + + private var highlightedItemIndex: Int? = nil + + public var textAttributeAction: (NSAttributedString.Key, (Any) -> Void)? { + didSet { + if let (attribute, textAttributeAction) = self.textAttributeAction { + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[attribute] { + return attribute + } else { + return nil + } + } + self.textNode.tapAttributeAction = { attributes, _ in + if let value = attributes[attribute] { + textAttributeAction(value) + } + } + self.textNode.linkHighlightColor = self.theme.accentColor.withAlphaComponent(0.5) + } else { + self.textNode.highlightAttributeAction = nil + self.textNode.tapAttributeAction = nil + } + } + } + + public init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout, dismissOnOutsideTap: Bool, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil) { + self.theme = theme + self.actionLayout = actionLayout + self._dismissOnOutsideTap = dismissOnOutsideTap + if let title = title { + let titleNode = ImmediateTextNode() + titleNode.attributedText = title + titleNode.displaysAsynchronously = false + titleNode.isUserInteractionEnabled = false + titleNode.maximumNumberOfLines = 4 + titleNode.truncationType = .end + titleNode.isAccessibilityElement = true + titleNode.accessibilityLabel = title.string + self.titleNode = titleNode + } else { + self.titleNode = nil + } + + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 0 + self.textNode.attributedText = text + self.textNode.displaysAsynchronously = false + self.textNode.isLayerBacked = false + self.textNode.isAccessibilityElement = true + self.textNode.accessibilityLabel = text.string + self.textNode.insets = UIEdgeInsets(top: 1.0, left: 1.0, bottom: 1.0, right: 1.0) + self.textNode.tapAttributeAction = linkAction + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] { + return NSAttributedString.Key(rawValue: "URL") + } else { + return nil + } + } + self.textNode.linkHighlightColor = theme.accentColor.withMultipliedAlpha(0.1) + if text.length != 0 { + if let paragraphStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle { + self.textNode.textAlignment = paragraphStyle.alignment + } + } + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + self.actionNodesSeparator.backgroundColor = theme.separatorColor + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(theme: theme, action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + separatorNode.backgroundColor = theme.separatorColor + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + if let titleNode = self.titleNode { + self.addSubnode(titleNode) + } + self.addSubnode(self.textNode) + + self.addSubnode(self.actionNodesSeparator) + + var i = 0 + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + + let index = i + actionNode.highlightedUpdated = { [weak self] highlighted in + if highlighted { + self?.highlightedItemIndex = index + } + } + i += 1 + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + } + + func setHighlightedItemIndex(_ index: Int?, update: Bool = false) { + self.highlightedItemIndex = index + + if update { + var i = 0 + for actionNode in self.actionNodes { + if i == index { + actionNode.setHighlighted(true, animated: false) + } else { + actionNode.setHighlighted(false, animated: false) + } + i += 1 + } + } + } + + override public func decreaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? 0 + + self.setHighlightedItemIndex(max(0, currentHighlightedIndex - 1), update: true) + } + + override public func increaseHighlightedIndex() { + let currentHighlightedIndex = self.highlightedItemIndex ?? -1 + + self.setHighlightedItemIndex(min(self.actionNodes.count - 1, currentHighlightedIndex + 1), update: true) + } + + override public func performHighlightedAction() { + guard let highlightedItemIndex = self.highlightedItemIndex else { + return + } + + var i = 0 + for itemNode in self.actionNodes { + if i == highlightedItemIndex { + itemNode.performAction() + return + } + i += 1 + } + } + + override public func updateTheme(_ theme: AlertControllerTheme) { + self.theme = theme + + if let titleNode = self.titleNode, let attributedText = titleNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: attributedText) + updatedText.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.primaryColor, range: NSRange(location: 0, length: updatedText.length)) + titleNode.attributedText = updatedText + } + if let attributedText = self.textNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: attributedText) + updatedText.addAttribute(NSAttributedString.Key.foregroundColor, value: theme.primaryColor, range: NSRange(location: 0, length: updatedText.length)) + self.textNode.attributedText = updatedText + } + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + + override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + self.validLayout = size + + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + var size = size + size.width = min(size.width, alertWidth) + + var titleSize: CGSize? + if let titleNode = self.titleNode { + titleSize = titleNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + } + let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + + let actionButtonHeight: CGFloat = 44.0 + + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = self.actionLayout + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.updateLayout(CGSize(width: maxActionWidth, height: actionButtonHeight)) + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } + } + + let resultSize: CGSize + + var actionsHeight: CGFloat = 0.0 + switch effectiveActionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + + let contentWidth = alertWidth - insets.left - insets.right + if let titleNode = self.titleNode, let titleSize = titleSize { + let spacing: CGFloat = 6.0 + let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) + transition.updateFrame(node: titleNode, frame: titleFrame) + + let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame.offsetBy(dx: -1.0, dy: -1.0)) + + resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom) + } else { + let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: insets.top), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame) + + resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) + } + + self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + switch effectiveActionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + switch effectiveActionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width + } + + let actionNodeFrame: CGRect + switch effectiveActionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } + + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +private final class SuggestedPostAlertImpl: AlertController { + private let toastText: String? + private var toast: ComponentView? + + init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool, toastText: String?) { + self.toastText = toastText + + super.init(theme: theme, contentNode: contentNode, allowInputInset: allowInputInset) + + self.willDismiss = { [weak self] in + guard let self else { + return + } + if let toastView = self.toast?.view { + toastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + } + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if let toastText = self.toastText { + let toast: ComponentView + if let current = self.toast { + toast = current + } else { + toast = ComponentView() + self.toast = toast + } + let body = MarkdownAttributeSet(font: Font.regular(14.0), textColor: .white) + let bold = MarkdownAttributeSet(font: Font.semibold(14.0), textColor: .white) + //TODO:localize + let playOnce = ActionSlot() + let toastSize = toast.update( + transition: ComponentTransition(transition), + component: AnyComponent(ToastContentComponent( + icon: AnyComponent(LottieComponent( + content: LottieComponent.AppBundleContent(name: "anim_infotip"), + startingPosition: .begin, + size: CGSize(width: 32.0, height: 32.0), + playOnce: playOnce + )), + content: AnyComponent(VStack([ + AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .markdown(text: toastText, attributes: MarkdownAttributes(body: body, bold: bold, link: body, linkAttribute: { _ in nil })), + maximumNumberOfLines: 0 + ))) + ], alignment: .left, spacing: 6.0)), + insets: UIEdgeInsets(top: 10.0, left: 12.0, bottom: 10.0, right: 10.0), + iconSpacing: 12.0 + )), + environment: {}, + containerSize: CGSize(width: layout.size.width - layout.safeInsets.left - layout.safeInsets.right - 12.0 * 2.0, height: 1000.0) + ) + let toastFrame = CGRect(origin: CGPoint(x: layout.safeInsets.left + 12.0, y: layout.insets(options: .statusBar).top + 4.0), size: toastSize) + if let toastView = toast.view { + if toastView.superview == nil { + self.view.addSubview(toastView) + playOnce.invoke(()) + } + transition.updatePosition(layer: toastView.layer, position: toastFrame.center) + transition.updateBounds(layer: toastView.layer, bounds: CGRect(origin: CGPoint(), size: toastFrame.size)) + } + } + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if let toastView = self.toast?.view { + toastView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + } + + override func dismissAnimated() { + super.dismissAnimated() + + if let toastView = self.toast?.view { + toastView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + } + } +} + +public func SuggestedPostApproveAlert(presentationData: PresentationData, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal, allowInputInset: Bool = true, parseMarkdown: Bool = false, dismissOnOutsideTap: Bool = true, linkAction: (([NSAttributedString.Key: Any], Int) -> Void)? = nil, toastText: String?) -> AlertController { + let theme = AlertControllerTheme(presentationData: presentationData) + + var dismissImpl: (() -> Void)? + let attributedText: NSAttributedString + if parseMarkdown { + let font = title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)) + let boldFont = title == nil ? Font.bold(theme.baseFontSize) : Font.semibold(floor(theme.baseFontSize * 13.0 / 17.0)) + let body = MarkdownAttributeSet(font: font, textColor: theme.primaryColor) + let bold = MarkdownAttributeSet(font: boldFont, textColor: theme.primaryColor) + let link = MarkdownAttributeSet(font: font, textColor: theme.accentColor) + attributedText = parseMarkdownIntoAttributedString(text, attributes: MarkdownAttributes(body: body, bold: bold, link: link, linkAttribute: { url in + return ("URL", url) + }), textAlignment: .center) + } else { + attributedText = NSAttributedString(string: text, font: title == nil ? Font.semibold(theme.baseFontSize) : Font.regular(floor(theme.baseFontSize * 13.0 / 17.0)), textColor: theme.primaryColor, paragraphAlignment: .center) + } + let controller = SuggestedPostAlertImpl(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title != nil ? NSAttributedString(string: title!, font: Font.semibold(theme.baseFontSize), textColor: theme.primaryColor, paragraphAlignment: .center) : nil, text: attributedText, actions: actions.map { action in + return TextAlertAction(type: action.type, title: action.title, action: { + dismissImpl?() + action.action() + }) + }, actionLayout: actionLayout, dismissOnOutsideTap: dismissOnOutsideTap, linkAction: linkAction), allowInputInset: allowInputInset, toastText: toastText) + dismissImpl = { [weak controller] in + controller?.dismissAnimated() + } + return controller +} diff --git a/submodules/TelegramUI/Resources/Animations/TonLogo.tgs b/submodules/TelegramUI/Resources/Animations/TonLogo.tgs new file mode 100644 index 0000000000000000000000000000000000000000..63d1896d9a7f14fde1bad6bf002fae9cd0f0b104 GIT binary patch literal 47932 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 wJXN4Zj*AxZ!K Void) @@ -2362,8 +2363,19 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(promptController, in: .window(.root)) case 1: var timestamp: Int32? + var funds: (amount: CurrencyAmount, commissionPermille: Int)? + if let amount = attribute.amount { + let configuration = StarsSubscriptionConfiguration.with(appConfiguration: strongSelf.context.currentAppConfiguration.with { $0 }) + funds = (amount, amount.currency == .stars ? Int(configuration.channelMessageSuggestionStarsCommissionPermille) : Int(configuration.channelMessageSuggestionTonCommissionPermille)) + } + + var isAdmin = false + if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = strongSelf.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isAdmin = true + } + if attribute.timestamp == nil { - let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in + let controller = ChatScheduleTimeController(context: strongSelf.context, updatedPresentationData: strongSelf.updatedPresentationData, mode: .suggestPost(needsTime: true, isAdmin: isAdmin, funds: funds), style: .default, currentTime: nil, minimalTime: nil, dismissByTapOutside: true, completion: { [weak strongSelf] time in guard let strongSelf else { return } @@ -2378,16 +2390,43 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G timestamp = Int32(Date().timeIntervalSince1970) + 1 * 60 * 60 } else { //TODO:localize - let textString = "Publish this message now?" - strongSelf.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: strongSelf.presentationData), title: nil, text: textString, actions: [ - TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}), + var textString: String + if isAdmin { + textString = "Do you really want to publish this post from **\((message.author.flatMap(EnginePeer.init))?.compactDisplayTitle ?? "")**?" + + if let funds { + var commissionValue: String + commissionValue = "\(Double(funds.commissionPermille) * 0.1)" + if commissionValue.hasSuffix(".0") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -2)]) + } else if commissionValue.hasSuffix(".00") { + commissionValue = String(commissionValue[commissionValue.startIndex ..< commissionValue.index(commissionValue.endIndex, offsetBy: -3)]) + } + + textString += "\n\n" + + switch funds.amount.currency { + case .stars: + let displayAmount = funds.amount.amount.totalValue * Double(funds.commissionPermille) / 1000.0 + textString += "You will receive \(displayAmount) Stars (\(commissionValue)%)\nfor publishing this post. It must remain visible for **24** hours after publication." + case .ton: + let displayAmount = Double(funds.amount.amount.value) / 1000000000.0 * Double(funds.commissionPermille) / 1000.0 + textString += "You will receive \(displayAmount) TON (\(commissionValue)%)\nfor publishing this post. It must remain visible for **24** hours after publication." + } + } + } else { + textString = "Do you really want to publish this post?" + } + + strongSelf.present(SuggestedPostApproveAlert(presentationData: strongSelf.presentationData, title: "Accept Terms", text: textString, actions: [ TextAlertAction(type: .defaultAction, title: "Publish", action: { [weak strongSelf] in guard let strongSelf else { return } let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() - }) - ]), in: .window(.root)) + }), + TextAlertAction(type: .genericAction, title: strongSelf.presentationData.strings.Common_Cancel, action: {}) + ], actionLayout: .vertical, parseMarkdown: true, toastText: funds?.amount.currency == .ton ? "Transactions in **Stars** may be reversed by the payment provider within **21** days. Only accept Stars from people you trust." : nil), in: .window(.root)) } case 2: strongSelf.interfaceInteraction?.openSuggestPost(message, .default)