diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 13770abdf4..78d2318e99 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1033,8 +1033,8 @@ public enum StarsWithdrawalScreenSubject { case withdraw(completion: (Int64) -> Void) case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void) - case postSuggestion(channel: EnginePeer, current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) - case postSuggestionModification(current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) + case postSuggestion(channel: EnginePeer, currency: TelegramCurrency, current: StarsAmount, timestamp: Int32?, completion: (TelegramCurrency, Int64, Int32?) -> Void) + case postSuggestionModification(currency: TelegramCurrency, current: StarsAmount, timestamp: Int32?, completion: (TelegramCurrency, Int64, Int32?) -> Void) } public protocol SharedAccountContext: AnyObject { diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index d129ec396b..095e7b84a9 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -1265,7 +1265,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { + }, openSuggestPost: { _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift index 5fa923ea3b..274ce3b596 100644 --- a/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift +++ b/submodules/BrowserUI/Sources/BrowserBookmarksScreen.swift @@ -70,7 +70,7 @@ public final class BrowserBookmarksScreen: ViewController { return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak controller] url in diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 3391e3e865..27765c53b0 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -497,11 +497,13 @@ public final class ChatInterfaceState: Codable, Equatable { public struct PostSuggestionState: Codable, Equatable { public var editingOriginalMessageId: MessageId? + public var currency: TelegramCurrency public var price: Int64 public var timestamp: Int32? - public init(editingOriginalMessageId: MessageId?, price: Int64, timestamp: Int32?) { + public init(editingOriginalMessageId: MessageId?, currency: TelegramCurrency, price: Int64, timestamp: Int32?) { self.editingOriginalMessageId = editingOriginalMessageId + self.currency = currency self.price = price self.timestamp = timestamp } diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index 24f2737965..84a3dfb9c1 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -168,7 +168,7 @@ public final class ChatPanelInterfaceInteraction { public let addDoNotTranslateLanguage: (String) -> Void public let hideTranslationPanel: () -> Void public let openPremiumGift: () -> Void - public let openSuggestPost: () -> Void + public let openSuggestPost: (Message?) -> Void public let openPremiumRequiredForMessaging: () -> Void public let openStarsPurchase: (Int64?) -> Void public let openMessagePayment: () -> Void @@ -291,7 +291,7 @@ public final class ChatPanelInterfaceInteraction { addDoNotTranslateLanguage: @escaping (String) -> Void, hideTranslationPanel: @escaping () -> Void, openPremiumGift: @escaping () -> Void, - openSuggestPost: @escaping () -> Void, + openSuggestPost: @escaping (Message?) -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, openStarsPurchase: @escaping (Int64?) -> Void, openMessagePayment: @escaping () -> Void, @@ -544,7 +544,7 @@ public final class ChatPanelInterfaceInteraction { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { + }, openSuggestPost: { _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift b/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift index 043415132b..75752bea75 100644 --- a/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift +++ b/submodules/ComponentFlow/Source/Base/ChildComponentTransitions.swift @@ -80,15 +80,20 @@ public extension ComponentTransition.DisappearWithGuide { public extension ComponentTransition.Update { static let `default` = ComponentTransition.Update { component, view, transition in - let frame = component.size.centered(around: component._position ?? CGPoint()) + let position = component._position ?? CGPoint() + let size = component.size + view.layer.anchorPoint = component._anchorPoint ?? CGPoint(x: 0.5, y: 0.5) if let scale = component._scale { - transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: frame.size)) - transition.setPosition(view: view, position: frame.center) + transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: size)) + transition.setPosition(view: view, position: position) transition.setScale(view: view, scale: scale) } else { - if view.frame != frame { - transition.setFrame(view: view, frame: frame) + if component._anchorPoint != nil { + view.bounds = CGRect(origin: CGPoint(), size: size) + } else { + transition.setBounds(view: view, bounds: CGRect(origin: CGPoint(), size: size)) } + transition.setPosition(view: view, position: position) } let opacity = component._opacity ?? 1.0 if view.alpha != opacity { diff --git a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift index 92505f2900..95ea2ebda7 100644 --- a/submodules/ComponentFlow/Source/Base/CombinedComponent.swift +++ b/submodules/ComponentFlow/Source/Base/CombinedComponent.swift @@ -175,6 +175,7 @@ public final class _UpdatedChildComponent { public let size: CGSize var _removed: Bool = false + var _anchorPoint: CGPoint? var _position: CGPoint? var _scale: CGFloat? var _opacity: CGFloat? @@ -237,6 +238,11 @@ public final class _UpdatedChildComponent { self._removed = removed return self } + + @discardableResult public func anchorPoint(_ anchorPoint: CGPoint) -> _UpdatedChildComponent { + self._anchorPoint = anchorPoint + return self + } @discardableResult public func position(_ position: CGPoint) -> _UpdatedChildComponent { self._position = position @@ -327,6 +333,10 @@ public extension _EnvironmentChildComponent { func update(_ component: ComponentType, @EnvironmentBuilder environment: () -> Environment, availableSize: CGSize, transition: ComponentTransition) -> _UpdatedChildComponent where ComponentType.EnvironmentType == EnvironmentType { return self.update(component: AnyComponent(component), environment: environment, availableSize: availableSize, transition: transition) } + + func update(_ component: AnyComponent, @EnvironmentBuilder environment: () -> Environment, availableSize: CGSize, transition: ComponentTransition) -> _UpdatedChildComponent { + return self.update(component: component, environment: environment, availableSize: availableSize, transition: transition) + } func update(_ component: ComponentType, @EnvironmentBuilder environment: () -> Environment, availableSize: CGSize, transition: ComponentTransition) -> _UpdatedChildComponent where ComponentType.EnvironmentType == EnvironmentType, EnvironmentType == Empty { return self.update(component: AnyComponent(component), environment: {}, availableSize: availableSize, transition: transition) @@ -707,12 +717,19 @@ public extension CombinedComponent { view.insertSubview(updatedChild.view, at: index) + updatedChild.view.layer.anchorPoint = updatedChild._anchorPoint ?? CGPoint(x: 0.5, y: 0.5) + if let scale = updatedChild._scale { updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size) updatedChild.view.center = updatedChild._position ?? CGPoint() updatedChild.view.transform = CGAffineTransform(scaleX: scale, y: scale) } else { - updatedChild.view.frame = updatedChild.size.centered(around: updatedChild._position ?? CGPoint()) + updatedChild.view.bounds = CGRect(origin: CGPoint(), size: updatedChild.size) + if updatedChild.view.layer.anchorPoint != CGPoint(x: 0.5, y: 0.5) { + updatedChild.view.layer.position = updatedChild._position ?? CGPoint() + } else { + updatedChild.view.center = updatedChild._position ?? CGPoint() + } } updatedChild.view.alpha = updatedChild._opacity ?? 1.0 diff --git a/submodules/TelegramApi/Sources/Api38.swift b/submodules/TelegramApi/Sources/Api38.swift index 8e510a3cae..782a7f097b 100644 --- a/submodules/TelegramApi/Sources/Api38.swift +++ b/submodules/TelegramApi/Sources/Api38.swift @@ -5682,9 +5682,9 @@ public extension Api.functions.messages { } } public extension Api.functions.messages { - static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + static func forwardMessages(flags: Int32, fromPeer: Api.InputPeer, id: [Int32], randomId: [Int64], toPeer: Api.InputPeer, topMsgId: Int32?, replyTo: Api.InputReplyTo?, scheduleDate: Int32?, sendAs: Api.InputPeer?, quickReplyShortcut: Api.InputQuickReplyShortcut?, videoTimestamp: Int32?, allowPaidStars: Int64?, suggestedPost: Api.SuggestedPost?) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() - buffer.appendInt32(955259020) + buffer.appendInt32(-1752618806) serializeInt32(flags, buffer: buffer, boxed: false) fromPeer.serialize(buffer, true) buffer.appendInt32(481674261) @@ -5705,7 +5705,8 @@ public extension Api.functions.messages { if Int(flags) & Int(1 << 17) != 0 {quickReplyShortcut!.serialize(buffer, true)} if Int(flags) & Int(1 << 20) != 0 {serializeInt32(videoTimestamp!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 21) != 0 {serializeInt64(allowPaidStars!, buffer: buffer, boxed: false)} - return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + if Int(flags) & Int(1 << 23) != 0 {suggestedPost!.serialize(buffer, true)} + return (FunctionDescription(name: "messages.forwardMessages", parameters: [("flags", String(describing: flags)), ("fromPeer", String(describing: fromPeer)), ("id", String(describing: id)), ("randomId", String(describing: randomId)), ("toPeer", String(describing: toPeer)), ("topMsgId", String(describing: topMsgId)), ("replyTo", String(describing: replyTo)), ("scheduleDate", String(describing: scheduleDate)), ("sendAs", String(describing: sendAs)), ("quickReplyShortcut", String(describing: quickReplyShortcut)), ("videoTimestamp", String(describing: videoTimestamp)), ("allowPaidStars", String(describing: allowPaidStars)), ("suggestedPost", String(describing: suggestedPost))]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in let reader = BufferReader(buffer) var result: Api.Updates? if let signature = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 310cbc2656..2cac48b3b5 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -486,7 +486,7 @@ private func sendUploadedMessageContent( } if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: nil, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: nil), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 334c70dbf0..770abaa3e0 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -860,6 +860,7 @@ public final class PendingMessageManager { var quickReply: OutgoingQuickReplyMessageAttribute? var messageEffect: EffectMessageAttribute? var allowPaidStars: Int64? + var suggestedPost: Api.SuggestedPost? var flags: Int32 = 0 @@ -898,6 +899,8 @@ public final class PendingMessageManager { videoTimestamp = attribute.timestamp } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value * Int64(messages.count) + } else if let attribute = attribute as? SuggestedPostMessageAttribute { + suggestedPost = attribute.apiSuggestedPost() } } @@ -978,6 +981,10 @@ public final class PendingMessageManager { flags |= 1 << 22 } + if suggestedPost != nil { + flags |= 1 << 23 + } + let forwardPeerIds = Set(forwardIds.map { $0.0.peerId }) if forwardPeerIds.count != 1 { assertionFailure() @@ -985,7 +992,7 @@ public final class PendingMessageManager { } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) } else { assertionFailure() sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) @@ -1663,8 +1670,12 @@ public final class PendingMessageManager { flags |= 1 << 22 } + if suggestedPost != nil { + flags |= 1 << 23 + } + if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { - sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars), tag: dependencyTag) + sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer, topMsgId: topMsgId, replyTo: replyTo, scheduleDate: scheduleTime, sendAs: sendAsInputPeer, quickReplyShortcut: quickReplyShortcut, videoTimestamp: videoTimestamp, allowPaidStars: allowPaidStars, suggestedPost: suggestedPost), tag: dependencyTag) |> map(NetworkRequestResult.result) } else { sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) diff --git a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift index 4c19f54711..44796ee458 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift @@ -8,23 +8,27 @@ public final class SuggestedPostMessageAttribute: Equatable, MessageAttribute { case rejected = 1 } + public let currency: TelegramCurrency public let amount: Int64 public let timestamp: Int32? public let state: State? - public init(amount: Int64, timestamp: Int32?, state: State?) { + public init(currency: TelegramCurrency, amount: Int64, timestamp: Int32?, state: State?) { + self.currency = currency self.amount = amount self.timestamp = timestamp self.state = state } required public init(decoder: PostboxDecoder) { + self.currency = decoder.decodeCodable(TelegramCurrency.self, forKey: "cur") ?? .stars self.amount = decoder.decodeInt64ForKey("am", orElse: 0) self.timestamp = decoder.decodeOptionalInt32ForKey("ts") self.state = decoder.decodeOptionalInt32ForKey("st").flatMap(State.init(rawValue:)) } public func encode(_ encoder: PostboxEncoder) { + encoder.encodeCodable(self.currency, forKey: "cur") encoder.encodeInt64(self.amount, forKey: "am") if let timestamp = self.timestamp { encoder.encodeInt32(timestamp, forKey: "ts") @@ -39,6 +43,9 @@ public final class SuggestedPostMessageAttribute: Equatable, MessageAttribute { } public static func ==(lhs: SuggestedPostMessageAttribute, rhs: SuggestedPostMessageAttribute) -> Bool { + if lhs.currency != rhs.currency { + return false + } if lhs.amount != rhs.amount { return false } @@ -62,7 +69,7 @@ extension SuggestedPostMessageAttribute { } else if (flags & (1 << 2)) != 0 { state = .rejected } - self.init(amount: starsAmount, timestamp: scheduleDate, state: state) + self.init(currency: .stars, amount: starsAmount, timestamp: scheduleDate, state: state) } } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift index 2a6677042f..594593e546 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Messages/ForwardGame.swift @@ -14,7 +14,7 @@ func _internal_forwardGameWithScore(account: Account, messageId: MessageId, to p flags |= (1 << 13) } - return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: nil, allowPaidStars: nil)) + return account.network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: fromInputPeer, id: [messageId.id], randomId: [Int64.random(in: Int64.min ... Int64.max)], toPeer: toInputPeer, topMsgId: threadId.flatMap { Int32(clamping: $0) }, replyTo: nil, scheduleDate: nil, sendAs: sendAsInputPeer, quickReplyShortcut: nil, videoTimestamp: nil, allowPaidStars: nil, suggestedPost: nil)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 464f5f757d..b5900da6f7 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -352,6 +352,38 @@ extension StarsAmount { } } +public enum TelegramCurrency: Codable { + private enum CodingKeys: String, CodingKey { + case discriminator = "_" + } + + case stars + case ton + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + switch try container.decode(Int32.self, forKey: .discriminator) { + case 0: + self = .stars + case 1: + self = .ton + default: + assertionFailure() + self = .stars + } + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + switch self { + case .stars: + try container.encode(0 as Int32, forKey: .discriminator) + case .ton: + try container.encode(1 as Int32, forKey: .discriminator) + } + } +} + struct InternalStarsStatus { let balance: StarsAmount let subscriptionsMissingBalance: StarsAmount? diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 06bb6df055..18d368bc2a 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1244,8 +1244,21 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } case let .paidMessagesRefunded(_, stars): let starsString = strings.Notification_PaidMessageRefund_Stars(Int32(stars)) - if message.author?.id == accountPeerId, let messagePeer = message.peers[message.id.peerId] { - let peerName = EnginePeer(messagePeer).compactDisplayTitle + + var isOutgoing = false + var messagePeer: EnginePeer? + if message.author?.id == accountPeerId, let messagePeerValue = message.peers[message.id.peerId] { + isOutgoing = true + messagePeer = EnginePeer(messagePeerValue) + } else if message.id.peerId.namespace == Namespaces.Peer.CloudChannel, let peer = message.peers[message.id.peerId] as? TelegramChannel, peer.isMonoForum { + if let author = message.author, let threadId = message.threadId, let threadPeer = message.peers[PeerId(threadId)], author.id != threadPeer.id { + isOutgoing = true + messagePeer = EnginePeer(threadPeer) + } + } + + if isOutgoing, let messagePeer { + let peerName = messagePeer.compactDisplayTitle var attributes = peerMentionsAttributes(primaryTextColor: primaryTextColor, peerIds: [(1, messagePeer.id)]) attributes[0] = boldAttributes let resultString = strings.Notification_PaidMessageRefundYou(starsString, peerName) diff --git a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift index f741f5fb6b..dc4e563224 100644 --- a/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatButtonKeyboardInputNode/Sources/ChatButtonKeyboardInputNode.swift @@ -385,11 +385,11 @@ public final class ChatButtonKeyboardInputNode: ChatInputNode { self.controllerInteraction.shareAccountContact() case .openWebApp: if let message = self.message { - self.controllerInteraction.requestMessageActionCallback(message, nil, true, false) + self.controllerInteraction.requestMessageActionCallback(message, nil, true, false, nil) } case let .callback(requiresPassword, data): if let message = self.message { - self.controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword) + self.controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword, nil) } case let .switchInline(samePeer, query, _): if let message = message { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index f6c9f86578..4fc59e666c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -261,17 +261,25 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let amountString = amount == 1 ? "\(amount) Star" : "\(amount) Stars" let rawString: String - if timestamp != nil { - if !item.message.effectivelyIncoming(item.context.account.peerId) { - rawString = "šŸ“… The post will be automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + if let timestamp { + if Int32(Date().timeIntervalSince1970) >= timestamp { + if !item.message.effectivelyIncoming(item.context.account.peerId) { + rawString = "šŸ“… The post has been automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + } else { + rawString = "šŸ“… Your post has been automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + } } else { - rawString = "šŸ“… Your post will be automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + if !item.message.effectivelyIncoming(item.context.account.peerId) { + rawString = "šŸ“… The post will be automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + } else { + rawString = "šŸ“… Your post will be automatically published in **\(channelName)** **\(timeString)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + } } } else { if !item.message.effectivelyIncoming(item.context.account.peerId) { - rawString = "šŸ“… The post will be automatically published in **\(channelName)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." + rawString = "šŸ“… The post has been automatically published in **\(channelName)**.\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive the Stars once the post has been live for 24 hours.\n\nšŸ”„ If your remove the post before it has been live for 24 hours, the user's Stars will be refunded." } else { - rawString = "šŸ“… Your post will be automatically published in **\(channelName)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." + rawString = "šŸ“… Your post has been automatically published in **\(channelName)**.\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your Stars once the post has been live for 24 hours.\n\nšŸ”„ If **\(channelName)** removes the post before it has been live for 24 hours, your Stars will be refunded." } } updatedAttributedString = parseMarkdownIntoAttributedString(rawString, attributes: MarkdownAttributes( diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD index 0b8e57031f..23e6cb338d 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/BUILD @@ -10,6 +10,7 @@ swift_library( "-warnings-as-errors", ], deps = [ + "//submodules/SSignalKit/SwiftSignalKit", "//submodules/AsyncDisplayKit", "//submodules/TelegramCore", "//submodules/Postbox", @@ -18,6 +19,7 @@ swift_library( "//submodules/AccountContext", "//submodules/WallpaperBackgroundNode", "//submodules/UrlHandling", + "//submodules/TelegramUI/Components/TextLoadingEffect", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index 82af123a30..ade800e580 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -8,6 +8,8 @@ import TelegramPresentationData import AccountContext import WallpaperBackgroundNode import UrlHandling +import SwiftSignalKit +import TextLoadingEffect private let titleFont = Font.medium(16.0) @@ -72,16 +74,21 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { private var backgroundContent: WallpaperBubbleBackgroundNode? private var backgroundColorNode: ASDisplayNode? + private var maskPath: CGPath? + private var loadingEffectView: TextLoadingEffectView? + private var absolutePosition: (CGRect, CGSize)? private var button: ReplyMarkupButton? - var pressed: ((ReplyMarkupButton) -> Void)? + var pressed: ((ReplyMarkupButton, Promise) -> Void)? var longTapped: ((ReplyMarkupButton) -> Void)? var longTapRecognizer: UILongPressGestureRecognizer? private let accessibilityArea: AccessibilityAreaNode + private var progressDisposable: Disposable? + override init() { self.accessibilityArea = AccessibilityAreaNode() self.accessibilityArea.accessibilityTraits = .button @@ -96,6 +103,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } + deinit { + self.progressDisposable?.dispose() + } + override func didLoad() { super.didLoad() @@ -140,7 +151,48 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { @objc func buttonPressed() { if let button = self.button, let pressed = self.pressed { - pressed(button) + let progressPromise = Promise() + pressed(button, progressPromise) + + self.progressDisposable?.dispose() + self.progressDisposable = (progressPromise.get() + |> deliverOnMainQueue).startStrict(next: { [weak self] isLoading in + guard let self else { + return + } + self.updateIsLoading(isLoading: isLoading) + }) + } + } + + private func updateIsLoading(isLoading: Bool) { + if isLoading { + if self.loadingEffectView == nil { + let loadingEffectView = TextLoadingEffectView(frame: CGRect()) + self.loadingEffectView = loadingEffectView + + if let iconNode = self.iconNode, iconNode.view.superview != nil { + self.view.insertSubview(loadingEffectView, belowSubview: iconNode.view) + } else if let titleNode = self.titleNode, titleNode.view.superview != nil { + self.view.insertSubview(loadingEffectView, belowSubview: titleNode.view) + } else { + self.view.addSubview(loadingEffectView) + } + + if let buttonView = self.buttonView, let maskPath = self.maskPath { + let loadingFrame = buttonView.frame + + loadingEffectView.frame = loadingFrame + loadingEffectView.update(color: UIColor(white: 1.0, alpha: 1.0), rect: CGRect(origin: CGPoint(), size: loadingFrame.size), path: maskPath) + } + } + } else { + if let loadingEffectView { + self.loadingEffectView = nil + loadingEffectView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak loadingEffectView] _ in + loadingEffectView?.removeFromSuperview() + }) + } } } @@ -351,6 +403,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { let rect = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)) let maskPath: CGPath? + var needsMask = true switch position { case .bottomSingle: maskPath = UIBezierPath(roundRect: rect, topLeftRadius: bubbleCorners.auxiliaryRadius, topRightRadius: bubbleCorners.auxiliaryRadius, bottomLeftRadius: bubbleCorners.mainRadius, bottomRightRadius: bubbleCorners.mainRadius).cgPath @@ -359,14 +412,19 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { case .bottomRight: maskPath = UIBezierPath(roundRect: rect, topLeftRadius: bubbleCorners.auxiliaryRadius, topRightRadius: bubbleCorners.auxiliaryRadius, bottomLeftRadius: bubbleCorners.auxiliaryRadius, bottomRightRadius: bubbleCorners.mainRadius).cgPath default: - maskPath = nil + needsMask = false + maskPath = UIBezierPath(roundRect: rect, topLeftRadius: bubbleCorners.auxiliaryRadius, topRightRadius: bubbleCorners.auxiliaryRadius, bottomLeftRadius: bubbleCorners.auxiliaryRadius, bottomRightRadius: bubbleCorners.auxiliaryRadius).cgPath } let currentMaskPath = (node.layer.mask as? CAShapeLayer)?.path - if currentMaskPath != maskPath { - if let maskPath = maskPath { + node.maskPath = maskPath + + let effectiveMaskPath = needsMask ? maskPath : nil + + if currentMaskPath != effectiveMaskPath { + if let effectiveMaskPath = effectiveMaskPath { let shapeLayer = CAShapeLayer() - shapeLayer.path = maskPath + shapeLayer.path = effectiveMaskPath node.layer.mask = shapeLayer } else { node.layer.mask = nil @@ -437,9 +495,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { private var buttonNodes: [ChatMessageActionButtonNode] = [] - private var buttonPressedWrapper: ((ReplyMarkupButton) -> Void)? + private var buttonPressedWrapper: ((ReplyMarkupButton, Promise) -> Void)? private var buttonLongTappedWrapper: ((ReplyMarkupButton) -> Void)? - public var buttonPressed: ((ReplyMarkupButton) -> Void)? + public var buttonPressed: ((ReplyMarkupButton, Promise) -> Void)? public var buttonLongTapped: ((ReplyMarkupButton) -> Void)? private var absolutePosition: (CGRect, CGSize)? @@ -447,9 +505,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { override public init() { super.init() - self.buttonPressedWrapper = { [weak self] button in + self.buttonPressedWrapper = { [weak self] button, promise in if let buttonPressed = self?.buttonPressed { - buttonPressed(button) + buttonPressed(button, promise) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index f73ab88e85..67834c78ce 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1718,9 +1718,9 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { actionButtonsNode.frame = actionButtonsFrame if actionButtonsNode !== strongSelf.actionButtonsNode { strongSelf.actionButtonsNode = actionButtonsNode - actionButtonsNode.buttonPressed = { button in + actionButtonsNode.buttonPressed = { button, progress in if let strongSelf = weakSelf.value { - strongSelf.performMessageButtonAction(button: button) + strongSelf.performMessageButtonAction(button: button, progress: progress) } } actionButtonsNode.buttonLongTapped = { button in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index 06f98b2c84..8598d20b82 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -4700,9 +4700,9 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI let actionButtonsFrame = CGRect(origin: CGPoint(x: backgroundFrame.minX + (incoming ? layoutConstants.bubble.contentInsets.left : layoutConstants.bubble.contentInsets.right), y: backgroundFrame.maxY), size: actionButtonsSizeAndApply.0) if actionButtonsNode !== strongSelf.actionButtonsNode { strongSelf.actionButtonsNode = actionButtonsNode - actionButtonsNode.buttonPressed = { [weak strongSelf] button in + actionButtonsNode.buttonPressed = { [weak strongSelf] button, progress in if let strongSelf = strongSelf { - strongSelf.performMessageButtonAction(button: button) + strongSelf.performMessageButtonAction(button: button, progress: progress) } } actionButtonsNode.buttonLongTapped = { [weak strongSelf] button in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift index 98475296cd..647345b103 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageGameBubbleContentNode/Sources/ChatMessageGameBubbleContentNode.swift @@ -28,14 +28,14 @@ public final class ChatMessageGameBubbleContentNode: ChatMessageBubbleContentNod self.addSubnode(self.contentNode) self.contentNode.openMedia = { [weak self] _ in if let strongSelf = self, let item = strongSelf.item { - item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false, nil) } } } override public func accessibilityActivate() -> Bool { if let item = self.item { - item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false, nil) } return true } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift index bc4dca41fe..ac2d939b28 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageInstantVideoItemNode/Sources/ChatMessageInstantVideoItemNode.swift @@ -890,9 +890,9 @@ public class ChatMessageInstantVideoItemNode: ChatMessageItemView, ASGestureReco actionButtonsNode.frame = actionButtonsFrame if actionButtonsNode !== strongSelf.actionButtonsNode { strongSelf.actionButtonsNode = actionButtonsNode - actionButtonsNode.buttonPressed = { button in + actionButtonsNode.buttonPressed = { button, progress in if let strongSelf = weakSelf.value { - strongSelf.performMessageButtonAction(button: button) + strongSelf.performMessageButtonAction(button: button, progress: progress) } } actionButtonsNode.buttonLongTapped = { button in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift index 839e46afd1..2a3b973f25 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemView/Sources/ChatMessageItemView.swift @@ -805,7 +805,7 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { } } - open func performMessageButtonAction(button: ReplyMarkupButton) { + public func performMessageButtonAction(button: ReplyMarkupButton, progress: Promise?) { if let item = self.item { switch button.action { case .text: @@ -815,15 +815,15 @@ open class ChatMessageItemView: ListViewItemNode, ChatMessageItemNodeProtocol { if url.hasPrefix("tg://") { concealed = false } - item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, progress: Promise())) + item.controllerInteraction.openUrl(ChatControllerInteraction.OpenUrl(url: url, concealed: concealed, progress: progress)) case .requestMap: item.controllerInteraction.shareCurrentLocation() case .requestPhone: item.controllerInteraction.shareAccountContact() case .openWebApp: - item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false) + item.controllerInteraction.requestMessageActionCallback(item.message, nil, true, false, progress) case let .callback(requiresPassword, data): - item.controllerInteraction.requestMessageActionCallback(item.message, data, false, requiresPassword) + item.controllerInteraction.requestMessageActionCallback(item.message, data, false, requiresPassword, progress) case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index d26834db75..1388eb443a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -1276,9 +1276,9 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { actionButtonsNode.frame = actionButtonsFrame if actionButtonsNode !== strongSelf.actionButtonsNode { strongSelf.actionButtonsNode = actionButtonsNode - actionButtonsNode.buttonPressed = { button in + actionButtonsNode.buttonPressed = { button, progress in if let strongSelf = weakSelf.value { - strongSelf.performMessageButtonAction(button: button) + strongSelf.performMessageButtonAction(button: button, progress: progress) } } actionButtonsNode.buttonLongTapped = { button in diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index a7beb77d71..fda8a2c741 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -48,11 +48,13 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { let labelSpacing: CGFloat = 8.0 let valuesVerticalSpacing: CGFloat = 2.0 + var currency: TelegramCurrency = .stars var amount: Int64 = 0 var timestamp: Int32? for attribute in item.message.attributes { if let attribute = attribute as? SuggestedPostMessageAttribute { + currency = attribute.currency amount = attribute.amount timestamp = attribute.timestamp } @@ -60,12 +62,23 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { //TODO:localize let amountString: String - if amount == 0 { - amountString = "Free" - } else if amount == 1 { - amountString = "1 Star" - } else { - amountString = "\(amount) Stars" + switch currency { + case .stars: + if amount == 0 { + amountString = "Free" + } else if amount == 1 { + amountString = "1 Star" + } else { + amountString = "\(amount) Stars" + } + case .ton: + if amount == 0 { + amountString = "Free" + } else if amount == 1 { + amountString = "1 TON" + } else { + amountString = "\(amount) TON" + } } var timestampString: String diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 6cbdec6b6b..c3c3e8bb96 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -165,7 +165,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { + }, openSuggestPost: { _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift index d094b27748..18f45863d7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsControllerNode.swift @@ -316,7 +316,7 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode { let _ = context.sharedContext.navigateToForumThread(context: context, peerId: peerId, threadId: threadId, messageId: nil, navigationController: navigationController, activateInput: nil, scrollToEndIfExists: false, keepStack: .always, animated: true).startStandalone() } }, tapMessage: nil, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { [weak self] message, _, _, _ in + }, requestMessageActionCallback: { [weak self] message, _, _, _, _ in guard let self else { return } diff --git a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift index 697c141356..65e8b3c90b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendAudioMessageContextPreview/Sources/ChatSendAudioMessageContextPreview.swift @@ -421,7 +421,7 @@ public final class ChatSendGroupMediaMessageContextPreview: UIView, ChatSendMess }, clickThroughMessage: { _, _ in }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: { diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift index 906cea9034..5623c9d932 100644 --- a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift @@ -41,6 +41,7 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)? private var inlineTextStarImage: UIImage? + private var inlineTextTonImage: (UIImage, UIColor)? public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { self.context = context @@ -203,6 +204,26 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { } } + var inlineTextTonImage: UIImage? + if let current = self.inlineTextTonImage, current.1 == self.theme.list.itemAccentColor { + inlineTextTonImage = current.0 + } else { + if let image = UIImage(bundleImageName: "Ads/TonMedium") { + let tonInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) + let inlineTextTonImageValue = generateTintedImage(image: generateImage(CGSize(width: tonInsets.left + image.size.width + tonInsets.right, height: image.size.height), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + defer { + UIGraphicsPopContext() + } + + image.draw(at: CGPoint(x: tonInsets.left, y: tonInsets.top)) + }), color: self.theme.list.itemAccentColor)!.withRenderingMode(.alwaysOriginal) + inlineTextTonImage = inlineTextTonImageValue + self.inlineTextTonImage = (inlineTextTonImageValue, self.theme.list.itemAccentColor) + } + } + //TODO:localize var titleText: [CompositeTextNode.Component] = [] if let postSuggestionState = interfaceState.interfaceState.postSuggestionState, postSuggestionState.editingOriginalMessageId != nil { @@ -219,6 +240,13 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { let textString: NSAttributedString if let postSuggestionState = interfaceState.interfaceState.postSuggestionState, postSuggestionState.price != 0 { + let currencySymbol: String + switch postSuggestionState.currency { + case .stars: + currencySymbol = "#" + case .ton: + currencySymbol = "$" + } if let timestamp = postSuggestionState.timestamp { let timeString = humanReadableStringForTimestamp(strings: interfaceState.strings, dateTimeFormat: interfaceState.dateTimeFormat, timestamp: timestamp, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat( dateFormatString: { value in @@ -234,57 +262,70 @@ public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { return PresentationStrings.FormattedString(string: interfaceState.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) } )).string - textString = NSAttributedString(string: "#\(postSuggestionState.price) šŸ“… \(timeString)", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) + textString = NSAttributedString(string: "\(currencySymbol)\(postSuggestionState.price) šŸ“… \(timeString)", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) } else { - textString = NSAttributedString(string: "#\(postSuggestionState.price) for publishing anytime", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) + textString = NSAttributedString(string: "\(currencySymbol)\(postSuggestionState.price) for publishing anytime", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) } } else { textString = NSAttributedString(string: "Tap to offer a price for publishing", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) } let mutableTextString = NSMutableAttributedString(attributedString: textString) - if let range = mutableTextString.string.range(of: "#"), let starImage = inlineTextStarImage { - final class RunDelegateData { - let ascent: CGFloat - let descent: CGFloat - let width: CGFloat - - init(ascent: CGFloat, descent: CGFloat, width: CGFloat) { - self.ascent = ascent - self.descent = descent - self.width = width - } + for currency in [.stars, .ton] as [TelegramCurrency] { + let currencySymbol: String + let currencyImage: UIImage? + switch currency { + case .stars: + currencySymbol = "#" + currencyImage = inlineTextStarImage + case .ton: + currencySymbol = "$" + currencyImage = inlineTextTonImage } - let runDelegateData = RunDelegateData( - ascent: Font.regular(15.0).ascender, - descent: Font.regular(15.0).descender, - width: starImage.size.width + 2.0 - ) - var callbacks = CTRunDelegateCallbacks( - version: kCTRunDelegateCurrentVersion, - dealloc: { dataRef in - Unmanaged.fromOpaque(dataRef).release() - }, - getAscent: { dataRef in - let data = Unmanaged.fromOpaque(dataRef) - return data.takeUnretainedValue().ascent - }, - getDescent: { dataRef in - let data = Unmanaged.fromOpaque(dataRef) - return data.takeUnretainedValue().descent - }, - getWidth: { dataRef in - let data = Unmanaged.fromOpaque(dataRef) - return data.takeUnretainedValue().width + if let range = mutableTextString.string.range(of: currencySymbol), let currencyImage { + final class RunDelegateData { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + + init(ascent: CGFloat, descent: CGFloat, width: CGFloat) { + self.ascent = ascent + self.descent = descent + self.width = width + } } - ) - if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { - mutableTextString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: mutableTextString.string)) + + let runDelegateData = RunDelegateData( + ascent: Font.regular(15.0).ascender, + descent: Font.regular(15.0).descender, + width: currencyImage.size.width + 2.0 + ) + var callbacks = CTRunDelegateCallbacks( + version: kCTRunDelegateCurrentVersion, + dealloc: { dataRef in + Unmanaged.fromOpaque(dataRef).release() + }, + getAscent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().ascent + }, + getDescent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().descent + }, + getWidth: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().width + } + ) + if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { + mutableTextString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: mutableTextString.string)) + } + mutableTextString.addAttribute(.attachment, value: currencyImage, range: NSRange(range, in: mutableTextString.string)) + mutableTextString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: mutableTextString.string)) + mutableTextString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: mutableTextString.string)) } - mutableTextString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: mutableTextString.string)) - mutableTextString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: mutableTextString.string)) - mutableTextString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: mutableTextString.string)) } self.textNode.attributedText = mutableTextString diff --git a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift index 24092293e9..cdd992d223 100644 --- a/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift +++ b/submodules/TelegramUI/Components/ChatControllerInteraction/Sources/ChatControllerInteraction.swift @@ -193,7 +193,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol public let sendEmoji: (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void public let sendGif: (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool public let sendBotContextResultAsGif: (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool - public let requestMessageActionCallback: (Message, MemoryBuffer?, Bool, Bool) -> Void + public let requestMessageActionCallback: (Message, MemoryBuffer?, Bool, Bool, Promise?) -> Void public let requestMessageActionUrlAuth: (String, MessageActionUrlSubject) -> Void public let activateSwitchInline: (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void public let openUrl: (OpenUrl) -> Void @@ -360,7 +360,7 @@ public final class ChatControllerInteraction: ChatControllerInteractionProtocol sendEmoji: @escaping (String, ChatTextInputTextCustomEmojiAttribute, Bool) -> Void, sendGif: @escaping (FileMediaReference, UIView, CGRect, Bool, Bool) -> Bool, sendBotContextResultAsGif: @escaping (ChatContextResultCollection, ChatContextResult, UIView, CGRect, Bool, Bool) -> Bool, - requestMessageActionCallback: @escaping (Message, MemoryBuffer?, Bool, Bool) -> Void, + requestMessageActionCallback: @escaping (Message, MemoryBuffer?, Bool, Bool, Promise?) -> Void, requestMessageActionUrlAuth: @escaping (String, MessageActionUrlSubject) -> Void, activateSwitchInline: @escaping (PeerId?, String, ReplyMarkupButtonAction.PeerTypes?) -> Void, openUrl: @escaping (OpenUrl) -> Void, diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 5cbe853e6b..26a7cd4b7d 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -430,7 +430,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { + }, openSuggestPost: { _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { @@ -3692,7 +3692,7 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { [weak self] url in diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index 6b03482035..190bb9afc2 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -818,7 +818,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { - }, openSuggestPost: { + }, openSuggestPost: { _ in }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD index a4e640935e..b29361fb08 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD @@ -38,6 +38,7 @@ swift_library( "//submodules/PasswordSetupUI", "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", "//submodules/TelegramUI/Components/ChatScheduleTimeController", + "//submodules/TelegramUI/Components/TabSelectorComponent", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 3fcde05e7c..e989cf8087 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -23,6 +23,7 @@ import TelegramStringFormatting import UndoUI import ListActionItemComponent import ChatScheduleTimeController +import TabSelectorComponent private let amountTag = GenericComponentViewTag() @@ -54,6 +55,7 @@ private final class SheetContent: CombinedComponent { let closeButton = Child(Button.self) let balance = Child(BalanceComponent.self) let title = Child(Text.self) + let currencyToggle = Child(TabSelectorComponent.self) let amountSection = Child(ListSectionComponent.self) let amountAdditionalLabel = Child(MultilineTextComponent.self) let timestampSection = Child(ListSectionComponent.self) @@ -80,7 +82,7 @@ private final class SheetContent: CombinedComponent { let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 - if case let .suggestedPost(mode, _, _, _) = component.mode { + if case let .suggestedPost(mode, _, _, _, _) = component.mode { switch mode { case .sender: let balance = balance.update( @@ -88,6 +90,7 @@ private final class SheetContent: CombinedComponent { context: component.context, theme: environment.theme, strings: environment.strings, + currency: state.currency, balance: state.balance, alignment: .right ), @@ -96,7 +99,8 @@ private final class SheetContent: CombinedComponent { ) let balanceFrame = CGRect(origin: CGPoint(x: context.availableSize.width - balance.size.width - 15.0, y: floor((56.0 - balance.size.height) * 0.5)), size: balance.size) context.add(balance - .position(balanceFrame.center) + .anchorPoint(CGPoint(x: 1.0, y: 0.0)) + .position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY)) ) case .admin: break @@ -199,7 +203,7 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: minAmountValue, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) - case let .suggestedPost(mode, _, _, _): + case let .suggestedPost(mode, _, _, _, _): //TODO:localize switch mode { case .sender: @@ -207,7 +211,12 @@ private final class SheetContent: CombinedComponent { case .admin: titleString = "Suggest Changes" } - amountTitle = "ENTER A PRICE IN STARS" + switch state.currency { + case .stars: + amountTitle = "ENTER A PRICE IN STARS" + case .ton: + amountTitle = "ENTER A PRICE IN TON" + } amountPlaceholder = "Price" minAmount = StarsAmount(value: 0, nanos: 0) @@ -280,6 +289,65 @@ private final class SheetContent: CombinedComponent { ) } + if case let .suggestedPost(mode, _, _, _, _) = component.mode { + //TODO:localize + let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) + let starsTitle: String + let tonTitle: String + switch mode { + case .sender: + starsTitle = "Offer Stars" + tonTitle = "Offer TON" + case .admin: + starsTitle = "Request Stars" + tonTitle = "Request TON" + } + + let currencyToggle = currencyToggle.update( + component: TabSelectorComponent( + colors: TabSelectorComponent.Colors( + foreground: theme.list.itemSecondaryTextColor, + selection: theme.list.itemSecondaryTextColor.withMultipliedAlpha(0.15), + simple: true + ), + customLayout: TabSelectorComponent.CustomLayout( + font: Font.medium(14.0), + spacing: 10.0 + ), + items: [ + TabSelectorComponent.Item( + id: AnyHashable(0), + content: .component(AnyComponent(CurrencyTabItemComponent(icon: .stars, title: starsTitle, theme: theme))) + ), + TabSelectorComponent.Item( + id: AnyHashable(1), + content: .component(AnyComponent(CurrencyTabItemComponent(icon: .ton, title: tonTitle, theme: theme))) + ) + ], + selectedId: selectedId, + setSelectedId: { [weak state] id in + guard let state else { + return + } + if id == AnyHashable(0) { + state.currency = .stars + } else { + state.currency = .ton + } + state.updated(transition: .spring(duration: 0.4)) + } + ), + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: 100.0), + transition: context.transition + ) + contentSize.height -= 17.0 + let currencyToggleFrame = CGRect(origin: CGPoint(x: floor((context.availableSize.width - currencyToggle.size.width) * 0.5), y: contentSize.height), size: currencyToggle.size) + context.add(currencyToggle + .position(currencyToggle.size.centered(in: currencyToggleFrame).center)) + + contentSize.height += currencyToggle.size.height + 29.0 + } + let amountFont = Font.regular(13.0) let boldAmountFont = Font.semibold(13.0) let amountTextColor = theme.list.freeTextColor @@ -356,18 +424,32 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) - case let .suggestedPost(mode, _, _, _): + case let .suggestedPost(mode, _, _, _, _): switch mode { case let .sender(channel): //TODO:localize - let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let string: String + switch state.currency { + case .stars: + string = "Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message." + case .ton: + string = "Choose how many TON you want to offer \(channel.compactDisplayTitle) to publish this message." + } + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural)) amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), maximumNumberOfLines: 0 )) case .admin: //TODO:localize - let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you charge for the message.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let string: String + switch state.currency { + case .stars: + string = "Choose how many Stars you charge for the message." + case .ton: + string = "Choose how many TON you charge for the message." + } + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString(string, attributes: amountMarkdownAttributes, textAlignment: .natural)) amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), maximumNumberOfLines: 0 @@ -396,11 +478,13 @@ private final class SheetContent: CombinedComponent { textColor: theme.list.itemPrimaryTextColor, secondaryColor: theme.list.itemSecondaryTextColor, placeholderColor: theme.list.itemPlaceholderTextColor, + accentColor: theme.list.itemAccentColor, value: state.amount?.value, minValue: minAmount?.value, maxValue: maxAmount?.value, placeholderText: amountPlaceholder, labelText: amountLabel, + currency: state.currency, amountUpdated: { [weak state] amount in state?.amount = amount.flatMap { StarsAmount(value: $0, nanos: 0) } state?.updated() @@ -413,7 +497,7 @@ private final class SheetContent: CombinedComponent { ), environment: {}, availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), - transition: context.transition + transition: .immediate ) context.add(amountSection .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + amountSection.size.height / 2.0)) @@ -431,7 +515,7 @@ private final class SheetContent: CombinedComponent { .position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0))) } - if case let .suggestedPost(mode, _, _, _) = component.mode { + if case let .suggestedPost(mode, _, _, _, _) = component.mode { contentSize.height += 24.0 //TODO:localize @@ -542,12 +626,19 @@ private final class SheetContent: CombinedComponent { } } else if case .paidMessages = component.mode { buttonString = environment.strings.Stars_SendMessage_AdjustmentAction - } else if case let .suggestedPost(mode, _, _, _) = component.mode { + } else if case let .suggestedPost(mode, _, _, _, _) = component.mode { //TODO:localize switch mode { case .sender: if let amount = state.amount { - buttonString = "Offer # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" + let currencySymbol: String + switch state.currency { + case .stars: + currencySymbol = "#" + case .ton: + currencySymbol = "$" + } + buttonString = "Offer \(currencySymbol) \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" } else { buttonString = "Offer for Free" } @@ -563,6 +654,9 @@ private final class SheetContent: CombinedComponent { if state.cachedStarImage == nil || state.cachedStarImage?.1 !== theme { state.cachedStarImage = (generateTintedImage(image: UIImage(bundleImageName: "Item List/PremiumIcon"), color: theme.list.itemCheckColors.foregroundColor)!, theme) } + if state.cachedTonImage == nil || state.cachedTonImage?.1 !== theme { + state.cachedTonImage = (generateTintedImage(image: UIImage(bundleImageName: "Ads/TonAbout"), color: theme.list.itemCheckColors.foregroundColor)!, theme) + } let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: theme.list.itemCheckColors.foregroundColor, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = state.cachedStarImage?.0 { @@ -571,6 +665,12 @@ private final class SheetContent: CombinedComponent { buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string)) buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) } + if let range = buttonAttributedString.string.range(of: "$"), let tonImage = state.cachedTonImage?.0 { + buttonAttributedString.addAttribute(.attachment, value: tonImage, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.foregroundColor, value: theme.list.itemCheckColors.foregroundColor, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.baselineOffset, value: 1.5, range: NSRange(range, in: buttonAttributedString.string)) + buttonAttributedString.addAttribute(.kern, value: 2.0, range: NSRange(range, in: buttonAttributedString.string)) + } var isButtonEnabled = false let amount = state.amount ?? StarsAmount.zero @@ -618,8 +718,8 @@ private final class SheetContent: CombinedComponent { completion(amount.value) case let .paidMessages(_, _, _, _, completion): completion(amount.value) - case let .suggestedPost(_, _, _, completion): - completion(amount.value, state.timestamp) + case let .suggestedPost(_, _, _, _, completion): + completion(state.currency, amount.value, state.timestamp) } controller.dismissAnimated() @@ -653,6 +753,7 @@ private final class SheetContent: CombinedComponent { fileprivate var component: SheetContent fileprivate var amount: StarsAmount? + fileprivate var currency: TelegramCurrency fileprivate var timestamp: Int32? fileprivate var balance: StarsAmount? @@ -660,6 +761,7 @@ private final class SheetContent: CombinedComponent { var cachedCloseImage: (UIImage, PresentationTheme)? var cachedStarImage: (UIImage, PresentationTheme)? + var cachedTonImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)? init(component: SheetContent) { @@ -668,6 +770,7 @@ private final class SheetContent: CombinedComponent { self.component = component var amount: StarsAmount? + var currency: TelegramCurrency = .stars switch mode { case let .withdraw(stats, _): amount = StarsAmount(value: stats.balances.availableBalance.value, nanos: 0) @@ -681,13 +784,15 @@ private final class SheetContent: CombinedComponent { amount = nil case let .paidMessages(initialValue, _, _, _, _): amount = StarsAmount(value: initialValue, nanos: 0) - case let .suggestedPost(_, initialValue, initialTimestamp, _): + case let .suggestedPost(_, currencyValue, initialValue, initialTimestamp, _): + currency = currencyValue if initialValue != 0 { amount = StarsAmount(value: initialValue, nanos: 0) } self.timestamp = initialTimestamp } + self.currency = currency self.amount = amount super.init() @@ -696,7 +801,7 @@ private final class SheetContent: CombinedComponent { switch self.mode { case .reaction: needsBalance = true - case let .suggestedPost(mode, _, _, _): + case let .suggestedPost(mode, _, _, _, _): switch mode { case .sender: needsBalance = true @@ -854,7 +959,7 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { case reaction(Int64?, completion: (Int64) -> Void) case starGiftResell(StarGift.UniqueGift, Bool, completion: (Int64) -> Void) case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind, completion: (Int64) -> Void) - case suggestedPost(mode: SuggestedPostMode, price: Int64, timestamp: Int32?, completion: (Int64, Int32?) -> Void) + case suggestedPost(mode: SuggestedPostMode, currency: TelegramCurrency, price: Int64, timestamp: Int32?, completion: (TelegramCurrency, Int64, Int32?) -> Void) } private let context: AccountContext @@ -938,11 +1043,13 @@ private final class AmountFieldComponent: Component { let textColor: UIColor let secondaryColor: UIColor let placeholderColor: UIColor + let accentColor: UIColor let value: Int64? let minValue: Int64? let maxValue: Int64? let placeholderText: String let labelText: String? + let currency: TelegramCurrency let amountUpdated: (Int64?) -> Void let tag: AnyObject? @@ -950,22 +1057,26 @@ private final class AmountFieldComponent: Component { textColor: UIColor, secondaryColor: UIColor, placeholderColor: UIColor, + accentColor: UIColor, value: Int64?, minValue: Int64?, maxValue: Int64?, placeholderText: String, labelText: String?, + currency: TelegramCurrency, amountUpdated: @escaping (Int64?) -> Void, tag: AnyObject? = nil ) { self.textColor = textColor self.secondaryColor = secondaryColor self.placeholderColor = placeholderColor + self.accentColor = accentColor self.value = value self.minValue = minValue self.maxValue = maxValue self.placeholderText = placeholderText self.labelText = labelText + self.currency = currency self.amountUpdated = amountUpdated self.tag = tag } @@ -980,6 +1091,9 @@ private final class AmountFieldComponent: Component { if lhs.placeholderColor != rhs.placeholderColor { return false } + if lhs.accentColor != rhs.accentColor { + return false + } if lhs.value != rhs.value { return false } @@ -995,6 +1109,9 @@ private final class AmountFieldComponent: Component { if lhs.labelText != rhs.labelText { return false } + if lhs.currency != rhs.currency { + return false + } return true } @@ -1010,7 +1127,7 @@ private final class AmountFieldComponent: Component { } private let placeholderView: ComponentView - private let iconView: UIImageView + private let icon = ComponentView() private let textField: TextFieldNodeView private let labelView: ComponentView @@ -1021,8 +1138,6 @@ private final class AmountFieldComponent: Component { self.placeholderView = ComponentView() self.textField = TextFieldNodeView(frame: .zero) self.labelView = ComponentView() - - self.iconView = UIImageView(image: UIImage(bundleImageName: "Premium/Stars/StarLarge")) super.init(frame: frame) @@ -1030,7 +1145,6 @@ private final class AmountFieldComponent: Component { self.textField.addTarget(self, action: #selector(self.textChanged(_:)), for: .editingChanged) self.addSubview(self.textField) - self.addSubview(self.iconView) } required init?(coder: NSCoder) { @@ -1125,10 +1239,40 @@ private final class AmountFieldComponent: Component { let sideInset: CGFloat = 16.0 var leftInset: CGFloat = 16.0 - if let icon = self.iconView.image { - leftInset += icon.size.width + 6.0 - self.iconView.frame = CGRect(origin: CGPoint(x: 15.0, y: floorToScreenPixels((size.height - icon.size.height) / 2.0)), size: icon.size) + + let iconName: String + var iconTintColor: UIColor? + let iconMaxSize: CGSize? + var iconOffset = CGPoint() + switch component.currency { + case .stars: + iconName = "Premium/Stars/StarLarge" + iconMaxSize = CGSize(width: 22.0, height: 22.0) + case .ton: + iconName = "Ads/TonBig" + iconTintColor = component.accentColor + iconMaxSize = CGSize(width: 18.0, height: 18.0) + iconOffset = CGPoint(x: 3.0, y: 1.0) } + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: iconName, + tintColor: iconTintColor, + maxSize: iconMaxSize + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.frame = CGRect(origin: CGPoint(x: iconOffset.x + 15.0, y: iconOffset.y - 1.0 + floorToScreenPixels((size.height - iconSize.height) / 2.0)), size: iconSize) + } + + leftInset += 24.0 + 6.0 let placeholderSize = self.placeholderView.update( transition: .easeInOut(duration: 0.2), @@ -1148,7 +1292,7 @@ private final class AmountFieldComponent: Component { self.insertSubview(placeholderComponentView, at: 0) } - placeholderComponentView.frame = CGRect(origin: CGPoint(x: leftInset, y: floorToScreenPixels((size.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize) + placeholderComponentView.frame = CGRect(origin: CGPoint(x: leftInset, y: -1.0 + floorToScreenPixels((size.height - placeholderSize.height) / 2.0) + 1.0 - UIScreenPixel), size: placeholderSize) placeholderComponentView.isHidden = !(self.textField.text ?? "").isEmpty } @@ -1255,6 +1399,7 @@ private final class BalanceComponent: CombinedComponent { let context: AccountContext let theme: PresentationTheme let strings: PresentationStrings + let currency: TelegramCurrency let balance: StarsAmount? let alignment: NSTextAlignment @@ -1262,12 +1407,14 @@ private final class BalanceComponent: CombinedComponent { context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, + currency: TelegramCurrency, balance: StarsAmount?, alignment: NSTextAlignment ) { self.context = context self.theme = theme self.strings = strings + self.currency = currency self.balance = balance self.alignment = alignment } @@ -1282,6 +1429,9 @@ private final class BalanceComponent: CombinedComponent { if lhs.strings !== rhs.strings { return false } + if lhs.currency != rhs.currency { + return false + } if lhs.balance != rhs.balance { return false } @@ -1324,11 +1474,25 @@ private final class BalanceComponent: CombinedComponent { transition: .immediate ) - let iconSize = CGSize(width: 18.0, height: 18.0) + let iconSize: CGSize + let iconName: String + var iconOffset = CGPoint() + var iconTintColor: UIColor? + switch context.component.currency { + case .stars: + iconSize = CGSize(width: 18.0, height: 18.0) + iconName = "Premium/Stars/StarLarge" + case .ton: + iconSize = CGSize(width: 13.0, height: 13.0) + iconName = "Ads/TonBig" + iconTintColor = context.component.theme.list.itemAccentColor + iconOffset = CGPoint(x: 0.0, y: 2.33) + } + let icon = icon.update( component: BundleIconComponent( - name: "Premium/Stars/StarLarge", - tintColor: nil + name: iconName, + tintColor: iconTintColor ), availableSize: iconSize, transition: context.transition @@ -1355,7 +1519,7 @@ private final class BalanceComponent: CombinedComponent { ) context.add( icon.position( - icon.size.centered(in: CGRect(origin: CGPoint(x: size.width - balance.size.width - icon.size.width - 1.0, y: title.size.height + titleSpacing), size: icon.size)).center + icon.size.centered(in: CGRect(origin: CGPoint(x: iconOffset.x + size.width - balance.size.width - icon.size.width - 1.0, y: iconOffset.y + title.size.height + titleSpacing), size: icon.size)).center ) ) } else { @@ -1380,3 +1544,103 @@ private final class BalanceComponent: CombinedComponent { } } } + +private final class CurrencyTabItemComponent: Component { + typealias EnvironmentType = TabSelectorComponent.ItemEnvironment + + enum Icon { + case stars + case ton + } + + let icon: Icon + let title: String + let theme: PresentationTheme + + init( + icon: Icon, + title: String, + theme: PresentationTheme + ) { + self.icon = icon + self.title = title + self.theme = theme + } + + static func ==(lhs: CurrencyTabItemComponent, rhs: CurrencyTabItemComponent) -> Bool { + if lhs.icon != rhs.icon { + return false + } + if lhs.title != rhs.title { + return false + } + if lhs.theme !== rhs.theme { + return false + } + return true + } + + final class View: UIView { + private let title = ComponentView() + private let icon = ComponentView() + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func update(component: CurrencyTabItemComponent, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + let iconSpacing: CGFloat = 4.0 + + let iconSize = self.icon.update( + transition: .immediate, + component: AnyComponent(BundleIconComponent( + name: component.icon == .stars ? "Premium/Stars/StarLarge" : "Ads/TonAbout", + tintColor: component.icon == .stars ? nil : component.theme.list.itemAccentColor + )), + environment: {}, + containerSize: CGSize(width: 100.0, height: 100.0) + ) + + let titleSize = self.title.update( + transition: .immediate, + component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString(string: component.title, font: Font.medium(14.0), textColor: .white)) + )), + environment: {}, + containerSize: CGSize(width: availableSize.width, height: 100.0) + ) + + let titleFrame = CGRect(origin: CGPoint(x: iconSize.width + iconSpacing, y: 0.0), size: titleSize) + if let titleView = self.title.view { + if titleView.superview == nil { + self.addSubview(titleView) + } + titleView.frame = titleFrame + + transition.setTintColor(layer: titleView.layer, color: component.theme.list.freeTextColor.mixedWith(component.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.5), alpha: environment[TabSelectorComponent.ItemEnvironment.self].value.selectionFraction)) + } + + let iconFrame = CGRect(origin: CGPoint(x: 0.0, y: floorToScreenPixels((titleSize.height - iconSize.height) * 0.5)), size: iconSize) + if let iconView = self.icon.view { + if iconView.superview == nil { + self.addSubview(iconView) + } + iconView.frame = iconFrame + } + + return CGSize(width: iconSize.width + iconSpacing + titleSize.width, height: titleSize.height) + } + } + + func makeView() -> View { + return View(frame: CGRect()) + } + + func update(view: View, availableSize: CGSize, state: State, environment: Environment, transition: ComponentTransition) -> CGSize { + return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition) + } +} diff --git a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift index efce5db5c6..80e4ff12df 100644 --- a/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift +++ b/submodules/TelegramUI/Components/TabSelectorComponent/Sources/TabSelectorComponent.swift @@ -8,6 +8,21 @@ import TextFormat import AccountContext public final class TabSelectorComponent: Component { + public final class ItemEnvironment: Equatable { + public let selectionFraction: CGFloat + + init(selectionFraction: CGFloat) { + self.selectionFraction = selectionFraction + } + + public static func ==(lhs: ItemEnvironment, rhs: ItemEnvironment) -> Bool { + if lhs.selectionFraction != rhs.selectionFraction { + return false + } + return true + } + } + public struct Colors: Equatable { public var foreground: UIColor public var selection: UIColor @@ -43,15 +58,27 @@ public final class TabSelectorComponent: Component { } public struct Item: Equatable { + public enum Content: Equatable { + case text(String) + case component(AnyComponent) + } + public var id: AnyHashable - public var title: String + public var content: Content + public init( + id: AnyHashable, + content: Content + ) { + self.id = id + self.content = content + } + public init( id: AnyHashable, title: String ) { - self.id = id - self.title = title + self.init(id: id, content: .text(title)) } } @@ -227,16 +254,21 @@ public final class TabSelectorComponent: Component { selectionFraction = item.id == component.selectedId ? 1.0 : 0.0 } + var useSelectionFraction = isLineSelection + if case .component = item.content { + useSelectionFraction = true + } + let itemSize = itemView.title.update( transition: .immediate, component: AnyComponent(PlainButtonComponent( content: AnyComponent(ItemComponent( context: component.context, - text: item.title, + content: item.content, font: itemFont, color: component.colors.foreground, selectedColor: component.colors.selection, - selectionFraction: isLineSelection ? selectionFraction : 0.0 + selectionFraction: useSelectionFraction ? selectionFraction : 0.0 )), effectAlignment: .center, minSize: nil, @@ -379,7 +411,7 @@ extension CGRect { private final class ItemComponent: CombinedComponent { let context: AccountContext? - let text: String + let content: TabSelectorComponent.Item.Content let font: UIFont let color: UIColor let selectedColor: UIColor @@ -387,14 +419,14 @@ private final class ItemComponent: CombinedComponent { init( context: AccountContext?, - text: String, + content: TabSelectorComponent.Item.Content, font: UIFont, color: UIColor, selectedColor: UIColor, selectionFraction: CGFloat ) { self.context = context - self.text = text + self.content = content self.font = font self.color = color self.selectedColor = selectedColor @@ -405,7 +437,7 @@ private final class ItemComponent: CombinedComponent { if lhs.context !== rhs.context { return false } - if lhs.text != rhs.text { + if lhs.content != rhs.content { return false } if lhs.font != rhs.font { @@ -426,55 +458,73 @@ private final class ItemComponent: CombinedComponent { static var body: Body { let title = Child(MultilineTextWithEntitiesComponent.self) let selectedTitle = Child(MultilineTextWithEntitiesComponent.self) + let contentComponent = Child(environment: TabSelectorComponent.ItemEnvironment.self) return { context in let component = context.component - let attributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.color) - var range = (attributedTitle.string as NSString).range(of: "ā­ļø") - if range.location != NSNotFound { - attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + switch component.content { + case let .text(text): + let attributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: component.color) + var range = (attributedTitle.string as NSString).range(of: "ā­ļø") + if range.location != NSNotFound { + attributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + } + + let title = title.update( + component: MultilineTextWithEntitiesComponent( + context: component.context, + animationCache: component.context?.animationCache, + animationRenderer: component.context?.animationRenderer, + placeholderColor: .white, + text: .plain(attributedTitle) + ), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(title + .position(CGPoint(x: title.size.width / 2.0, y: title.size.height / 2.0)) + .opacity(1.0 - component.selectionFraction) + ) + + let selectedAttributedTitle = NSMutableAttributedString(string: text, font: component.font, textColor: component.selectedColor) + range = (selectedAttributedTitle.string as NSString).range(of: "ā­ļø") + if range.location != NSNotFound { + selectedAttributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) + } + + let selectedTitle = selectedTitle.update( + component: MultilineTextWithEntitiesComponent( + context: nil, + animationCache: nil, + animationRenderer: nil, + placeholderColor: .white, + text: .plain(selectedAttributedTitle) + ), + availableSize: context.availableSize, + transition: .immediate + ) + context.add(selectedTitle + .position(CGPoint(x: selectedTitle.size.width / 2.0, y: selectedTitle.size.height / 2.0)) + .opacity(component.selectionFraction) + ) + + return title.size + case let .component(contentComponentValue): + let content = contentComponent.update( + contentComponentValue, + environment: { + TabSelectorComponent.ItemEnvironment(selectionFraction: component.selectionFraction) + }, + availableSize: context.availableSize, + transition: .immediate + ) + context.add(content + .position(CGPoint(x: content.size.width / 2.0, y: content.size.height / 2.0)) + ) + + return content.size } - - let title = title.update( - component: MultilineTextWithEntitiesComponent( - context: component.context, - animationCache: component.context?.animationCache, - animationRenderer: component.context?.animationRenderer, - placeholderColor: .white, - text: .plain(attributedTitle) - ), - availableSize: context.availableSize, - transition: .immediate - ) - context.add(title - .position(CGPoint(x: title.size.width / 2.0, y: title.size.height / 2.0)) - .opacity(1.0 - component.selectionFraction) - ) - - let selectedAttributedTitle = NSMutableAttributedString(string: component.text, font: component.font, textColor: component.selectedColor) - range = (selectedAttributedTitle.string as NSString).range(of: "ā­ļø") - if range.location != NSNotFound { - selectedAttributedTitle.addAttribute(ChatTextInputAttributes.customEmoji, value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: 0, file: nil, custom: .stars(tinted: false)), range: range) - } - - let selectedTitle = selectedTitle.update( - component: MultilineTextWithEntitiesComponent( - context: nil, - animationCache: nil, - animationRenderer: nil, - placeholderColor: .white, - text: .plain(selectedAttributedTitle) - ), - availableSize: context.availableSize, - transition: .immediate - ) - context.add(selectedTitle - .position(CGPoint(x: selectedTitle.size.width / 2.0, y: selectedTitle.size.height / 2.0)) - .opacity(component.selectionFraction) - ) - - return title.size } } } diff --git a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift index 9b5bacdb47..dbd217153c 100644 --- a/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift +++ b/submodules/TelegramUI/Components/TextLoadingEffect/Sources/TextLoadingEffect.swift @@ -13,9 +13,11 @@ public final class TextLoadingEffectView: UIView { private let maskContentsView: UIView private let maskHighlightNode: LinkHighlightingNode + private var maskShapeLayer: SimpleShapeLayer? private let maskBorderContentsView: UIView private let maskBorderHighlightNode: LinkHighlightingNode + private var maskBorderShapeLayer: SimpleShapeLayer? private let backgroundView: UIImageView private let borderBackgroundView: UIImageView @@ -201,4 +203,63 @@ public final class TextLoadingEffectView: UIView { self.updateAnimations(size: maskFrame.size) } } + + public func update(color: UIColor, rect: CGRect, path: CGPath) { + let maskShapeLayer: SimpleShapeLayer + if let current = self.maskShapeLayer { + maskShapeLayer = current + } else { + maskShapeLayer = SimpleShapeLayer() + maskShapeLayer.fillColor = UIColor.white.cgColor + self.maskShapeLayer = maskShapeLayer + } + + let maskBorderShapeLayer: SimpleShapeLayer + if let current = self.maskBorderShapeLayer { + maskBorderShapeLayer = current + } else { + maskBorderShapeLayer = SimpleShapeLayer() + maskBorderShapeLayer.fillColor = nil + maskBorderShapeLayer.strokeColor = UIColor.white.cgColor + maskBorderShapeLayer.lineWidth = 4.0 + self.maskBorderShapeLayer = maskBorderShapeLayer + } + + maskShapeLayer.path = path + maskBorderShapeLayer.path = path + + if self.maskContentsView.layer.mask !== maskShapeLayer { + self.maskContentsView.layer.mask = maskShapeLayer + } + if self.maskBorderContentsView.layer.mask !== maskBorderShapeLayer { + self.maskBorderContentsView.layer.mask = maskBorderShapeLayer + } + + let maskFrame = CGRect(origin: CGPoint(), size: rect.size) + + self.gradientWidth = 260.0 + self.duration = 0.7 + + self.maskContentsView.backgroundColor = .clear + + self.backgroundView.alpha = 0.25 + self.backgroundView.tintColor = color + + self.borderBackgroundView.alpha = 0.5 + self.borderBackgroundView.tintColor = color + + self.maskContentsView.frame = maskFrame + self.maskBorderContentsView.frame = maskFrame + + maskShapeLayer.frame = CGRect(origin: CGPoint(x: -maskFrame.minX, y: -maskFrame.minY), size: CGSize()) + + if self.size != maskFrame.size { + self.size = maskFrame.size + + self.backgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) + self.borderBackgroundView.frame = CGRect(origin: CGPoint(x: -self.gradientWidth, y: 0.0), size: CGSize(width: self.gradientWidth, height: maskFrame.height)) + + self.updateAnimations(size: maskFrame.size) + } + } } diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 8a7b32b08d..5d36e4bff5 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -4131,23 +4131,80 @@ extension ChatControllerImpl { }) self.push(controller) } - }, openSuggestPost: { [weak self] in + }, openSuggestPost: { [weak self] message in guard let self else { return } - self.updateChatPresentationInterfaceState(interactive: true, { state in - var state = state - state = state.updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( - editingOriginalMessageId: nil, - price: 0, - timestamp: nil - )) - return interfaceState - } - return state - }) + + if let message { + let attribute = message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute + + self.updateChatPresentationInterfaceState(interactive: true, { state in + var entities: [MessageTextEntity] = [] + for attribute in message.attributes { + if let attribute = attribute as? TextEntitiesMessageAttribute { + entities = attribute.entities + break + } + } + var inputTextMaxLength: Int32 = 4096 + var webpageUrl: String? + for media in message.media { + if media is TelegramMediaImage || media is TelegramMediaFile { + inputTextMaxLength = self.context.userLimits.maxCaptionLength + } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { + webpageUrl = content.url + } + } + + let inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities) + var disableUrlPreviews: [String] = [] + if webpageUrl == nil { + disableUrlPreviews = detectUrls(inputText) + } + + var updated = state.updatedInterfaceState { interfaceState in + return interfaceState.withUpdatedEditMessage(ChatEditMessageState(messageId: message.id, inputState: ChatTextInputState(inputText: inputText), disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: inputTextMaxLength, mediaCaptionIsAbove: nil)) + } + + let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: self.context, state: updated, message: message) + updated = updatedState + self.editingUrlPreviewQueryState?.1.dispose() + self.editingUrlPreviewQueryState = updatedPreviewQueryState + + updated = updated.updatedInputMode({ _ in + return .text + }) + updated = updated.updatedShowCommands(false) + updated = updated.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + editingOriginalMessageId: message.id, + currency: attribute?.currency ?? .stars, + price: attribute?.amount ?? 0, + timestamp: attribute?.timestamp + )) + return interfaceState + } + return updated + }) + } else { + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + editingOriginalMessageId: nil, + currency: .stars, + price: 0, + timestamp: nil + )) + return interfaceState + } + return state + }) + } self.presentSuggestPostOptions() }, openPremiumRequiredForMessaging: { [weak self] in guard let self else { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 85a0215d36..c2fb775666 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -986,13 +986,35 @@ extension ChatControllerImpl { guard let postSuggestionState = self.presentationInterfaceState.interfaceState.postSuggestionState else { return } - self.push(self.context.sharedContext.makeStarsWithdrawalScreen( - context: self.context, - subject: .postSuggestion( + + let subject: StarsWithdrawalScreenSubject + if postSuggestionState.editingOriginalMessageId != nil { + subject = .postSuggestionModification(currency: postSuggestionState.currency, current: StarsAmount(value: postSuggestionState.price, nanos: 0), timestamp: postSuggestionState.timestamp, completion: { [weak self] currency, price, timestamp in + guard let self else { + return + } + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId, + currency: currency, + price: price, + timestamp: timestamp + )) + return interfaceState + } + return state + }) + }) + } else { + subject = .postSuggestion( channel: .channel(channel), + currency: postSuggestionState.currency, current: StarsAmount(value: postSuggestionState.price, nanos: 0), timestamp: postSuggestionState.timestamp, - completion: { [weak self] price, timestamp in + completion: { [weak self] currency, price, timestamp in guard let self else { return } @@ -1002,6 +1024,7 @@ extension ChatControllerImpl { var interfaceState = interfaceState interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( editingOriginalMessageId: interfaceState.postSuggestionState?.editingOriginalMessageId, + currency: currency, price: price, timestamp: timestamp )) @@ -1011,6 +1034,11 @@ extension ChatControllerImpl { }) } ) + } + + self.push(self.context.sharedContext.makeStarsWithdrawalScreen( + context: self.context, + subject: subject )) } } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index eff3574fbf..c7eef0549c 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -2305,7 +2305,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.enqueueChatContextResult(collection, result, hideVia: true, closeMediaInput: true, silentPosting: silentPosting, resetTextInputState: resetTextInputState) return true - }, requestMessageActionCallback: { [weak self] message, data, isGame, requiresPassword in + }, requestMessageActionCallback: { [weak self] message, data, isGame, requiresPassword, progress in guard let strongSelf = self else { return } @@ -2337,7 +2337,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return } if let value { - let _ = self.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: value.isEmpty ? nil : value)).startStandalone() + progress?.set(.single(true)) + let _ = self.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .reject(comment: value.isEmpty ? nil : value)).startStandalone(completed: { + progress?.set(.single(false)) + }) } }) strongSelf.present(promptController, in: .window(.root)) @@ -2348,7 +2351,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let strongSelf else { return } - let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone() + progress?.set(.single(true)) + let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: time != 0 ? time : nil)).startStandalone(completed: { + progress?.set(.single(false)) + }) }) strongSelf.view.endEditing(true) strongSelf.present(controller, in: .window(.root)) @@ -2358,55 +2364,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let _ = strongSelf.context.engine.messages.monoforumPerformSuggestedPostAction(id: message.id, action: .approve(timestamp: timestamp)).startStandalone() } case 2: - strongSelf.updateChatPresentationInterfaceState(interactive: true, { state in - var entities: [MessageTextEntity] = [] - for attribute in message.attributes { - if let attribute = attribute as? TextEntitiesMessageAttribute { - entities = attribute.entities - break - } - } - var inputTextMaxLength: Int32 = 4096 - var webpageUrl: String? - for media in message.media { - if media is TelegramMediaImage || media is TelegramMediaFile { - inputTextMaxLength = strongSelf.context.userLimits.maxCaptionLength - } else if let webpage = media as? TelegramMediaWebpage, case let .Loaded(content) = webpage.content { - webpageUrl = content.url - } - } - - let inputText = chatInputStateStringWithAppliedEntities(message.text, entities: entities) - var disableUrlPreviews: [String] = [] - if webpageUrl == nil { - disableUrlPreviews = detectUrls(inputText) - } - - var updated = state.updatedInterfaceState { interfaceState in - return interfaceState.withUpdatedEditMessage(ChatEditMessageState(messageId: messageId, inputState: ChatTextInputState(inputText: inputText), disableUrlPreviews: disableUrlPreviews, inputTextMaxLength: inputTextMaxLength, mediaCaptionIsAbove: nil)) - } - - let (updatedState, updatedPreviewQueryState) = updatedChatEditInterfaceMessageState(context: strongSelf.context, state: updated, message: message) - updated = updatedState - strongSelf.editingUrlPreviewQueryState?.1.dispose() - strongSelf.editingUrlPreviewQueryState = updatedPreviewQueryState - - updated = updated.updatedInputMode({ _ in - return .text - }) - updated = updated.updatedShowCommands(false) - updated = updated.updatedInterfaceState { interfaceState in - var interfaceState = interfaceState - - interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( - editingOriginalMessageId: message.id, - price: attribute.amount, - timestamp: attribute.timestamp - )) - return interfaceState - } - return updated - }) + strongSelf.interfaceInteraction?.openSuggestPost(message) default: break } @@ -2555,8 +2513,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G strongSelf.present(controller, in: .window(.root)) })) } else { + progress?.set(.single(true)) strongSelf.messageActionCallbackDisposable.set(((context.engine.messages.requestMessageActionCallback(messageId: messageId, isGame: isGame, password: nil, data: data) |> afterDisposed { + progress?.set(.single(false)) updateProgress() }) |> deliverOnMainQueue).startStrict(next: { result in @@ -8041,6 +8001,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G if let postSuggestionState = self.presentationInterfaceState.interfaceState.postSuggestionState { if attributes.first(where: { $0 is SuggestedPostMessageAttribute }) == nil { attributes.append(SuggestedPostMessageAttribute( + currency: postSuggestionState.currency, amount: postSuggestionState.price, timestamp: postSuggestionState.timestamp, state: nil diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index baa2276ad4..f7402e9518 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1067,7 +1067,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState controllerInteraction.displayUndo(.info(title: presentationData.strings.Notifications_UploadError_TooLong_Title(fileName).string, text: presentationData.strings.Notifications_UploadError_TooLong_Text(stringForDuration(Int32(settings.maxDuration))).string, timeout: nil, customUndoText: nil)) } else { let _ = (context.engine.peers.saveNotificationSound(file: .message(message: MessageReference(message), media: file)) - |> deliverOnMainQueue).startStandalone(completed: { + |> deliverOnMainQueue).startStandalone(completed: { controllerInteraction.displayUndo(.notificationSoundAdded(title: presentationData.strings.Notifications_UploadSuccess_Title, text: presentationData.strings.Notifications_SaveSuccess_Text, action: { controllerInteraction.navigationController()?.pushViewController(notificationsAndSoundsController(context: context, exceptionsList: nil)) })) @@ -1297,8 +1297,8 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState for media in message.media { if let image = media as? TelegramMediaImage, let largest = largestImageRepresentation(image.representations) { let _ = (context.account.postbox.mediaBox.resourceData(largest.resource, option: .incremental(waitUntilFetchStatus: false)) - |> take(1) - |> deliverOnMainQueue).startStandalone(next: { data in + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { data in if data.complete, let imageData = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { if let image = UIImage(data: imageData) { if !messageText.isEmpty { @@ -1382,7 +1382,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Save"), color: theme.actionSheet.primaryTextColor) }, action: { _, f in let _ = (saveToCameraRoll(context: context, postbox: context.account.postbox, userLocation: .peer(message.id.peerId), mediaReference: mediaReference) - |> deliverOnMainQueue).startStandalone(completed: { + |> deliverOnMainQueue).startStandalone(completed: { Queue.mainQueue().after(0.2) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } controllerInteraction.presentControllerInCurrent(UndoOverlayController(presentationData: presentationData, content: .mediaSaved(text: isVideo ? presentationData.strings.Gallery_VideoSaved : presentationData.strings.Gallery_ImageSaved), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return true }), nil) @@ -1481,7 +1481,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } else { isMigrated = false } - + var activePoll: TelegramMediaPoll? var activeTodo: TelegramMediaTodo? for media in message.media { @@ -1508,6 +1508,17 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } }))) } + + if let message = messages.first, message.id.namespace == Namespaces.Message.Cloud, let channel = message.peers[message.id.peerId] as? TelegramChannel, channel.isMonoForum { + //TODO:localize + actions.append(.action(ContextMenuActionItem(text: "Suggest a Post", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Customize"), color: theme.actionSheet.primaryTextColor) + }, action: { c, _ in + c?.dismiss(completion: { + interfaceInteraction.openSuggestPost(message) + }) + }))) + } if let activePoll = activePoll, let voters = activePoll.results.voters { var hasSelected = false diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 4dee7db1c6..bc7e3b87e0 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -935,10 +935,29 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { controllerInteraction.shareAccountContact() return case .openWebApp: - controllerInteraction.requestMessageActionCallback(message, nil, true, false) + let progressPromise = Promise() + controllerInteraction.requestMessageActionCallback(message, nil, true, false, progressPromise) + self.progressDisposable?.dispose() + self.progressDisposable = (progressPromise.get() + |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + self.updateIsLoading(isLoading: value) + }) + return case let .callback(requiresPassword, data): - controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword) + let progressPromise = Promise() + controllerInteraction.requestMessageActionCallback(message, data, false, requiresPassword, progressPromise) + self.progressDisposable?.dispose() + self.progressDisposable = (progressPromise.get() + |> deliverOnMainQueue).startStrict(next: { [weak self] value in + guard let self else { + return + } + self.updateIsLoading(isLoading: value) + }) return case let .switchInline(samePeer, query, peerTypes): var botPeer: Peer? diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index f110d755c1..8c87be008e 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -46,6 +46,7 @@ import AnimatedCountLabelNode import TelegramStringFormatting import TextNodeWithEntities import DeviceModel +import PhotoResources private let accessoryButtonFont = Font.medium(14.0) private let counterFont = Font.with(size: 14.0, design: .regular, traits: [.monospacedNumbers]) @@ -565,7 +566,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch let attachmentButton: HighlightableButtonNode let attachmentButtonDisabledNode: HighlightableButtonNode - + var attachmentImageNode: TransformImageNode? let searchLayoutClearButton: HighlightableButton private let searchLayoutClearImageNode: ASImageNode @@ -1563,6 +1564,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch } else { attachmentButtonAlpha = 0.0 } + transition.updateAlpha(layer: self.attachmentButton.layer, alpha: attachmentButtonAlpha) self.attachmentButton.isEnabled = isMediaEnabled && !isRecording self.attachmentButton.accessibilityTraits = (!isSlowmodeActive || isMediaEnabled) ? [.button] : [.button, .notEnabled] @@ -2469,9 +2471,66 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch leftInset += leftMenuInset - transition.updateFrame(layer: self.attachmentButton.layer, frame: CGRect(origin: CGPoint(x: attachmentButtonX, y: hideOffset.y + panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight))) + let attachmentButtonFrame = CGRect(origin: CGPoint(x: attachmentButtonX, y: hideOffset.y + panelHeight - minimalHeight), size: CGSize(width: 40.0, height: minimalHeight)) + + transition.updateFrame(layer: self.attachmentButton.layer, frame: attachmentButtonFrame) transition.updateFrame(node: self.attachmentButtonDisabledNode, frame: self.attachmentButton.frame) + if let context = self.context, let interfaceState = self.presentationInterfaceState, let editMessageState = interfaceState.editMessageState, let updatedMediaReference = editMessageState.mediaReference { + let attachmentImageNode: TransformImageNode + if let current = self.attachmentImageNode { + attachmentImageNode = current + } else { + attachmentImageNode = TransformImageNode() + attachmentImageNode.isUserInteractionEnabled = false + self.attachmentImageNode = attachmentImageNode + self.addSubnode(attachmentImageNode) + } + + let attachmentImageSize = CGSize(width: 26.0, height: 26.0) + let attachmentImageFrame = CGRect(origin: CGPoint(x: attachmentButtonFrame.minX + floorToScreenPixels((40.0 - attachmentImageSize.width) * 0.5), y: attachmentButtonFrame.minY + floorToScreenPixels((attachmentButtonFrame.height - attachmentImageSize.height) * 0.5)), size: attachmentImageSize) + attachmentImageNode.frame = attachmentImageFrame + + let hasSpoiler: Bool = false + + var updateImageSignal: Signal<(TransformImageArguments) -> DrawingContext?, NoError>? + var imageDimensions: CGSize? + if let imageReference = updatedMediaReference.concrete(TelegramMediaImage.self) { + imageDimensions = imageReference.media.representations.last?.dimensions.cgSize + updateImageSignal = chatMessagePhotoThumbnail(account: context.account, userLocation: .other, photoReference: imageReference, blurred: hasSpoiler) + } else if let fileReference = updatedMediaReference.concrete(TelegramMediaFile.self) { + imageDimensions = fileReference.media.dimensions?.cgSize + if fileReference.media.isVideo { + updateImageSignal = chatMessageVideoThumbnail(account: context.account, userLocation: .other, fileReference: fileReference, blurred: hasSpoiler) + } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { + updateImageSignal = chatWebpageSnippetFile(account: context.account, userLocation: .other, mediaReference: fileReference.abstract, representation: iconImageRepresentation) + } + } + //TODO:release catch updates + if let updateImageSignal { + attachmentImageNode.setSignal(updateImageSignal) + } + + let makeAttachmentImageNodeLayout = attachmentImageNode.asyncLayout() + let isRoundImage = !"".isEmpty + + if let imageDimensions { + let boundingSize = attachmentImageSize + var radius: CGFloat = 4.0 + var imageSize = imageDimensions.aspectFilled(boundingSize) + if isRoundImage { + radius = floor(boundingSize.width / 2.0) + imageSize.width += 2.0 + imageSize.height += 2.0 + } + let applyImage = makeAttachmentImageNodeLayout(TransformImageArguments(corners: ImageCorners(radius: radius), imageSize: imageSize, boundingSize: boundingSize, intrinsicInsets: UIEdgeInsets())) + applyImage() + } + } else if let attachmentImageNode = self.attachmentImageNode { + self.attachmentImageNode = nil + attachmentImageNode.removeFromSupernode() + } + var composeButtonsOffset: CGFloat = 0.0 if self.extendedSearchLayout { composeButtonsOffset = 44.0 @@ -4714,7 +4773,7 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch case .gift: self.interfaceInteraction?.openPremiumGift() case .suggestPost: - self.interfaceInteraction?.openSuggestPost() + self.interfaceInteraction?.openSuggestPost(nil) } break } diff --git a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift index 29a9e1bea1..2456775353 100644 --- a/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift +++ b/submodules/TelegramUI/Sources/OverlayAudioPlayerControllerNode.swift @@ -95,7 +95,7 @@ final class OverlayAudioPlayerControllerNode: ViewControllerTracingNode, ASGestu return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index f093393c11..ccbb0fc80c 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -2319,7 +2319,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { clickThroughMessage?(view, location) }, toggleMessagesSelection: { _, _ in }, sendCurrentMessage: { _, _ in }, sendMessage: { _ in }, sendSticker: { _, _, _, _, _, _, _, _, _ in return false }, sendEmoji: { _, _, _ in }, sendGif: { _, _, _, _, _ in return false }, sendBotContextResultAsGif: { _, _, _, _, _, _ in return false - }, requestMessageActionCallback: { _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in + }, requestMessageActionCallback: { _, _, _, _, _ in }, requestMessageActionUrlAuth: { _, _ in }, activateSwitchInline: { _, _, _ in }, openUrl: { _ in }, shareCurrentLocation: {}, shareAccountContact: {}, sendBotCommand: { _, _ in }, openInstantPage: { _, _ in }, openWallpaper: { _ in }, openTheme: { _ in }, openHashtag: { _, _ in }, updateInputState: { _ in }, updateInputMode: { _ in }, openMessageShareMenu: { _ in }, presentController: { _, _ in }, presentControllerInCurrent: { _, _ in }, navigationController: { @@ -3734,10 +3734,10 @@ public final class SharedAccountContextImpl: SharedAccountContext { mode = .accountWithdraw(completion: completion) case let .enterAmount(current, minValue, fractionAfterCommission, kind, completion): mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion) - case let .postSuggestion(channel, current, timestamp, completion): - mode = .suggestedPost(mode: .sender(channel: channel), price: current.value, timestamp: timestamp, completion: completion) - case let .postSuggestionModification(current, timestamp, completion): - mode = .suggestedPost(mode: .admin, price: current.value, timestamp: timestamp, completion: completion) + case let .postSuggestion(channel, currency, current, timestamp, completion): + mode = .suggestedPost(mode: .sender(channel: channel), currency: currency, price: current.value, timestamp: timestamp, completion: completion) + case let .postSuggestionModification(currency, current, timestamp, completion): + mode = .suggestedPost(mode: .admin, currency: currency, price: current.value, timestamp: timestamp, completion: completion) } return StarsWithdrawScreen(context: context, mode: mode) }