From abfdf04e9f953ea4890c016b1d575325896c68d6 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 26 Jun 2025 15:02:21 +0200 Subject: [PATCH 1/7] Update API --- submodules/TelegramApi/Sources/Api0.swift | 2 + submodules/TelegramApi/Sources/Api15.swift | 42 +++++++++++++++++++ .../ApiUtils/StoreMessage_Telegram.swift | 2 +- .../ApiUtils/TelegramMediaAction.swift | 4 ++ .../SyncCore_TelegramMediaAction.swift | 34 +++++++++++++++ .../Sources/ServiceMessageStrings.swift | 6 +++ .../Sources/ChatMessageItemCommon.swift | 2 +- 7 files changed, 90 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 2d961b7d2c..4f2428fbac 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -609,6 +609,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[775611918] = { return Api.MessageAction.parse_messageActionStarGiftUnique($0) } dict[1474192222] = { return Api.MessageAction.parse_messageActionSuggestProfilePhoto($0) } dict[-293988970] = { return Api.MessageAction.parse_messageActionSuggestedPostApproval($0) } + dict[1777932024] = { return Api.MessageAction.parse_messageActionSuggestedPostRefund($0) } + dict[-1780625559] = { return Api.MessageAction.parse_messageActionSuggestedPostSuccess($0) } dict[-940721021] = { return Api.MessageAction.parse_messageActionTodoAppendTasks($0) } dict[-864265079] = { return Api.MessageAction.parse_messageActionTodoCompletions($0) } dict[228168278] = { return Api.MessageAction.parse_messageActionTopicCreate($0) } diff --git a/submodules/TelegramApi/Sources/Api15.swift b/submodules/TelegramApi/Sources/Api15.swift index 7359821a69..5c2505e379 100644 --- a/submodules/TelegramApi/Sources/Api15.swift +++ b/submodules/TelegramApi/Sources/Api15.swift @@ -397,6 +397,8 @@ public extension Api { case messageActionStarGiftUnique(flags: Int32, gift: Api.StarGift, canExportAt: Int32?, transferStars: Int64?, fromId: Api.Peer?, peer: Api.Peer?, savedId: Int64?, resaleStars: Int64?, canTransferAt: Int32?, canResellAt: Int32?) case messageActionSuggestProfilePhoto(photo: Api.Photo) case messageActionSuggestedPostApproval(flags: Int32, rejectComment: String?, scheduleDate: Int32?, price: Api.StarsAmount?) + case messageActionSuggestedPostRefund(flags: Int32) + case messageActionSuggestedPostSuccess(price: Api.StarsAmount) case messageActionTodoAppendTasks(list: [Api.TodoItem]) case messageActionTodoCompletions(completed: [Int32], incompleted: [Int32]) case messageActionTopicCreate(flags: Int32, title: String, iconColor: Int32, iconEmojiId: Int64?) @@ -825,6 +827,18 @@ public extension Api { if Int(flags) & Int(1 << 3) != 0 {serializeInt32(scheduleDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 4) != 0 {price!.serialize(buffer, true)} break + case .messageActionSuggestedPostRefund(let flags): + if boxed { + buffer.appendInt32(1777932024) + } + serializeInt32(flags, buffer: buffer, boxed: false) + break + case .messageActionSuggestedPostSuccess(let price): + if boxed { + buffer.appendInt32(-1780625559) + } + price.serialize(buffer, true) + break case .messageActionTodoAppendTasks(let list): if boxed { buffer.appendInt32(-940721021) @@ -985,6 +999,10 @@ public extension Api { return ("messageActionSuggestProfilePhoto", [("photo", photo as Any)]) case .messageActionSuggestedPostApproval(let flags, let rejectComment, let scheduleDate, let price): return ("messageActionSuggestedPostApproval", [("flags", flags as Any), ("rejectComment", rejectComment as Any), ("scheduleDate", scheduleDate as Any), ("price", price as Any)]) + case .messageActionSuggestedPostRefund(let flags): + return ("messageActionSuggestedPostRefund", [("flags", flags as Any)]) + case .messageActionSuggestedPostSuccess(let price): + return ("messageActionSuggestedPostSuccess", [("price", price as Any)]) case .messageActionTodoAppendTasks(let list): return ("messageActionTodoAppendTasks", [("list", list as Any)]) case .messageActionTodoCompletions(let completed, let incompleted): @@ -1832,6 +1850,30 @@ public extension Api { return nil } } + public static func parse_messageActionSuggestedPostRefund(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestedPostRefund(flags: _1!) + } + else { + return nil + } + } + public static func parse_messageActionSuggestedPostSuccess(_ reader: BufferReader) -> MessageAction? { + var _1: Api.StarsAmount? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.StarsAmount + } + let _c1 = _1 != nil + if _c1 { + return Api.MessageAction.messageActionSuggestedPostSuccess(price: _1!) + } + else { + return nil + } + } public static func parse_messageActionTodoAppendTasks(_ reader: BufferReader) -> MessageAction? { var _1: [Api.TodoItem]? if let _ = reader.readInt32() { diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index eb7de8d13b..070586f06c 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -232,7 +232,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall, .messageActionSetMessagesTTL, .messageActionGroupCallScheduled, .messageActionSetChatTheme, .messageActionChatJoinedByRequest, .messageActionWebViewDataSent, .messageActionWebViewDataSentMe, .messageActionGiftPremium, .messageActionGiftStars, .messageActionTopicCreate, .messageActionTopicEdit, .messageActionSuggestProfilePhoto, .messageActionSetChatWallPaper, .messageActionGiveawayLaunch, .messageActionGiveawayResults, .messageActionBoostApply, .messageActionRequestedPeerSentMe, .messageActionStarGift, .messageActionStarGiftUnique, .messageActionPaidMessagesRefunded, .messageActionPaidMessagesPrice, .messageActionTodoCompletions, .messageActionTodoAppendTasks, .messageActionSuggestedPostApproval, .messageActionGiftTon, .messageActionSuggestedPostSuccess, .messageActionSuggestedPostRefund: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: PeerId.Id._internalFromInt64Value(chatId))) diff --git a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift index 8354858c1a..c3c28bd13e 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/TelegramMediaAction.swift @@ -264,6 +264,10 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .suggestedPostApprovalStatus(status: status)) case let .messageActionGiftTon(_, currency, amount, cryptoCurrency, cryptoAmount, transactionId): return TelegramMediaAction(action: .giftTon(currency: currency, amount: amount, cryptoCurrency: cryptoCurrency, cryptoAmount: cryptoAmount, transactionId: transactionId)) + case let .messageActionSuggestedPostSuccess(price): + return TelegramMediaAction(action: .suggestedPostSuccess(amount: CurrencyAmount(apiAmount: price))) + case let .messageActionSuggestedPostRefund(flags): + return TelegramMediaAction(action: .suggestedPostRefund(TelegramMediaActionType.SuggestedPostRefund(isUserInitiated: (flags & (1 << 0)) != 0))) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift index 78b964a97f..a079328a77 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SyncCore_TelegramMediaAction.swift @@ -177,6 +177,28 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } } + public struct SuggestedPostRefund: Codable, Equatable { + private enum CodingKeys: String, CodingKey { + case isUserInitiated = "iui" + } + + public var isUserInitiated: Bool + + public init(isUserInitiated: Bool) { + self.isUserInitiated = isUserInitiated + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.isUserInitiated = try container.decode(Bool.self, forKey: .isUserInitiated) + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(self.isUserInitiated, forKey: .isUserInitiated) + } + } + case unknown case groupCreated(title: String) case addedMembers(peerIds: [PeerId]) @@ -230,6 +252,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case todoAppendTasks([TelegramMediaTodo.Item]) case suggestedPostApprovalStatus(status: SuggestedPostApprovalStatus) case giftTon(currency: String, amount: Int64, cryptoCurrency: String?, cryptoAmount: Int64?, transactionId: String?) + case suggestedPostSuccess(amount: CurrencyAmount) + case suggestedPostRefund(SuggestedPostRefund) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -379,6 +403,10 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .suggestedPostApprovalStatus(status: status ?? .rejected(reason: .generic, comment: nil)) case 52: self = .giftTon(currency: decoder.decodeStringForKey("currency", orElse: ""), amount: decoder.decodeInt64ForKey("amount", orElse: 0), cryptoCurrency: decoder.decodeOptionalStringForKey("cryptoCurrency"), cryptoAmount: decoder.decodeOptionalInt64ForKey("cryptoAmount"), transactionId: decoder.decodeOptionalStringForKey("transactionId")) + case 53: + self = .suggestedPostSuccess(amount: decoder.decodeCodable(CurrencyAmount.self, forKey: "amt") ?? CurrencyAmount(amount: .zero, currency: .stars)) + case 54: + self = .suggestedPostRefund(decoder.decodeCodable(SuggestedPostRefund.self, forKey: "s") ?? SuggestedPostRefund(isUserInitiated: true)) default: self = .unknown } @@ -808,6 +836,12 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { } else { encoder.encodeNil(forKey: "transactionId") } + case let .suggestedPostSuccess(amount): + encoder.encodeInt32(53, forKey: "_rawValue") + encoder.encodeCodable(amount, forKey: "amt") + case let .suggestedPostRefund(status): + encoder.encodeInt32(54, forKey: "_rawValue") + encoder.encodeCodable(status, forKey: "s") } } diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 4f24755979..81bfc24495 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1450,6 +1450,12 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) + case .suggestedPostSuccess: + //TODO:localize + attributedString = NSAttributedString(string: "Suggested post was posted", font: titleFont, textColor: primaryTextColor) + case .suggestedPostRefund: + //TODO:localize + attributedString = NSAttributedString(string: "Suggested post was refunded", font: titleFont, textColor: primaryTextColor) case let .giftTon(currency, amount, _, _, _): attributedString = nil if !forAdditionalServiceMessage { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift index bbdb19ca9a..0eab89f7bb 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageItemCommon/Sources/ChatMessageItemCommon.swift @@ -291,7 +291,7 @@ public func canAddMessageReactions(message: Message) -> Bool { return true } else { switch action.action { - case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched, .suggestedPostApprovalStatus: + case .unknown, .groupCreated, .channelMigratedFromGroup, .groupMigratedToChannel, .historyCleared, .customText, .botDomainAccessGranted, .botAppAccessGranted, .botSentSecureValues, .phoneNumberRequest, .webViewData, .topicCreated, .attachMenuBotAllowed, .requestedPeer, .giveawayLaunched, .suggestedPostApprovalStatus, .suggestedPostSuccess, .suggestedPostRefund: return false default: return true From d7dbeed5d638add097f0c57a90d7507346eea027 Mon Sep 17 00:00:00 2001 From: Mikhail Filimonov Date: Thu, 26 Jun 2025 16:23:19 +0100 Subject: [PATCH 2/7] group call e2e-offset bug fix --- .../State/ConferenceCallE2EContext.swift | 4 ++-- third-party/td/TdBinding/Sources/TdBinding.mm | 20 +++++++++++++++++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift index ad71c3ea4f..d947507fb5 100644 --- a/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift +++ b/submodules/TelegramCore/Sources/State/ConferenceCallE2EContext.swift @@ -240,12 +240,12 @@ public final class ConferenceCallE2EContext { var delayPoll = true if let result { if subChainId == 0 { - if self.e2ePoll0Offset != result.nextOffset { + if let e2ePoll0Offset = self.e2ePoll0Offset, e2ePoll0Offset < result.nextOffset { self.e2ePoll0Offset = result.nextOffset delayPoll = false } } else if subChainId == 1 { - if self.e2ePoll1Offset != result.nextOffset { + if let e2ePoll1Offset = self.e2ePoll1Offset, e2ePoll1Offset < result.nextOffset { self.e2ePoll1Offset = result.nextOffset delayPoll = false } diff --git a/third-party/td/TdBinding/Sources/TdBinding.mm b/third-party/td/TdBinding/Sources/TdBinding.mm index d7f5f1f369..edc8270c38 100644 --- a/third-party/td/TdBinding/Sources/TdBinding.mm +++ b/third-party/td/TdBinding/Sources/TdBinding.mm @@ -392,6 +392,26 @@ NSData * _Nullable tdGenerateSelfAddBlock(TdKeyPair *keyPair, int64_t userId, NS std::string mappedPublicKey((uint8_t *)keyPair.publicKey.bytes, ((uint8_t *)keyPair.publicKey.bytes) + keyPair.publicKey.length); std::string mappedPreviousBlock((uint8_t *)previousBlock.bytes, ((uint8_t *)previousBlock.bytes) + previousBlock.length); + + #if DEBUG + auto describeResult = tde2e_api::call_describe_block(mappedPreviousBlock); + if (describeResult.is_ok()) { + NSString *utf8String = [[NSString alloc] initWithBytes:describeResult.value().data() length:describeResult.value().size() encoding:NSUTF8StringEncoding]; + if (utf8String) { + NSLog(@"TdCall.selfAddBlock block: %@", utf8String); + } else { + NSString *lossyString = [[NSString alloc] initWithData:[NSData dataWithBytes:describeResult.value().data() length:describeResult.value().size()] encoding:NSASCIIStringEncoding]; + if (lossyString) { + NSLog(@"TdCall.selfAddBlock block (lossy conversion): %@", lossyString); + } else { + NSLog(@"TdCall.selfAddBlock block: [binary data, length: %lu]", (unsigned long)describeResult.value().size()); + } + } + } else { + NSLog(@"TdCall.selfAddBlock describe block failed"); + } + #endif + auto publicKeyId = tde2e_api::key_from_public_key(mappedPublicKey); if (!publicKeyId.is_ok()) { return nil; From 3e40713aad20a52f446cd1c82914d01d8d2fc369 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Thu, 26 Jun 2025 20:29:23 +0200 Subject: [PATCH 3/7] Various improvements --- .../Sources/AccountContext.swift | 2 +- .../StandaloneSendMessage.swift | 4 +- .../Sources/State/PendingMessageManager.swift | 4 +- .../SuggestedPostMessageAttribute.swift | 13 +- .../ChatMessageActionBubbleContentNode.swift | 15 +- .../ChatMessageActionButtonsNode.swift | 55 ++- .../ChatMessageAnimatedStickerItemNode.swift | 24 +- .../Sources/ChatMessageBubbleItemNode.swift | 26 +- .../Sources/ChatMessageStickerItemNode.swift | 24 +- .../Sources/ChatSendStarsScreen.swift | 137 ------ .../Sources/StarsWithdrawalScreen.swift | 405 ++++-------------- .../Chat/ChatMessageActionOptions.swift | 7 + .../Sources/ChatInterfaceInputContexts.swift | 2 +- .../Sources/SharedAccountContext.swift | 4 +- 14 files changed, 207 insertions(+), 515 deletions(-) diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 42bf87629b..877ed5d1f5 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1038,7 +1038,7 @@ 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: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) + case postSuggestion(channel: EnginePeer, isFromAdmin: Bool, current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) case postSuggestionModification(current: CurrencyAmount, timestamp: Int32?, completion: (CurrencyAmount, Int32?) -> Void) } diff --git a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift index 2cac48b3b5..b3749f0605 100644 --- a/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift +++ b/submodules/TelegramCore/Sources/PendingMessages/StandaloneSendMessage.swift @@ -384,7 +384,7 @@ private func sendUploadedMessageContent( } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { - suggestedPost = attribute.apiSuggestedPost() + suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) } } @@ -656,7 +656,7 @@ private func sendMessageContent(account: Account, peerId: PeerId, attributes: [M } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { - suggestedPost = attribute.apiSuggestedPost() + suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) } } diff --git a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift index 770abaa3e0..df88ea29d2 100644 --- a/submodules/TelegramCore/Sources/State/PendingMessageManager.swift +++ b/submodules/TelegramCore/Sources/State/PendingMessageManager.swift @@ -900,7 +900,7 @@ public final class PendingMessageManager { } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value * Int64(messages.count) } else if let attribute = attribute as? SuggestedPostMessageAttribute { - suggestedPost = attribute.apiSuggestedPost() + suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) } } @@ -1412,7 +1412,7 @@ public final class PendingMessageManager { } else if let attribute = attribute as? PaidStarsMessageAttribute { allowPaidStars = attribute.stars.value } else if let attribute = attribute as? SuggestedPostMessageAttribute { - suggestedPost = attribute.apiSuggestedPost() + suggestedPost = attribute.apiSuggestedPost(fixMinTime: Int32(Date().timeIntervalSince1970 + 10)) } } diff --git a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift index 390710fb04..4602bd7ccc 100644 --- a/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift +++ b/submodules/TelegramCore/Sources/SyncCore/SuggestedPostMessageAttribute.swift @@ -70,7 +70,7 @@ extension SuggestedPostMessageAttribute { } } - func apiSuggestedPost() -> Api.SuggestedPost { + func apiSuggestedPost(fixMinTime: Int32?) -> Api.SuggestedPost { var flags: Int32 = 0 if let state = self.state { switch state { @@ -80,7 +80,14 @@ extension SuggestedPostMessageAttribute { flags |= 1 << 2 } } - if self.timestamp != nil { + var timestamp = self.timestamp + if let timestampValue = timestamp, let fixMinTime { + if timestampValue < fixMinTime { + timestamp = fixMinTime + } + } + + if timestamp != nil { flags |= 1 << 0 } var price: Api.StarsAmount? @@ -88,7 +95,7 @@ extension SuggestedPostMessageAttribute { flags |= 1 << 3 price = amount.apiAmount } - return .suggestedPost(flags: flags, price: price, scheduleDate: self.timestamp) + return .suggestedPost(flags: flags, price: price, scheduleDate: timestamp) } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift index c6673ec321..4aac0f9313 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionBubbleContentNode/Sources/ChatMessageActionBubbleContentNode.swift @@ -217,6 +217,11 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { } } + var isUser = true + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isUser = false + } + let imageSize = CGSize(width: 212.0, height: 212.0) var updatedAttributedString = attributedString @@ -270,13 +275,13 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { switch amount.currency { case .stars: - if !item.message.effectivelyIncoming(item.context.account.peerId) { + if !isUser { pricePart = "\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 { pricePart = "\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." } case .ton: - if !item.message.effectivelyIncoming(item.context.account.peerId) { + if !isUser { pricePart = "\n\nšŸ’° The user have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive TON 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 TON will be refunded." } else { pricePart = "\n\nšŸ’° You have been charged \(amountString).\n\nāŒ› **\(channelName)** will receive your TON 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 TON will be refunded." @@ -287,20 +292,20 @@ public class ChatMessageActionBubbleContentNode: ChatMessageBubbleContentNode { let rawString: String if let timestamp { if Int32(Date().timeIntervalSince1970) >= timestamp { - if !item.message.effectivelyIncoming(item.context.account.peerId) { + if !isUser { rawString = "šŸ“… The post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart } else { rawString = "šŸ“… Your post has been automatically published in **\(channelName)** **\(timeString)**." + pricePart } } else { - if !item.message.effectivelyIncoming(item.context.account.peerId) { + if !isUser { rawString = "šŸ“… The post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart } else { rawString = "šŸ“… Your post will be automatically published in **\(channelName)** **\(timeString)**." + pricePart } } } else { - if !item.message.effectivelyIncoming(item.context.account.peerId) { + if !isUser { rawString = "šŸ“… The post has been automatically published in **\(channelName)**." + pricePart } else { rawString = "šŸ“… Your post has been automatically published in **\(channelName)**." + pricePart diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift index ade800e580..9aa7f29a9b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageActionButtonsNode/Sources/ChatMessageActionButtonsNode.swift @@ -213,10 +213,10 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } } - class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customIcon: ChatMessageActionButtonsNode.CustomIcon?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { + class func asyncLayout(_ maybeNode: ChatMessageActionButtonNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ bubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ message: Message, _ button: ReplyMarkupButton, _ customInfo: ChatMessageActionButtonsNode.CustomInfo?, _ constrainedWidth: CGFloat, _ position: MessageBubbleActionButtonPosition) -> (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) { let titleLayout = TextNode.asyncLayout(maybeNode?.titleNode) - return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customIcon, constrainedWidth, position in + return { context, theme, bubbleCorners, strings, backgroundNode, message, button, customInfo, constrainedWidth, position in let incoming = message.effectivelyIncoming(context.account.peerId) let graphics = PresentationResourcesChat.additionalGraphics(theme.theme, wallpaper: theme.wallpaper, bubbleCorners: bubbleCorners) @@ -227,7 +227,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { var isStarsPayment = false let iconImage: UIImage? var tintColor: UIColor? - if let customIcon { + if let customIcon = customInfo?.icon { switch customIcon { case .suggestedPostReject: iconImage = PresentationResourcesChat.messageButtonsPostReject(theme.theme) @@ -314,7 +314,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } var customIconSpaceWidth: CGFloat = 0.0 - if let iconImage, customIcon != nil { + if let iconImage, customInfo?.icon != nil { customIconSpaceWidth = 3.0 + iconImage.size.width } @@ -334,14 +334,13 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } node.wallpaperBackgroundNode = backgroundNode - node.button = button switch button.action { - case .url: - node.longTapRecognizer?.isEnabled = true - default: - node.longTapRecognizer?.isEnabled = false + case .url: + node.longTapRecognizer?.isEnabled = true + default: + node.longTapRecognizer?.isEnabled = false } //animation.animator.updateFrame(layer: node.backgroundBlurNode.layer, frame: CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, width), height: 42.0)), completion: nil) @@ -453,7 +452,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } var titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.size.width) / 2.0), y: floor((42.0 - titleSize.size.height) / 2.0) + 1.0), size: titleSize.size) - if let image = node.iconNode?.image, customIcon != nil { + if let image = node.iconNode?.image, customInfo?.icon != nil { titleFrame.origin.x = floorToScreenPixels((width - titleSize.size.width - image.size.width - 3.0) * 0.5) + 3.0 + image.size.width } titleNode.layer.bounds = CGRect(origin: CGPoint(), size: titleFrame.size) @@ -464,7 +463,7 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { } if let iconNode = node.iconNode { let iconFrame: CGRect - if customIcon != nil, let image = iconNode.image { + if customInfo?.icon != nil, let image = iconNode.image { iconFrame = CGRect(x: titleFrame.minX - 3.0 - image.size.width, y: titleFrame.minY + floorToScreenPixels((titleFrame.height - image.size.height) * 0.5) - 1.0, width: image.size.width, height: image.size.height) } else { iconFrame = CGRect(x: width - 16.0, y: 4.0, width: 12.0, height: 12.0) @@ -479,6 +478,18 @@ private final class ChatMessageActionButtonNode: ASDisplayNode { node.accessibilityArea.accessibilityLabel = title node.accessibilityArea.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: 42.0)) + if let buttonView = node.buttonView { + let isEnabled = customInfo?.isEnabled ?? true + if buttonView.isEnabled != isEnabled { + buttonView.isEnabled = isEnabled + + if let backgroundBlurView = node.backgroundBlurView { + backgroundBlurView.view.alpha = isEnabled ? 1.0 : 0.55 + } + node.backgroundContent?.alpha = isEnabled ? 1.0 : 0.55 + } + } + return node }) }) @@ -493,6 +504,16 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { case suggestedPostEdit } + public struct CustomInfo { + var isEnabled: Bool + var icon: CustomIcon? + + public init(isEnabled: Bool, icon: CustomIcon?) { + self.isEnabled = isEnabled + self.icon = icon + } + } + private var buttonNodes: [ChatMessageActionButtonNode] = [] private var buttonPressedWrapper: ((ReplyMarkupButton, Promise) -> Void)? @@ -529,10 +550,10 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { } } - public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customIcons: [MemoryBuffer: CustomIcon], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { + public class func asyncLayout(_ maybeNode: ChatMessageActionButtonsNode?) -> (_ context: AccountContext, _ theme: ChatPresentationThemeData, _ chatBubbleCorners: PresentationChatBubbleCorners, _ strings: PresentationStrings, _ backgroundNode: WallpaperBackgroundNode?, _ replyMarkup: ReplyMarkupMessageAttribute, _ customInfos: [MemoryBuffer: CustomInfo], _ message: Message, _ constrainedWidth: CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (_ animation: ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)) { let currentButtonLayouts = maybeNode?.buttonNodes.map { ChatMessageActionButtonNode.asyncLayout($0) } ?? [] - return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customIcons, message, constrainedWidth in + return { context, theme, chatBubbleCorners, strings, backgroundNode, replyMarkup, customInfos, message, constrainedWidth in let buttonHeight: CGFloat = 42.0 let buttonSpacing: CGFloat = 2.0 @@ -548,9 +569,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { var finalizeRowButtonLayouts: [((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))] = [] var rowButtonIndex = 0 for button in row.buttons { - var customIcon: CustomIcon? + var customInfo: CustomInfo? if case let .callback(_, data) = button.action { - customIcon = customIcons[data] + customInfo = customInfos[data] } let buttonPosition: MessageBubbleActionButtonPosition @@ -570,9 +591,9 @@ public final class ChatMessageActionButtonsNode: ASDisplayNode { let prepareButtonLayout: (minimumWidth: CGFloat, layout: ((CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonNode))) if buttonIndex < currentButtonLayouts.count { - prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition) + prepareButtonLayout = currentButtonLayouts[buttonIndex](context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customInfo, maximumButtonWidth, buttonPosition) } else { - prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customIcon, maximumButtonWidth, buttonPosition) + prepareButtonLayout = ChatMessageActionButtonNode.asyncLayout(nil)(context, theme, chatBubbleCorners, strings, backgroundNode, message, button, customInfo, maximumButtonWidth, buttonPosition) } maximumRowButtonWidth = max(maximumRowButtonWidth, prepareButtonLayout.minimumWidth) diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 8fd93f0bcf..8053656588 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1295,6 +1295,11 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + var canApprove = true + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) { + canApprove = false + } + //TODO:localize var buttonDeclineValue: UInt8 = 0 let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) @@ -1303,10 +1308,19 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { var buttonSuggestChangesValue: UInt8 = 2 let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) - let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [ - buttonDecline: .suggestedPostReject, - buttonApprove: .suggestedPostApprove, - buttonSuggestChanges: .suggestedPostEdit + let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [ + buttonDecline: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: true, + icon: .suggestedPostReject + ), + buttonApprove: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostApprove + ), + buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostEdit + ) ] let (minWidth, buttonsLayout) = actionButtonsLayout( @@ -1327,7 +1341,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { ], flags: [], placeholder: nil - ), customIcons, item.message, baseWidth) + ), customInfos, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index cfd5d507aa..1f786cf66a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -1491,7 +1491,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI threadInfoLayout: (ChatMessageThreadInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageThreadInfoNode), forwardInfoLayout: (AccountContext, ChatPresentationData, PresentationStrings, ChatMessageForwardInfoType, Peer?, String?, String?, ChatMessageForwardInfoNode.StoryData?, CGSize) -> (CGSize, (CGFloat) -> ChatMessageForwardInfoNode), replyInfoLayout: (ChatMessageReplyInfoNode.Arguments) -> (CGSize, (CGSize, Bool, ListViewItemUpdateAnimation) -> ChatMessageReplyInfoNode), - actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), + actionButtonsLayout: (AccountContext, ChatPresentationThemeData, PresentationChatBubbleCorners, PresentationStrings, WallpaperBackgroundNode?, ReplyMarkupMessageAttribute, [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo], Message, CGFloat) -> (minWidth: CGFloat, layout: (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageActionButtonsNode)), reactionButtonsLayout: (ChatMessageReactionButtonsNode.Arguments) -> (minWidth: CGFloat, layout: (CGFloat) -> (size: CGSize, apply: (ListViewItemUpdateAnimation) -> ChatMessageReactionButtonsNode)), unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode), mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode), @@ -2806,6 +2806,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI lastNodeTopPosition = .None(.Both) } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + var canApprove = true + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) { + canApprove = false + } + //TODO:localize var buttonDeclineValue: UInt8 = 0 let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) @@ -2814,10 +2819,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI var buttonSuggestChangesValue: UInt8 = 2 let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) - let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [ - buttonDecline: .suggestedPostReject, - buttonApprove: .suggestedPostApprove, - buttonSuggestChanges: .suggestedPostEdit + let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [ + buttonDecline: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: true, + icon: .suggestedPostReject + ), + buttonApprove: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostApprove + ), + buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostEdit + ) ] let (minWidth, buttonsLayout) = actionButtonsLayout( @@ -2838,7 +2852,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI ], flags: [], placeholder: nil - ), customIcons, item.message, baseWidth) + ), customInfos, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index ad8c362519..8e4d882243 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -856,6 +856,11 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } else if incoming, let attribute = item.message.attributes.first(where: { $0 is SuggestedPostMessageAttribute }) as? SuggestedPostMessageAttribute, attribute.state == nil { + var canApprove = true + if let peer = item.message.peers[item.message.id.peerId] as? TelegramChannel, peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = item.message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect), !mainChannel.hasPermission(.sendSomething) { + canApprove = false + } + //TODO:localize var buttonDeclineValue: UInt8 = 0 let buttonDecline = MemoryBuffer(data: Data(bytes: &buttonDeclineValue, count: 1)) @@ -864,10 +869,19 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { var buttonSuggestChangesValue: UInt8 = 2 let buttonSuggestChanges = MemoryBuffer(data: Data(bytes: &buttonSuggestChangesValue, count: 1)) - let customIcons: [MemoryBuffer: ChatMessageActionButtonsNode.CustomIcon] = [ - buttonDecline: .suggestedPostReject, - buttonApprove: .suggestedPostApprove, - buttonSuggestChanges: .suggestedPostEdit + let customInfos: [MemoryBuffer: ChatMessageActionButtonsNode.CustomInfo] = [ + buttonDecline: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: true, + icon: .suggestedPostReject + ), + buttonApprove: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostApprove + ), + buttonSuggestChanges: ChatMessageActionButtonsNode.CustomInfo( + isEnabled: canApprove, + icon: .suggestedPostEdit + ) ] let (minWidth, buttonsLayout) = actionButtonsLayout( @@ -888,7 +902,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { ], flags: [], placeholder: nil - ), customIcons, item.message, baseWidth) + ), customInfos, item.message, baseWidth) maxContentWidth = max(maxContentWidth, minWidth) actionButtonsFinalize = buttonsLayout } diff --git a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift index 654807dc07..673a9092d4 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSendStarsScreen/Sources/ChatSendStarsScreen.swift @@ -1015,8 +1015,6 @@ private final class ChatSendStarsScreenComponent: Component { private var channelsForPublicReaction: [EnginePeer] = [] private var channelsForPublicReactionDisposable: Disposable? - private var currentSuggestPostTimestamp: Int32? - override init(frame: CGRect) { self.bottomOverscrollLimit = 200.0 @@ -1393,28 +1391,6 @@ private final class ChatSendStarsScreenComponent: Component { controller.presentInGlobalOverlay(contextController) } - private func displaySuggestTimeSelectionMenu(sourceView: UIView) { - guard let component = self.component else { - return - } - guard let environment = self.environment else { - return - } - - let mode: ChatScheduleTimeControllerMode = .suggestPost(needsTime: false) - let theme = environment.theme - let controller = ChatScheduleTimeController(context: component.context, updatedPresentationData: (component.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), component.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: mode, style: .default, currentTime: self.currentSuggestPostTimestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak self] time in - guard let self else { - return - } - self.currentSuggestPostTimestamp = time == 0 ? nil : time - if !self.isUpdating { - self.state?.updated(transition: .immediate) - } - }) - environment.controller()?.present(controller, in: .window(.root)) - } - func update(component: ChatSendStarsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { self.isUpdating = true defer { @@ -1518,9 +1494,6 @@ private final class ChatSendStarsScreenComponent: Component { } } }) - case let .suggestPost(suggestPostData): - self.currentSuggestPostTimestamp = suggestPostData.initialTimestamp - self.amount = Amount(realValue: 50, maxRealValue: 10000, maxSliderValue: 999, isLogarithmic: true) } if let starsContext = component.context.starsContext { @@ -1578,8 +1551,6 @@ private final class ChatSendStarsScreenComponent: Component { switch component.initialData.subjectInitialData { case let .react(reactData): maxAmount = reactData.maxAmount - case let .suggestPost(suggestPostData): - maxAmount = suggestPostData.maxAmount } self.amount = self.amount.withSliderValue(value) @@ -1659,8 +1630,6 @@ private final class ChatSendStarsScreenComponent: Component { } else { self.isPastTopCutoff = nil } - case .suggestPost: - break } let _ = self.sliderBackground.update( @@ -1784,8 +1753,6 @@ private final class ChatSendStarsScreenComponent: Component { transition.setFrame(view: peerSelectorButtonView, frame: peerSelectorButtonFrame) peerSelectorButtonView.isHidden = sendAsPeers.count <= 1 } - case .suggestPost: - break } if themeUpdated { @@ -1837,8 +1804,6 @@ private final class ChatSendStarsScreenComponent: Component { case let .react(reactData): let currentMyPeer = self.currentMyPeer ?? reactData.myPeer subtitleText = environment.strings.SendStarReactions_SubtitleFrom(currentMyPeer.compactDisplayTitle).string - case .suggestPost: - subtitleText = nil } var subtitleSize: CGSize? @@ -1857,9 +1822,6 @@ private final class ChatSendStarsScreenComponent: Component { switch component.initialData.subjectInitialData { case .react: titleText = environment.strings.SendStarReactions_Title - case .suggestPost: - //TODO:localize - titleText = "Suggest a Message" } let titleSize = title.update( @@ -1907,9 +1869,6 @@ private final class ChatSendStarsScreenComponent: Component { } else { text = environment.strings.SendStarReactions_TextGeneric(reactData.peer.debugDisplayTitle).string } - case let .suggestPost(suggestPostData): - //TODO:localize - text = "Choose how many stars you want to offer **\(suggestPostData.peer.compactDisplayTitle)** to publish this message." } let body = MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemPrimaryTextColor) @@ -1943,38 +1902,6 @@ private final class ChatSendStarsScreenComponent: Component { contentHeight += 22.0 contentHeight += 2.0 - if case .suggestPost = component.initialData.subjectInitialData { - contentHeight += 3.0 - - let timeSelectorButtonSize = self.timeSelectorButton.update( - transition: transition, - component: AnyComponent(TimeSelectorBadgeComponent( - context: component.context, - theme: environment.theme, - strings: environment.strings, - timestamp: self.currentSuggestPostTimestamp, - action: { [weak self] sourceView in - guard let self else { - return - } - self.displaySuggestTimeSelectionMenu(sourceView: sourceView) - } - )), - environment: {}, - containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 100.0) - ) - let timeSelectorButtonFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - timeSelectorButtonSize.width) * 0.5), y: contentHeight), size: timeSelectorButtonSize) - if let timeSelectorButtonView = self.timeSelectorButton.view { - if timeSelectorButtonView.superview == nil { - self.navigationBarContainer.addSubview(timeSelectorButtonView) - } - transition.setFrame(view: timeSelectorButtonView, frame: timeSelectorButtonFrame) - } - contentHeight += timeSelectorButtonSize.height - - contentHeight += 32.0 - } - switch component.initialData.subjectInitialData { case let .react(reactData): if !reactData.topPeers.isEmpty { @@ -2307,8 +2234,6 @@ private final class ChatSendStarsScreenComponent: Component { } contentHeight += anonymousContentsSize.height + 27.0 - case .suggestPost: - break } initialContentHeight = contentHeight @@ -2321,8 +2246,6 @@ private final class ChatSendStarsScreenComponent: Component { switch component.initialData.subjectInitialData { case .react: buttonString = environment.strings.SendStarReactions_SendButtonTitle("\(self.amount.realValue)").string - case .suggestPost: - buttonString = "Offer # \(self.amount.realValue)" } let buttonAttributedString = NSMutableAttributedString(string: buttonString, font: Font.semibold(17.0), textColor: .white, paragraphAlignment: .center) if let range = buttonAttributedString.string.range(of: "#"), let starImage = self.cachedStarImage?.0 { @@ -2369,9 +2292,6 @@ private final class ChatSendStarsScreenComponent: Component { switch component.initialData.subjectInitialData { case let .react(reactData): purchasePurpose = .reactions(peerId: reactData.peer.id, requiredStars: Int64(self.amount.realValue)) - case let .suggestPost(suggestPost): - //TODO:release - purchasePurpose = .reactions(peerId: suggestPost.peer.id, requiredStars: Int64(self.amount.realValue)) } let purchaseScreen = component.context.sharedContext.makeStarsPurchaseScreen(context: component.context, starsContext: starsContext, options: options, purpose: purchasePurpose, completion: { result in @@ -2415,8 +2335,6 @@ private final class ChatSendStarsScreenComponent: Component { sourceView: badgeView.badgeIcon ) ) - case let .suggestPost(suggestPostData): - suggestPostData.completion(Int64(self.amount.realValue), self.currentSuggestPostTimestamp) } self.environment?.controller()?.dismiss() } @@ -2560,22 +2478,7 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { } } - class SuggestPost { - let peer: EnginePeer - let initialTimestamp: Int32? - let maxAmount: Int - let completion: (Int64, Int32?) -> Void - - init(peer: EnginePeer, initialTimestamp: Int32?, maxAmount: Int, completion: @escaping (Int64, Int32?) -> Void) { - self.peer = peer - self.initialTimestamp = initialTimestamp - self.maxAmount = maxAmount - self.completion = completion - } - } - case react(React) - case suggestPost(SuggestPost) } public final class InitialData { @@ -2828,46 +2731,6 @@ public class ChatSendStarsScreen: ViewControllerComponentContainer { } } - public static func initialData(context: AccountContext, peerId: EnginePeer.Id, suggestMessageAmount: StarsAmount, completion: @escaping (Int64, Int32?) -> Void) -> Signal { - let balance: Signal - if let starsContext = context.starsContext { - balance = starsContext.state - |> map { state in - return state?.balance - } - |> take(1) - } else { - balance = .single(nil) - } - - var maxAmount = 2500 - if let data = context.currentAppConfiguration.with({ $0 }).data, let value = data["stars_suggest_post_amount_max"] as? Double { - maxAmount = Int(value) - } - - return combineLatest( - context.engine.data.get( - TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) - ), - balance - ) - |> map { peer, balance -> InitialData? in - guard let peer else { - return nil - } - - return InitialData( - subjectInitialData: .suggestPost(SubjectInitialData.SuggestPost( - peer: peer, - initialTimestamp: nil, - maxAmount: maxAmount, - completion: completion - )), - balance: balance - ) - } - } - override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 5f6153be25..23c08659a0 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -24,6 +24,7 @@ import UndoUI import ListActionItemComponent import ChatScheduleTimeController import TabSelectorComponent +import PresentationDataUtils private let amountTag = GenericComponentViewTag() @@ -83,8 +84,15 @@ private final class SheetContent: CombinedComponent { let constrainedTitleWidth = context.availableSize.width - 16.0 * 2.0 if case let .suggestedPost(mode, _, _, _) = component.mode { + var displayBalance = false switch mode { - case .sender: + case let .sender(_, isFromAdmin): + displayBalance = !isFromAdmin + case .admin: + break + } + + if displayBalance { let balance = balance.update( component: BalanceComponent( context: component.context, @@ -102,8 +110,6 @@ private final class SheetContent: CombinedComponent { .anchorPoint(CGPoint(x: 1.0, y: 0.0)) .position(CGPoint(x: balanceFrame.maxX, y: balanceFrame.minY)) ) - case .admin: - break } let closeButton = closeButton.update( @@ -289,7 +295,11 @@ private final class SheetContent: CombinedComponent { ) } - if case let .suggestedPost(mode, _, _, _) = component.mode { + var tonBalanceValue: StarsAmount = .zero + if let tonBalance = state.tonBalance { + tonBalanceValue = tonBalance + } + if case let .suggestedPost(mode, _, _, _) = component.mode, (state.currency == .ton || tonBalanceValue > StarsAmount.zero) { //TODO:localize let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) let starsTitle: String @@ -432,14 +442,23 @@ private final class SheetContent: CombinedComponent { )) case let .suggestedPost(mode, _, _, _): switch mode { - case let .sender(channel): + case let .sender(channel, isFromAdmin): //TODO:localize 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." + if isFromAdmin { + 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." + } + } else { + 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( @@ -595,7 +614,9 @@ private final class SheetContent: CombinedComponent { let component = state.component let theme = environment.theme - let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in + + let minimalTime: Int32 = Int32(Date().timeIntervalSince1970) + 5 * 60 + 10 + let controller = ChatScheduleTimeController(context: state.context, updatedPresentationData: (state.context.sharedContext.currentPresentationData.with({ $0 }).withUpdated(theme: theme), state.context.sharedContext.presentationData |> map { $0.withUpdated(theme: theme) }), mode: .suggestPost(needsTime: false), style: .default, currentTime: state.timestamp, minimalTime: minimalTime, dismissByTapOutside: true, completion: { [weak state] time in guard let state else { return } @@ -729,6 +750,36 @@ private final class SheetContent: CombinedComponent { case let .paidMessages(_, _, _, _, completion): completion(amount.value) case let .suggestedPost(_, _, _, completion): + switch state.currency { + case .stars: + if let balance = state.starsBalance, amount > balance { + guard let starsContext = state.context.starsContext else { + return + } + let _ = (state.context.engine.payments.starsTopUpOptions() + |> take(1) + |> deliverOnMainQueue).startStandalone(next: { [weak controller, weak state] options in + guard let controller, let state else { + return + } + let purchaseController = state.context.sharedContext.makeStarsPurchaseScreen(context: state.context, starsContext: starsContext, options: options, purpose: .generic, completion: { _ in + }) + controller.push(purchaseController) + }) + + return + } + case .ton: + if let balance = state.tonBalance, amount > balance { + //TODO:localize + let presentationData = state.context.sharedContext.currentPresentationData.with { $0 } + controller.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: presentationData), title: nil, text: "Not enough TON", actions: [ + TextAlertAction(type: .defaultAction, title: strings.Common_OK, action: {}) + ]), in: .window(.root)) + return + } + } + completion(CurrencyAmount(amount: amount, currency: state.currency), state.timestamp) } @@ -971,7 +1022,7 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { public final class StarsWithdrawScreen: ViewControllerComponentContainer { public enum Mode { public enum SuggestedPostMode { - case sender(channel: EnginePeer) + case sender(channel: EnginePeer, isFromAdmin: Bool) case admin } @@ -1057,322 +1108,6 @@ public final class StarsWithdrawScreen: ViewControllerComponentContainer { } } -private let invalidAmountCharacters = CharacterSet.decimalDigits.inverted - -private final class AmountFieldTonFormatter: NSObject, UITextFieldDelegate { - private struct Representation { - private let format: CurrencyFormat - private var caretIndex: Int = 0 - private var wholePart: [Int] = [] - private var decimalPart: [Int] = [] - - init(string: String, format: CurrencyFormat) { - self.format = format - - var isDecimalPart = false - for c in string { - if c.isNumber { - if let value = Int(String(c)) { - if isDecimalPart { - self.decimalPart.append(value) - } else { - self.wholePart.append(value) - } - } - } else if String(c) == format.decimalSeparator { - isDecimalPart = true - } - } - - while self.wholePart.count > 1 { - if self.wholePart[0] != 0 { - break - } else { - self.wholePart.removeFirst() - } - } - if self.wholePart.isEmpty { - self.wholePart = [0] - } - - while self.decimalPart.count > 1 { - if self.decimalPart[self.decimalPart.count - 1] != 0 { - break - } else { - self.decimalPart.removeLast() - } - } - while self.decimalPart.count < format.decimalDigits { - self.decimalPart.append(0) - } - - self.caretIndex = self.wholePart.count - } - - var minCaretIndex: Int { - for i in 0 ..< self.wholePart.count { - if self.wholePart[i] != 0 { - return i - } - } - return self.wholePart.count - } - - mutating func moveCaret(offset: Int) { - self.caretIndex = max(self.minCaretIndex, min(self.caretIndex + offset, self.wholePart.count + self.decimalPart.count)) - } - - mutating func normalize() { - while self.wholePart.count > 1 { - if self.wholePart[0] != 0 { - break - } else { - self.wholePart.removeFirst() - self.moveCaret(offset: -1) - } - } - if self.wholePart.isEmpty { - self.wholePart = [0] - } - - while self.decimalPart.count < format.decimalDigits { - self.decimalPart.append(0) - } - while self.decimalPart.count > format.decimalDigits { - self.decimalPart.removeLast() - } - - self.caretIndex = max(self.minCaretIndex, min(self.caretIndex, self.wholePart.count + self.decimalPart.count)) - } - - mutating func backspace() { - if self.caretIndex > self.wholePart.count { - let decimalIndex = self.caretIndex - self.wholePart.count - if decimalIndex > 0 { - self.decimalPart.remove(at: decimalIndex - 1) - - self.moveCaret(offset: -1) - self.normalize() - } - } else { - if self.caretIndex > 0 { - self.wholePart.remove(at: self.caretIndex - 1) - - self.moveCaret(offset: -1) - self.normalize() - } - } - } - - mutating func insert(letter: String) { - if letter == "." || letter == "," { - if self.caretIndex == self.wholePart.count { - return - } else if self.caretIndex < self.wholePart.count { - for i in (self.caretIndex ..< self.wholePart.count).reversed() { - self.decimalPart.insert(self.wholePart[i], at: 0) - self.wholePart.remove(at: i) - } - } - - self.normalize() - } else if letter.count == 1 && letter[letter.startIndex].isNumber { - if let value = Int(letter) { - if self.caretIndex <= self.wholePart.count { - self.wholePart.insert(value, at: self.caretIndex) - } else { - let decimalIndex = self.caretIndex - self.wholePart.count - self.decimalPart.insert(value, at: decimalIndex) - } - self.moveCaret(offset: 1) - self.normalize() - } - } - } - - var string: String { - var result = "" - - for digit in self.wholePart { - result.append("\(digit)") - } - result.append(self.format.decimalSeparator) - for digit in self.decimalPart { - result.append("\(digit)") - } - - return result - } - - var stringCaretIndex: Int { - var logicalIndex = 0 - var resolvedIndex = 0 - - if logicalIndex == self.caretIndex { - return resolvedIndex - } - - for _ in self.wholePart { - logicalIndex += 1 - resolvedIndex += 1 - - if logicalIndex == self.caretIndex { - return resolvedIndex - } - } - - resolvedIndex += 1 - - for _ in self.decimalPart { - logicalIndex += 1 - resolvedIndex += 1 - - if logicalIndex == self.caretIndex { - return resolvedIndex - } - } - - return resolvedIndex - } - - var numericalValue: Int64 { - var result: Int64 = 0 - - for digit in self.wholePart { - result *= 10 - result += Int64(digit) - } - for digit in self.decimalPart { - result *= 10 - result += Int64(digit) - } - - return result - } - } - - private let format: CurrencyFormat - private let currency: String - private let maxNumericalValue: Int64 - private let updated: (Int64) -> Void - private let isEmptyUpdated: (Bool) -> Void - private let focusUpdated: (Bool) -> Void - - private var representation: Representation - - private var previousResolvedCaretIndex: Int = 0 - private var ignoreTextSelection: Bool = false - private var enableTextSelectionProcessing: Bool = false - - init?(textField: UITextField, currency: String, maxNumericalValue: Int64, initialValue: String, updated: @escaping (Int64) -> Void, isEmptyUpdated: @escaping (Bool) -> Void, focusUpdated: @escaping (Bool) -> Void) { - guard let format = CurrencyFormat(currency: currency) else { - return nil - } - self.format = format - self.currency = currency - self.maxNumericalValue = maxNumericalValue - self.updated = updated - self.isEmptyUpdated = isEmptyUpdated - self.focusUpdated = focusUpdated - - self.representation = Representation(string: initialValue, format: format) - - super.init() - - textField.text = self.representation.string - self.previousResolvedCaretIndex = self.representation.stringCaretIndex - - self.isEmptyUpdated(false) - } - - func reset(textField: UITextField, initialValue: String) { - self.representation = Representation(string: initialValue, format: self.format) - self.resetFromRepresentation(textField: textField, notifyUpdated: false) - } - - private func resetFromRepresentation(textField: UITextField, notifyUpdated: Bool) { - self.ignoreTextSelection = true - - if self.representation.numericalValue > self.maxNumericalValue { - self.representation = Representation(string: formatCurrencyAmountCustom(self.maxNumericalValue, currency: self.currency).0, format: self.format) - } - - textField.text = self.representation.string - self.previousResolvedCaretIndex = self.representation.stringCaretIndex - - if self.enableTextSelectionProcessing { - let stringCaretIndex = self.representation.stringCaretIndex - if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) { - textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition) - } - } - self.ignoreTextSelection = false - - if notifyUpdated { - self.updated(self.representation.numericalValue) - } - } - - @objc public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { - if string.count == 1 { - self.representation.insert(letter: string) - self.resetFromRepresentation(textField: textField, notifyUpdated: true) - } else if string.count == 0 { - self.representation.backspace() - self.resetFromRepresentation(textField: textField, notifyUpdated: true) - } - - return false - } - - @objc public func textFieldShouldReturn(_ textField: UITextField) -> Bool { - return false - } - - @objc public func textFieldDidBeginEditing(_ textField: UITextField) { - self.enableTextSelectionProcessing = true - self.focusUpdated(true) - - let stringCaretIndex = self.representation.stringCaretIndex - self.previousResolvedCaretIndex = stringCaretIndex - if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) { - self.ignoreTextSelection = true - textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition) - DispatchQueue.main.async { - textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition) - self.ignoreTextSelection = false - } - } - } - - @objc public func textFieldDidChangeSelection(_ textField: UITextField) { - if self.ignoreTextSelection { - return - } - if !self.enableTextSelectionProcessing { - return - } - - if let selectedTextRange = textField.selectedTextRange { - let index = textField.offset(from: textField.beginningOfDocument, to: selectedTextRange.end) - if self.previousResolvedCaretIndex != index { - self.representation.moveCaret(offset: self.previousResolvedCaretIndex < index ? 1 : -1) - - let stringCaretIndex = self.representation.stringCaretIndex - self.previousResolvedCaretIndex = stringCaretIndex - if let caretPosition = textField.position(from: textField.beginningOfDocument, offset: stringCaretIndex) { - textField.selectedTextRange = textField.textRange(from: caretPosition, to: caretPosition) - } - } - } - } - - @objc public func textFieldDidEndEditing(_ textField: UITextField) { - self.enableTextSelectionProcessing = false - self.focusUpdated(false) - } -} - private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { private let currency: CurrencyAmount.Currency private let dateTimeFormat: PresentationDateTimeFormat @@ -1479,8 +1214,21 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { return false } case .ton: + var fixedText = false + if let index = newText.firstIndex(of: ".") { + let fractionalString = newText[newText.index(after: index)...] + if fractionalString.count > 2 { + newText = String(newText[newText.startIndex ..< newText.index(index, offsetBy: 3)]) + fixedText = true + } + } + if (newText == "0" && !acceptZero) || (newText.count > 1 && newText.hasPrefix("0") && !newText.hasPrefix("0.")) { newText.removeFirst() + fixedText = true + } + + if fixedText { textField.text = newText self.onTextChanged(text: newText) return false @@ -1705,7 +1453,6 @@ private final class AmountFieldComponent: Component { } self.tonFormatter = nil self.textField.delegate = self.starsFormatter - self.textField.text = "" case .ton: self.textField.keyboardType = .numbersAndPunctuation if self.tonFormatter == nil { diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 6d9a1c7d34..4db78b786f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -1011,8 +1011,15 @@ extension ChatControllerImpl { }) }) } else { + var isFromAdmin = false + if let channel = self.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum { + if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = self.presentationInterfaceState.renderedPeer?.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isFromAdmin = true + } + } subject = .postSuggestion( channel: .channel(channel), + isFromAdmin: isFromAdmin, current: postSuggestionState.price ?? CurrencyAmount(amount: .zero, currency: .stars), timestamp: postSuggestionState.timestamp, completion: { [weak self] price, timestamp in diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 0569f1ebc1..9567217139 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -233,7 +233,7 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte } } - if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.manageDirect) { + if let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, (!mainChannel.hasPermission(.manageDirect) || chatPresentationInterfaceState.chatLocation.threadId != nil) { if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil { accessoryItems.append(.suggestPost) } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c39b3ac5d2..91f821e5a3 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3734,8 +3734,8 @@ 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, timestamp: timestamp, completion: completion) + case let .postSuggestion(channel, isFromAdmin, current, timestamp, completion): + mode = .suggestedPost(mode: .sender(channel: channel, isFromAdmin: isFromAdmin), price: current, timestamp: timestamp, completion: completion) case let .postSuggestionModification(current, timestamp, completion): mode = .suggestedPost(mode: .admin, price: current, timestamp: timestamp, completion: completion) } From a78224b9653249e85aa37dcaacb6ac7d6ff6b94a Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 27 Jun 2025 00:15:15 +0200 Subject: [PATCH 4/7] Add scheduled messages post alert --- .../Sources/ChatControllerAdminBanUsers.swift | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift index a7280cfbe2..cf28fe7d9a 100644 --- a/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift +++ b/submodules/TelegramUI/Sources/ChatControllerAdminBanUsers.swift @@ -389,6 +389,47 @@ extension ChatControllerImpl { return } + if let message = messages.values.compactMap({ $0 }).first(where: { message in message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) }), let attribute = message.attributes.first(where: { $0 is PublishedSuggestedPostMessageAttribute }) as? PublishedSuggestedPostMessageAttribute { + let commit = { [weak self] in + guard let self else { + return + } + //TODO:localize + let titleString: String + let textString: String + switch attribute.currency { + case .stars: + titleString = "Stars Will Be Lost" + textString = "You won't receive **Stars** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication." + case .ton: + titleString = "TON Will Be Lost" + textString = "You won't receive **TON** for this post if you delete it now. The post must remain visible for at least **24 hours** after publication." + } + self.present(standardTextAlertController( + theme: AlertControllerTheme(presentationData: self.presentationData), + title: titleString, + text: textString, + actions: [ + TextAlertAction(type: .destructiveAction, title: "Delete Anyway", action: { [weak self] in + guard let self else { + return + } + self.beginDeleteMessagesWithUndo(messageIds: messageIds, type: .forEveryone) + }), + TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_Cancel, action: {}) + ], + actionLayout: .vertical, + parseMarkdown: true + ), in: .window(.root)) + } + if let contextController { + contextController.dismiss(completion: commit) + } else { + commit() + } + return + } + let actionSheet = ActionSheetController(presentationData: self.presentationData) var items: [ActionSheetItem] = [] var personalPeerName: String? From fd324106e372e511f7a0bc1ed47ec3e6e98f34e2 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 27 Jun 2025 09:58:46 +0200 Subject: [PATCH 5/7] Various improvements --- .../Sources/ChatListController.swift | 3 +- .../Sources/Node/ChatListItem.swift | 9 +- .../Resources/PresentationResourceKey.swift | 1 + .../PresentationResourcesChatList.swift | 6 ++ .../Sources/ChatSideTopicsPanel.swift | 19 ++-- .../ChatTitleView/Sources/ChatTitleView.swift | 26 ++++-- .../Sources/EmojiStatusComponent.swift | 93 ++++++------------- .../TelegramUI/Sources/ChatController.swift | 5 +- .../Sources/ChatControllerContentData.swift | 16 +++- .../Sources/ChatControllerNode.swift | 4 +- .../Sources/NavigateToChatController.swift | 12 ++- 11 files changed, 103 insertions(+), 91 deletions(-) diff --git a/submodules/ChatListUI/Sources/ChatListController.swift b/submodules/ChatListUI/Sources/ChatListController.swift index 0b9865c4a6..858ccf4ab0 100644 --- a/submodules/ChatListUI/Sources/ChatListController.swift +++ b/submodules/ChatListUI/Sources/ChatListController.swift @@ -3797,6 +3797,7 @@ public class ChatListControllerImpl: TelegramBaseController, ChatListController controller.completion = { [weak controller] title, fileId, iconColor, _ in controller?.isInProgress = true + controller?.view.endEditing(true) let _ = (context.engine.peers.createForumChannelTopic(id: peerId, title: title, iconColor: iconColor, iconFileId: fileId) |> deliverOnMainQueue).startStandalone(next: { topicId in @@ -7127,7 +7128,7 @@ private final class ChatListLocationContext { strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, - content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), + content: .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true), tapped: { [weak self] in guard let self else { return diff --git a/submodules/ChatListUI/Sources/Node/ChatListItem.swift b/submodules/ChatListUI/Sources/Node/ChatListItem.swift index f04e0edc59..ad74d8d1b4 100644 --- a/submodules/ChatListUI/Sources/Node/ChatListItem.swift +++ b/submodules/ChatListUI/Sources/Node/ChatListItem.swift @@ -3220,7 +3220,14 @@ public class ChatListItemNode: ItemListRevealOptionsItemNode { if displayAsMessage { switch item.content { case let .peer(peerData): - if let peer = peerData.messages.last?.author { + var iconPeer: EnginePeer? + if case let .chat(itemPeer) = contentPeer, let peer = itemPeer.chatOrMonoforumMainPeer { + iconPeer = peer + } else { + iconPeer = peerData.messages.last?.author + } + + if let peer = iconPeer { if case let .peer(peerData) = item.content, peerData.customMessageListData != nil { currentCredibilityIconContent = nil } else if case .savedMessagesChats = item.chatListLocation, peer.id == item.context.account.peerId { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 49b197b86f..caeea7202f 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -127,6 +127,7 @@ public enum PresentationResourceKey: Int32 { case chatListLocationIcon case chatListGeneralTopicIcon + case chatListGeneralTopicTemplateIcon case chatListGeneralTopicSmallIcon case searchAdIcon diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift index f281093841..a152ad6a35 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChatList.swift @@ -445,6 +445,12 @@ public struct PresentationResourcesChatList { }) } + public static func generalTopicTemplateIcon(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatListGeneralTopicTemplateIcon.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat List/GeneralTopicIcon"), color: .white)?.withRenderingMode(.alwaysTemplate) + }) + } + public static func statusAutoremoveIcon(_ theme: PresentationTheme, isActive: Bool) -> UIImage? { return theme.image(PresentationResourceParameterKey.statusAutoremoveIcon(isActive: isActive), { theme in return generateTintedImage(image: UIImage(bundleImageName: isActive ? "Chat List/StatusIconAutoremoveOn" : "Chat List/StatusIconAutoremoveOff"), color: isActive ? theme.list.itemAccentColor : theme.list.itemSecondaryTextColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift index a30ed226fb..f2f63e0eac 100644 --- a/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift +++ b/submodules/TelegramUI/Components/Chat/ChatSideTopicsPanel/Sources/ChatSideTopicsPanel.swift @@ -360,6 +360,7 @@ public final class ChatSideTopicsPanel: Component { } func update(component: VerticalItemComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { + let previousComponent = self.component self.component = component self.tapRecognizer?.isEnabled = component.action != nil @@ -381,12 +382,12 @@ public final class ChatSideTopicsPanel: Component { if case let .forum(topicId) = component.item.item.id { if topicId != 1, let threadData = component.item.item.threadData { if let fileId = threadData.info.icon, fileId != 0 { - avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(0)) + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor, loopMode: .count(0)) } else { avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize) } } else { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(component.theme), tintColor: component.theme.rootController.navigationBar.secondaryTextColor) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor) } } @@ -406,8 +407,14 @@ public final class ChatSideTopicsPanel: Component { icon = ComponentView() self.icon = icon } + + var iconTransition = transition + if iconTransition.animation.isImmediate, let previousComponent, previousComponent.isSelected != component.isSelected { + iconTransition = .easeInOut(duration: 0.2) + } + let _ = icon.update( - transition: .immediate, + transition: iconTransition, component: AnyComponent(avatarIconComponent), environment: {}, containerSize: iconSize @@ -813,12 +820,12 @@ public final class ChatSideTopicsPanel: Component { if case let .forum(topicId) = component.item.item.id { if topicId != 1, let threadData = component.item.item.threadData { if let fileId = threadData.info.icon, fileId != 0 { - avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.theme.list.itemAccentColor, loopMode: .count(0)) + avatarIconContent = .animation(content: .customEmoji(fileId: fileId), size: iconSize, placeholderColor: component.theme.list.mediaPlaceholderColor, themeColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor, loopMode: .count(0)) } else { avatarIconContent = .topic(title: String(threadData.info.title.prefix(1)), color: threadData.info.iconColor, size: iconSize) } } else { - avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicIcon(component.theme), tintColor: component.theme.rootController.navigationBar.secondaryTextColor) + avatarIconContent = .image(image: PresentationResourcesChatList.generalTopicTemplateIcon(component.theme), tintColor: component.isSelected ? component.theme.rootController.navigationBar.accentTextColor : component.theme.rootController.navigationBar.controlColor) } } @@ -1845,7 +1852,7 @@ public final class ChatSideTopicsPanel: Component { case .side: scrollSize = CGSize(width: availableSize.width, height: availableSize.height - directionContainerInset) scrollFrame = CGRect(origin: CGPoint(x: 0.0, y: directionContainerInset), size: scrollSize) - listContentInsets = UIEdgeInsets(top: 8.0, left: 0.0, bottom: 8.0, right: 0.0) + listContentInsets = UIEdgeInsets(top: 8.0 + environment.insets.top, left: 0.0, bottom: 8.0 + environment.insets.bottom, right: 0.0) case .top: scrollSize = CGSize(width: availableSize.width - directionContainerInset, height: availableSize.height) scrollFrame = CGRect(origin: CGPoint(x: directionContainerInset, y: 0.0), size: scrollSize) diff --git a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift index 4c39ade9f7..d1226299bd 100644 --- a/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift +++ b/submodules/TelegramUI/Components/ChatTitleView/Sources/ChatTitleView.swift @@ -92,20 +92,23 @@ public enum ChatTitleContent: Equatable { case replies } - case peer(peerView: PeerData, customTitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) + case peer(peerView: PeerData, customTitle: String?, customSubtitle: String?, onlineMemberCount: (total: Int32?, recent: Int32?), isScheduledMessages: Bool, isMuted: Bool?, customMessageCount: Int?, isEnabled: Bool) case replyThread(type: ReplyThreadType, count: Int) case custom(String, String?, Bool) public static func ==(lhs: ChatTitleContent, rhs: ChatTitleContent) -> Bool { switch lhs { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled): - if case let .peer(rhsPeerView, rhsCustomTitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs { + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, isMuted, customMessageCount, isEnabled): + if case let .peer(rhsPeerView, rhsCustomTitle, rhsCustomSubtitle, rhsOnlineMemberCount, rhsIsScheduledMessages, rhsIsMuted, rhsCustomMessageCount, rhsIsEnabled) = rhs { if peerView != rhsPeerView { return false } if customTitle != rhsCustomTitle { return false } + if customSubtitle != rhsCustomSubtitle { + return false + } if onlineMemberCount.0 != rhsOnlineMemberCount.0 || onlineMemberCount.1 != rhsOnlineMemberCount.1 { return false } @@ -246,7 +249,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var titleStatusIcon: ChatTitleCredibilityIcon = .none var isEnabled = true switch titleContent { - case let .peer(peerView, customTitle, _, isScheduledMessages, isMuted, _, isEnabledValue): + case let .peer(peerView, customTitle, _, _, isScheduledMessages, isMuted, _, isEnabledValue): if peerView.peerId.isReplies { let typeText: String = self.strings.DialogList_Replies segments = [.text(0, NSAttributedString(string: typeText, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] @@ -260,7 +263,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { isEnabled = false } else { if let peer = peerView.peer { - if let customTitle = customTitle { + if let customTitle { segments = [.text(0, NSAttributedString(string: customTitle, font: titleFont, textColor: titleTheme.rootController.navigationBar.primaryTextColor))] } else if peerView.peerId == self.context.account.peerId { if peerView.isSavedMessages { @@ -444,8 +447,8 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var enableAnimation = false switch titleContent { - case let .peer(_, customTitle, _, _, _, _, _): - if case let .peer(_, previousCustomTitle, _, _, _, _, _) = oldValue { + case let .peer(_, customTitle, _, _, _, _, _, _): + if case let .peer(_, previousCustomTitle, _, _, _, _, _, _) = oldValue { if customTitle != previousCustomTitle { enableAnimation = false } @@ -471,7 +474,7 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { var inputActivitiesAllowed = true if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, _, _, isScheduledMessages, _, _, _): + case let .peer(peerView, _, _, _, isScheduledMessages, _, _, _): if let peer = peerView.peer { if peer.id == self.context.account.peerId || isScheduledMessages || peer.id.isRepliesOrVerificationCodes { inputActivitiesAllowed = false @@ -572,8 +575,11 @@ public final class ChatTitleView: UIView, NavigationBarTitleView { } else { if let titleContent = self.titleContent { switch titleContent { - case let .peer(peerView, customTitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): - if let customMessageCount = customMessageCount, customMessageCount != 0 { + case let .peer(peerView, customTitle, customSubtitle, onlineMemberCount, isScheduledMessages, _, customMessageCount, _): + if let customSubtitle { + let string = NSAttributedString(string: customSubtitle, font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) + state = .info(string, .generic) + } else if let customMessageCount = customMessageCount, customMessageCount != 0 { let string = NSAttributedString(string: self.strings.Conversation_Messages(Int32(customMessageCount)), font: subtitleFont, textColor: titleTheme.rootController.navigationBar.secondaryTextColor) state = .info(string, .generic) } else if let peer = peerView.peer { diff --git a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift index aa6e7296df..2a3ce4872f 100644 --- a/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift +++ b/submodules/TelegramUI/Components/EmojiStatusComponent/Sources/EmojiStatusComponent.swift @@ -233,7 +233,10 @@ public final class EmojiStatusComponent: Component { private weak var state: EmptyComponentState? private var component: EmojiStatusComponent? private var starsLayer: StarsEffectLayer? - private var iconView: UIImageView? + + private var iconLayer: SimpleLayer? + private var iconLayerImage: UIImage? + private var animationLayer: InlineStickerItemLayer? private var lottieAnimationView: AnimationView? private let hierarchyTrackingLayer: HierarchyTrackingLayer @@ -323,7 +326,7 @@ public final class EmojiStatusComponent: Component { case let .premium(color): iconTintColor = color - if case .premium = self.component?.content, let image = self.iconView?.image { + if case .premium = self.component?.content, let image = self.iconLayerImage { iconImage = image } else { if let sourceImage = UIImage(bundleImageName: "Chat/Input/Media/EntityInputPremiumIcon") { @@ -454,7 +457,7 @@ public final class EmojiStatusComponent: Component { } } } else { - iconImage = self.iconView?.image + iconImage = self.iconLayerImage if case let .animation(animationContent, size, placeholderColor, themeColor, loopMode) = component.content { emojiFileId = animationContent.fileId.id emojiPlaceholderColor = placeholderColor @@ -471,31 +474,28 @@ public final class EmojiStatusComponent: Component { var size = CGSize() if let iconImage = iconImage { - let iconView: UIImageView - if let current = self.iconView { - iconView = current + let iconLayer: SimpleLayer + if let current = self.iconLayer { + iconLayer = current } else { - iconView = UIImageView() - self.iconView = iconView - self.addSubview(iconView) + iconLayer = SimpleLayer() + self.iconLayer = iconLayer + self.layer.addSublayer(iconLayer) if !transition.animation.isImmediate { - iconView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) - iconView.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) + iconLayer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + iconLayer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) } } - if iconView.image !== iconImage { - iconView.image = iconImage + if self.iconLayerImage !== iconImage { + self.iconLayerImage = iconImage + iconLayer.contents = iconImage.cgImage } if let iconTintColor { - if transition.animation.isImmediate { - iconView.tintColor = iconTintColor - } else { - transition.setTintColor(view: iconView, color: iconTintColor) - } + transition.setTintColor(layer: iconLayer, color: iconTintColor) } else { - iconView.tintColor = nil + iconLayer.layerTintColor = nil } var useFit = false @@ -509,24 +509,25 @@ public final class EmojiStatusComponent: Component { } if useFit { size = CGSize(width: iconImage.size.width, height: availableSize.height) - iconView.frame = CGRect(origin: CGPoint(x: floor((size.width - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) + iconLayer.frame = CGRect(origin: CGPoint(x: floor((size.width - iconImage.size.width) / 2.0), y: floor((size.height - iconImage.size.height) / 2.0)), size: iconImage.size) } else { size = iconImage.size.aspectFilled(availableSize) - iconView.frame = CGRect(origin: CGPoint(), size: size) + iconLayer.frame = CGRect(origin: CGPoint(), size: size) } } else { - if let iconView = self.iconView { - self.iconView = nil + if let iconLayer = self.iconLayer { + self.iconLayer = nil if !transition.animation.isImmediate { - iconView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak iconView] _ in - iconView?.removeFromSuperview() + iconLayer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak iconLayer] _ in + iconLayer?.removeFromSuperlayer() }) - iconView.layer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) + iconLayer.animateScale(from: 1.0, to: 0.01, duration: 0.2, removeOnCompletion: false) } else { - iconView.removeFromSuperview() + iconLayer.removeFromSuperlayer() } } + self.iconLayerImage = nil } let emojiFileUpdated = component.emojiFileUpdated @@ -607,44 +608,6 @@ public final class EmojiStatusComponent: Component { animationLayer.frame = CGRect(origin: CGPoint(), size: size) animationLayer.isVisibleForAnimations = component.isVisibleForAnimations - /*} else { - if self.emojiFileDataPathDisposable == nil { - let account = component.context.account - self.emojiFileDataPathDisposable = (Signal { subscriber in - let disposable = MetaDisposable() - - let _ = (account.postbox.mediaBox.resourceData(emojiFile.resource) - |> take(1)).start(next: { firstAttemptData in - if firstAttemptData.complete { - subscriber.putNext(AnimationFileProperties.load(from: firstAttemptData.path)) - subscriber.putCompletion() - } else { - let fetchDisposable = freeMediaFileInteractiveFetched(account: account, fileReference: .standalone(media: emojiFile)).start() - let dataDisposable = account.postbox.mediaBox.resourceData(emojiFile.resource).start(next: { data in - if data.complete { - subscriber.putNext(AnimationFileProperties.load(from: data.path)) - subscriber.putCompletion() - } - }) - - disposable.set(ActionDisposable { - fetchDisposable.dispose() - dataDisposable.dispose() - }) - } - }) - - return disposable - } - |> deliverOnMainQueue).start(next: { [weak self] properties in - guard let strongSelf = self else { - return - } - strongSelf.emojiFileDataProperties = properties - strongSelf.state?.updated(transition: transition) - }) - } - }*/ } else { if self.emojiFileDisposable == nil { self.emojiFileDisposable = (component.resolveInlineStickers([emojiFileId]) diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 5183326007..bd1148b6d0 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -967,7 +967,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G return false } switch action.action { - case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText, .todoCompletions, .todoAppendTasks: + case .pinnedMessageUpdated, .gameScore, .setSameChatWallpaper, .giveawayResults, .customText, .todoCompletions, .todoAppendTasks, .suggestedPostRefund, .suggestedPostSuccess, .suggestedPostApprovalStatus: for attribute in message.attributes { if let attribute = attribute as? ReplyMessageAttribute { var todoTaskId: Int32? @@ -1244,6 +1244,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G case .boostsApplied: self.controllerInteraction?.openGroupBoostInfo(nil, 0) return true + case .paidMessagesPriceEdited: + self.interfaceInteraction?.openMonoforum() + return true default: break } diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index c7c8fe80d9..ebf5cdc9d3 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -548,12 +548,20 @@ extension ChatControllerImpl { strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false) } else if let channel = peer as? TelegramChannel, channel.isMonoForum { if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] { - strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, strings.Chat_Monoforum_Subtitle, true) + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData( + peerId: mainPeer.id, + peer: mainPeer, + isContact: false, + isSavedMessages: false, + notificationSettings: nil, + peerPresences: [:], + cachedData: nil + ), customTitle: nil, customSubtitle: strings.Chat_Monoforum_Subtitle, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: nil, customMessageCount: nil, isEnabled: true) } else { strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, true) } } else { - strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo) let imageOverride: AvatarNodeImageOverride? if context.account.peerId == peer.id { @@ -1447,7 +1455,7 @@ extension ChatControllerImpl { customMessageCount = savedMessagesPeer?.messageCount ?? 0 } - strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true) + strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, customSubtitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true) strongSelf.state.peerView = peerView @@ -1534,7 +1542,7 @@ extension ChatControllerImpl { } if let threadInfo = messageAndTopic.threadData?.info { - strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) + strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, customSubtitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true) let avatarContent: EmojiStatusComponent.Content if chatLocation.threadId == 1 { diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index 4287a59f0e..b86ab95d31 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -2477,11 +2477,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { ChatSidePanelEnvironment(insets: UIEdgeInsets( top: 0.0, left: leftPanelLeftInset, - bottom: 0.0, + bottom: containerInsets.bottom + inputPanelsHeight, right: 0.0 )) }, - containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset - (containerInsets.bottom + inputPanelsHeight)) + containerSize: CGSize(width: leftPanelSize.width, height: leftPanelSize.height - sidePanelTopInset) ) let leftPanelFrame = CGRect(origin: CGPoint(x: 0.0, y: sidePanelTopInset), size: leftPanelSize) diff --git a/submodules/TelegramUI/Sources/NavigateToChatController.swift b/submodules/TelegramUI/Sources/NavigateToChatController.swift index 478a10afa0..87cb864f00 100644 --- a/submodules/TelegramUI/Sources/NavigateToChatController.swift +++ b/submodules/TelegramUI/Sources/NavigateToChatController.swift @@ -149,6 +149,13 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam canMatchThread = true switchToThread = true } + if case .replyThread = params.chatLocation { + if case let .replyThread(replyThread) = params.chatLocation, (replyThread.isForumPost || replyThread.isMonoforumPost) { + } else { + canMatchThread = false + switchToThread = false + } + } if controller.chatLocation.peerId == params.chatLocation.asChatLocation.peerId && canMatchThread && (controller.subject != .scheduledMessages || controller.subject == params.subject) { if let updateTextInputState = params.updateTextInputState { @@ -191,7 +198,10 @@ public func navigateToChatControllerImpl(_ params: NavigateToChatControllerParam controller.purposefulAction = params.purposefulAction if let activateInput = params.activateInput { - controller.activateInput(type: activateInput) + if case let .replyThread(replyThread) = params.chatLocation, (replyThread.isForumPost || replyThread.isMonoforumPost) { + } else { + controller.activateInput(type: activateInput) + } } if params.changeColors { controller.presentThemeSelection() From 10c28d982eead73c717db2719e0b3a15b6b7da98 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 27 Jun 2025 12:45:19 +0200 Subject: [PATCH 6/7] Various improvements --- .../Sources/AccountContext.swift | 25 ++++++++++-- .../Sources/MediaPickerScreen.swift | 10 +++-- .../TelegramEngine/Payments/Stars.swift | 2 +- .../Payments/TelegramEnginePayments.swift | 4 ++ .../ChatMessageSuggestedPostInfoNode.swift | 20 ++++++---- .../PeerInfoScreen/Sources/PeerInfoData.swift | 6 ++- .../Sources/PeerInfoScreen.swift | 36 ++++++++++++------ .../PostSuggestionsSettingsScreen.swift | 12 +++--- .../Sources/StarsWithdrawalScreen.swift | 4 +- .../DeletePaid.imageset/Contents.json | 12 ++++++ .../DeletePaid.imageset/trash_24.pdf | Bin 0 -> 5476 bytes .../Sources/Chat/ChatControllerPaste.swift | 7 +++- .../TelegramUI/Sources/ChatController.swift | 14 +++++++ .../Sources/ChatControllerNode.swift | 1 + .../ChatControllerOpenAttachmentMenu.swift | 10 ++++- .../ChatInterfaceStateContextMenus.swift | 7 +++- 16 files changed, 132 insertions(+), 38 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/trash_24.pdf diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index 877ed5d1f5..4faa45e954 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1506,7 +1506,10 @@ public struct StarsSubscriptionConfiguration { paidMessagesAvailable: false, starGiftResaleMinAmount: 125, starGiftResaleMaxAmount: 3500, - starGiftCommissionPermille: 80 + starGiftCommissionPermille: 80, + channelMessageSuggestionCommissionPermille: 850, + channelMessageSuggestionMaxStarsAmount: 10000, + channelMessageSuggestionMaxTonAmount: 10000000000000 ) } @@ -1518,6 +1521,9 @@ public struct StarsSubscriptionConfiguration { public let starGiftResaleMinAmount: Int64 public let starGiftResaleMaxAmount: Int64 public let starGiftCommissionPermille: Int32 + public let channelMessageSuggestionCommissionPermille: Int32 + public let channelMessageSuggestionMaxStarsAmount: Int64 + public let channelMessageSuggestionMaxTonAmount: Int64 fileprivate init( maxFee: Int64, @@ -1527,7 +1533,10 @@ public struct StarsSubscriptionConfiguration { paidMessagesAvailable: Bool, starGiftResaleMinAmount: Int64, starGiftResaleMaxAmount: Int64, - starGiftCommissionPermille: Int32 + starGiftCommissionPermille: Int32, + channelMessageSuggestionCommissionPermille: Int32, + channelMessageSuggestionMaxStarsAmount: Int64, + channelMessageSuggestionMaxTonAmount: Int64 ) { self.maxFee = maxFee self.usdWithdrawRate = usdWithdrawRate @@ -1537,6 +1546,9 @@ public struct StarsSubscriptionConfiguration { self.starGiftResaleMinAmount = starGiftResaleMinAmount self.starGiftResaleMaxAmount = starGiftResaleMaxAmount self.starGiftCommissionPermille = starGiftCommissionPermille + self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille + self.channelMessageSuggestionMaxStarsAmount = channelMessageSuggestionMaxStarsAmount + self.channelMessageSuggestionMaxTonAmount = channelMessageSuggestionMaxTonAmount } public static func with(appConfiguration: AppConfiguration) -> StarsSubscriptionConfiguration { @@ -1550,6 +1562,10 @@ public struct StarsSubscriptionConfiguration { let starGiftResaleMaxAmount = (data["stars_stargift_resale_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftResaleMaxAmount let starGiftCommissionPermille = (data["stars_stargift_resale_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.starGiftCommissionPermille + let channelMessageSuggestionCommissionPermille = (data["stars_suggested_post_commission_permille"] as? Double).flatMap(Int32.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionCommissionPermille + let channelMessageSuggestionMaxStarsAmount = (data["stars_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxStarsAmount + let channelMessageSuggestionMaxTonAmount = (data["ton_suggested_post_amount_max"] as? Double).flatMap(Int64.init) ?? StarsSubscriptionConfiguration.defaultValue.channelMessageSuggestionMaxTonAmount + return StarsSubscriptionConfiguration( maxFee: maxFee, usdWithdrawRate: usdWithdrawRate, @@ -1558,7 +1574,10 @@ public struct StarsSubscriptionConfiguration { paidMessagesAvailable: paidMessagesAvailable, starGiftResaleMinAmount: starGiftResaleMinAmount, starGiftResaleMaxAmount: starGiftResaleMaxAmount, - starGiftCommissionPermille: starGiftCommissionPermille + starGiftCommissionPermille: starGiftCommissionPermille, + channelMessageSuggestionCommissionPermille: channelMessageSuggestionCommissionPermille, + channelMessageSuggestionMaxStarsAmount: channelMessageSuggestionMaxStarsAmount, + channelMessageSuggestionMaxTonAmount: channelMessageSuggestionMaxTonAmount ) } else { return .defaultValue diff --git a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift index f374dc79e3..3eeeb0943b 100644 --- a/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift +++ b/submodules/MediaPickerUI/Sources/MediaPickerScreen.swift @@ -182,6 +182,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att private let chatLocation: ChatLocation? private let bannedSendPhotos: (Int32, Bool)? private let bannedSendVideos: (Int32, Bool)? + private let enableMultiselection: Bool private let canBoostToUnrestrict: Bool fileprivate let paidMediaAllowed: Bool fileprivate let subject: Subject @@ -1845,6 +1846,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att isScheduledMessages: Bool = false, bannedSendPhotos: (Int32, Bool)? = nil, bannedSendVideos: (Int32, Bool)? = nil, + enableMultiselection: Bool = true, canBoostToUnrestrict: Bool = false, paidMediaAllowed: Bool = false, subject: Subject, @@ -1868,6 +1870,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.isScheduledMessages = isScheduledMessages self.bannedSendPhotos = bannedSendPhotos self.bannedSendVideos = bannedSendVideos + self.enableMultiselection = enableMultiselection self.canBoostToUnrestrict = canBoostToUnrestrict self.paidMediaAllowed = paidMediaAllowed self.subject = subject @@ -1877,7 +1880,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att self.mainButtonAction = mainButtonAction self.secondaryButtonAction = secondaryButtonAction - let selectionContext = selectionContext ?? TGMediaSelectionContext() + let selectionContext = selectionContext ?? TGMediaSelectionContext(groupingAllowed: false, selectionLimit: enableMultiselection ? 100 : 1)! self.titleView = MediaPickerTitleView(theme: self.presentationData.theme, segments: [self.presentationData.strings.Attachment_AllMedia, self.presentationData.strings.Attachment_SelectedMedia(1)], selectedIndex: 0) @@ -1924,11 +1927,12 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: presentationData)) self.statusBar.statusBarStyle = .Ignore - + selectionContext.attemptSelectingItem = { [weak self] item in guard let self else { return false } + if let _ = item as? TGMediaPickerGalleryPhotoItem { if self.bannedSendPhotos != nil { self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_SendNotAllowedPhoto, actions: [TextAlertAction(type: .defaultAction, title: self.presentationData.strings.Common_OK, action: {})]), in: .window(.root)) @@ -2807,7 +2811,7 @@ public final class MediaPickerScreenImpl: ViewController, MediaPickerScreen, Att return } if let selectionContext = self.interaction?.selectionState, let editingContext = self.interaction?.editingState { - selectionContext.selectionLimit = 10 + selectionContext.selectionLimit = self.enableMultiselection ? 10 : 1 for case let item as TGMediaEditableItem in selectionContext.selectedItems() { editingContext.setPrice(NSNumber(value: amount), for: item) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift index 7b947a4e7d..e5e7be5bfb 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/Stars.swift @@ -1666,7 +1666,7 @@ public struct StarsTransactionReference: PostboxCoding, Hashable, Equatable { public let id: String public let isRefund: Bool - public init(peerId: EnginePeer.Id, id: String, isRefund: Bool) { + public init(peerId: EnginePeer.Id, id: String, isRefund: Bool) { self.peerId = peerId self.id = id self.isRefund = isRefund diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift index cb887658a1..7b564cf325 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Payments/TelegramEnginePayments.swift @@ -160,6 +160,10 @@ public extension TelegramEngine { public func updateStarGiftResalePrice(reference: StarGiftReference, price: Int64?) -> Signal { return _internal_updateStarGiftResalePrice(account: self.account, reference: reference, price: price) } + + public func getStarsTransaction(reference: StarsTransactionReference) -> Signal { + return _internal_getStarsTransaction(accountPeerId: self.account.peerId, postbox: self.account.postbox, network: self.account.network, transactionReference: reference) + } } } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift index 8c9f2407a3..573baf3a9a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageSuggestedPostInfoNode/Sources/ChatMessageSuggestedPostInfoNode.swift @@ -199,10 +199,14 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { contentHeight += titleLayout.0.size.height contentHeight += titleSpacing - maxContentWidth = max(maxContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width) - contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing + var tableContentWidth: CGFloat = 0.0 + tableContentWidth = max(tableContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width) + tableContentWidth = max(tableContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width) - maxContentWidth = max(maxContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width) + let labelValueOffset = labelSpacing + max(priceLabelLayout.0.size.width, timeLabelLayout.0.size.width) + + maxContentWidth = max(maxContentWidth, tableContentWidth) + contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing contentHeight += timeLabelLayout.0.size.height let size = CGSize(width: insets.left + insets.right + maxContentWidth, height: insets.top + insets.bottom + contentHeight) @@ -252,13 +256,15 @@ public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode { let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.0.size.width) * 0.5), y: insets.top), size: titleLayout.0.size) titleNode.frame = titleFrame - let priceLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size) + let tableX: CGFloat = floor((size.width - tableContentWidth) * 0.5) + + let priceLabelFrame = CGRect(origin: CGPoint(x: tableX, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size) priceLabelNode.frame = priceLabelFrame - priceValueNode.frame = CGRect(origin: CGPoint(x: priceLabelFrame.maxX + labelSpacing, y: priceLabelFrame.minY), size: priceValueLayout.0.size) + priceValueNode.frame = CGRect(origin: CGPoint(x: tableX + labelValueOffset, y: priceLabelFrame.minY), size: priceValueLayout.0.size) - let timeLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size) + let timeLabelFrame = CGRect(origin: CGPoint(x: tableX, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size) timeLabelNode.frame = timeLabelFrame - timeValueNode.frame = CGRect(origin: CGPoint(x: timeLabelFrame.maxX + labelSpacing, y: timeLabelFrame.minY), size: timeValueLayout.0.size) + timeValueNode.frame = CGRect(origin: CGPoint(x: tableX + labelValueOffset, y: timeLabelFrame.minY), size: timeValueLayout.0.size) return node }) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift index fe4168a2ef..4e8eea21e7 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift @@ -2290,8 +2290,12 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro if hasVoiceChat || canStartVoiceChat { result.append(.voiceChat) } + if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) { + result.append(.message) + } result.append(.mute) - if hasDiscussion { + if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) { + } else if hasDiscussion { result.append(.discussion) } result.append(.search) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 0ade9c5412..0a9ee051f0 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -1959,7 +1959,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese })) } - if let personalChannel = data.personalChannel { + if channel.hasPermission(.manageDirect), let personalChannel = data.personalChannel { let peerId = personalChannel.peer.peerId items[.channelMonoforum]?.append(PeerInfoScreenPersonalChannelItem(id: ItemPeerPersonalChannel, context: context, data: personalChannel, controller: { [weak interaction] in guard let interaction else { @@ -5998,18 +5998,32 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro switch key { case .message: if let navigationController = controller.navigationController as? NavigationController, let peer = self.data?.peer { - self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in - if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { - var viewControllers = navigationController.viewControllers - viewControllers = viewControllers.filter { controller in - if controller is PeerInfoScreen { - return false - } - return true + if let channel = peer as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId { + Task { @MainActor [weak self] in + guard let self else { + return } - navigationController.setViewControllers(viewControllers, animated: false) + + guard let peer = await self.context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: linkedMonoforumId)).get() else { + return + } + + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(peer), keepStack: .default)) } - })) + } else { + self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(EnginePeer(peer)), keepStack: self.nearbyPeerDistance != nil ? .always : .default, peerNearbyData: self.nearbyPeerDistance.flatMap({ ChatPeerNearbyData(distance: $0) }), completion: { [weak self] _ in + if let strongSelf = self, strongSelf.nearbyPeerDistance != nil { + var viewControllers = navigationController.viewControllers + viewControllers = viewControllers.filter { controller in + if controller is PeerInfoScreen { + return false + } + return true + } + navigationController.setViewControllers(viewControllers, animated: false) + } + })) + } } case .discussion: if let cachedData = self.data?.cachedData as? CachedChannelData, case let .known(maybeLinkedDiscussionPeerId) = cachedData.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId { diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index 12568056f3..947cb43f32 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -29,7 +29,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { let context: AccountContext let usdWithdrawRate: Int64 - let paidMessageCommissionPermille: Int + let channelMessageSuggestionCommissionPermille: Int let peer: EnginePeer? let initialPrice: StarsAmount? let completion: () -> Void @@ -37,14 +37,14 @@ final class PostSuggestionsSettingsScreenComponent: Component { init( context: AccountContext, usdWithdrawRate: Int64, - paidMessageCommissionPermille: Int, + channelMessageSuggestionCommissionPermille: Int, peer: EnginePeer?, initialPrice: StarsAmount?, completion: @escaping () -> Void ) { self.context = context self.usdWithdrawRate = usdWithdrawRate - self.paidMessageCommissionPermille = paidMessageCommissionPermille + self.channelMessageSuggestionCommissionPermille = channelMessageSuggestionCommissionPermille self.peer = peer self.initialPrice = initialPrice self.completion = completion @@ -373,7 +373,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { } let currentAmount: StarsAmount = StarsAmount(value: Int64(self.starCount), nanos: 0) - let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: component.paidMessageCommissionPermille / 10, kind: .postSuggestion, completion: { [weak self] amount in + let starsScreen = component.context.sharedContext.makeStarsWithdrawalScreen(context: component.context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 0, nanos: 0), fractionAfterCommission: component.channelMessageSuggestionCommissionPermille / 10, kind: .postSuggestion, completion: { [weak self] amount in guard let self else { return } @@ -404,7 +404,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { )), footer: AnyComponent(MultilineTextComponent( text: .plain(NSAttributedString( - string: environment.strings.ChannelMessages_PriceSectionFooterValue("\(component.paidMessageCommissionPermille / 10)").string, + string: environment.strings.ChannelMessages_PriceSectionFooterValue("\(component.channelMessageSuggestionCommissionPermille / 10)").string, font: Font.regular(13.0), textColor: self.starCount == 0 ? .clear : environment.theme.list.freeTextColor )), @@ -503,7 +503,7 @@ public final class PostSuggestionsSettingsScreen: ViewControllerComponentContain super.init(context: context, component: PostSuggestionsSettingsScreenComponent( context: context, usdWithdrawRate: configuration.usdWithdrawRate, - paidMessageCommissionPermille: Int(configuration.paidMessageCommissionPermille), + channelMessageSuggestionCommissionPermille: Int(configuration.channelMessageSuggestionCommissionPermille), peer: peer, initialPrice: initialPrice, completion: completion diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index 23c08659a0..d064c79f9b 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -220,14 +220,14 @@ private final class SheetContent: CombinedComponent { switch state.currency { case .stars: amountTitle = "ENTER A PRICE IN STARS" + maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxStarsAmount, nanos: 0) case .ton: amountTitle = "ENTER A PRICE IN TON" + maxAmount = StarsAmount(value: resaleConfiguration.channelMessageSuggestionMaxTonAmount, nanos: 0) } amountPlaceholder = "Price" minAmount = StarsAmount(value: 0, nanos: 0) - //TODO:release - maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) } let title = title.update( diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json new file mode 100644 index 0000000000..ee235a4085 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "trash_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/trash_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Context Menu/DeletePaid.imageset/trash_24.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d6ed06acc043b9c5f7c4ae153f6877c9951e70ac GIT binary patch literal 5476 zcmai&cQl+^_s4aKDABu&7SRm`L!uLcF}g%A6Gk_NsL`ViLiCnVLWGDKJ$f(EyAVY0 zHA1}P=HB~ey}z}7=a1*v>wC`LXYX^?v!2h|>`-|{ULc<+0Ra#o0B|(7ApijG-v{u^ z*dbvE2QwrLz%LJTgImBfAIe@$T@8Lk`1Rb?+wN*w9%kWa3Hv>x3UjbRS_1?@BBH+) z2teq{l9VKHK_Xyg_5|)}6$x-C`6N|`cwYzj*=3Q%Vwl~$ zP1a~cs^L$nAdh1xjiKTJD6Y$v?!M*7o0>DvS>O*W0V zHvzbz(o_=|l>zsm7{`7WuNfFHL9dC0{e)iQmIp@66I5Y31Y!yh=9m)J;b_e>Ciw$y z;A;T5a`51Ccrmn=*g$5zPWmTUxN`8L|v1w^BPHG#2NcQx(|>I+kNJ*d3xbz?eotM%CIVac!<1BA%xg-a9eS1~alAIY=Z z&j_Ajjz`nC_%z1tPPx<9kw_Box30{q>~ZWdo<2W!IVY6yHWD98RZ7`=W=K$A?Nn1d*Ml?shSU~18oT_a!Q(hg3P%Rj-~;&{Su|LC#uq%K$$ zY)~!xy{=RnH`hGBSyx$?Qxlvmn74n(EW z4W~RwX-I*m;Dh+#5SzEw)wb+5@z$=^YSvm6MisbI>{Ha`CZFTim<}yHY3oxZbF!`y=#L#adKVR`eQcv!DCFF8eezJ z*P~yWRV@&=P_;O->abQ0if4OfhqO~zh%O=*Z5C_q>XW}wj_qQOn||}*Z8|&v9=ygY zG|@Y!YUsq?PtU!c`h*7wCPkZt zu|+n8FN)#|FN$0WrwcDjffWl=+7mNI@{gCwtPirr6pHQp?7mlq*zZhgY$Z%>eb^}d zVZLL^U+VZF)9LfGQ0IIkjrmZLOt0)WorjUxd5cdw+&ZEm#}M;8A&9TIP+DA?x_C6d z$dg_Z!!q{~h4J7;&1DwX+}idkCfE3ywJPVCg4v9kjp~CL+u2O7BzHE?w0-ww_%i3d z-M;q#!rb1{)e~LkUB6%NSs!1g=bq>#bv$?!cJlTldB5VoXlHsYWbfAQ^oD5bn5E04 z>n5i!{w3XhJz}aN-&N?8n0e!9Z9ujMpC!!&t??s}Z9C_557i zQ_>TdpvU7&Wye^@T*VPYS9*8Jva=RC@Hy-A0%bcl8+T3fQcG6Ldh=X}S1@&m3M&t* zh~iABb~J^2rQ$+XbCyl^8$HkBnNJ9wDL!$2PpA07f=!)q-FL+hW1r`!bGrVNGGJ2D zZc-dD6YQU6(~snls1$Sy@A=9!->_7D@W`FqoBBEM(j!g9(0RtaPUXDeGz{ls&`^*v zrV5nLJ$*INoo&xY({yMrXL4zGshz_6k!m*>9V?(bkzNp=l0KFAG$A4JuG+IlUos`x z32u)*m8N|s_@qsE=^;YPoM)1cwhP%;*m)j9ncym705nMQaVc7xGP2DqK6rex=y~Y5 zefFM6oW9b5W^%Qp#D=__(@ksqX?Xh-9c_C?duL-M zYG$8#`D7So+;g-JIf!DF7?i-3@Os8O_pL*<3atm8rARiFPP`hKU=m#=%cKeIK}qY^5<9UxxN&9vuRl$vY4<) z?#LJo8a+JkJTG~#TVMJ5xW{?Hc`a<7eAboN)s+JTp&D&DiaiU{8q9OJTsqRS#m+_- zXylg63yblBFTSoMW#SLuq^*hf+@Ic!XyT7o8+mj7G~dJiKwCvNq8wCOv6FUL{a$3H z@{I}a^9OV9hmD<$zk6%om#}`cIBu@FHJlDlkE34Xk8&$oqidjWUUN9v-=6EiOe{9u zt46%-$X;pINqo08p7uVyw!dx;(ehLAK&>`a?bGDN*of_yY44r|-3|MWX?vfDAL3`T zXZ&+}H7?71Kekc+Fl?VdZoC~L1rp_*y=~MN^@htcnQwu~3%n#il7l;(_qluu_o~jq zy5bby)*qgSj(7IuF{_6>tj^~59XP!>(BBs7E*n~z8rW#JZ=dGyzU%Nw|GW7nY^o=@ zt4@mIGT$3^zMd#Gdszpj1cHvA9C{uXH;dXhMt!RAnFcWmUz{3s*CwBQzcV74cG`J9 zalm;#e2`o?P&EKEQEe#qAv!&C5`noLOOTj19X}o1Dq`H)q7@;ut)Z5RVpXOZhV@6q zr=)R_4$2LQ8A_(hBqqmSrmBZMxFOx+hM|4OJA#=Y#kbe16f85JiR-KPjHcSs*tX-N zz6}VY7mfk-62#ea0qQ#4yQl>V1blb)CN&AQ1F1F+klk2(d2!1Fs|28eUZ}bOu(D6VDkp0&X=yO${7-jFAF;&wO{H3l!1AykRf8f z$SMU97Zn-HR#uCQ-u?z44u&39V`A3iWOBjeB!4Flv;_nC=qE1f#vm&J%)# z)JY)t4}Si_$G>Hve`Esyzp|X1teFeU5^&9`ngFBgFu$CI|5J*Ycy>9;r zR7g2yv08c`akDc5HlqTm7y(-FuznLR5wU%zQ+S3bCVbIf-5x4hmI%5qepFCFVU@{> zO7Vgrm5cf{^kHn=y0vhm@|>4LFIdxIjQdOCNKYiK7hQdo<5&R+)(%d_bhj6BN4_`w z!Kk6Fg#ys3z-v$V;ZB)UGB7(^KD5PJJt`-(dssnAzV`7ZgSc&<%P`D`Z!Dx#o8t|n zJEM%MSm)~}eU;&TyQGCX}JiAc;S6>V_0Kh zm;yPPNkYy6AKp;#bO~<5UzgHMgM{-GlydMVd?-pY$1tElqSc9h(HxyX>vB>V$=)=jv|h1m*QeqmdV z0YFdiE<`_+noIKtRJ{UJh?)RRn$k#2gPrGO^(Z$W4Dr}zj7y$9{3h=;on9o|q?h*G zriq3w#k0toX*A>1@do#H?UKx{&w8&$s$8yU$(M*nT9f1u?IYvmHogVzV_T%wI%T|K zLAO%9xiwpSQ@Q{B)Oa)g7Daw?eN*(UZgo8u8n$*qnUb|_W*)qK<}K;GNenAXG8&^U zkLWu7JPfykER6gejRLOIqMi!j_B28aMG1z9(E5?)&+lycViHMtw~OkD(?OTyN4Ynn36-yGz({lg=9HvsbVa0S6=qRFoYOw2l)by zIga#s%Prys2(?KNljUi|wzBCD?tUJDtyH7-aGN;sXp$-RUB}LY60q<+i_dP~x2s(N zD8%{()cWI!tZiQVDKRYwzm%LT?^Q$X2;SEU)kg1kIjvruM%C=AP1d&lbfauYG9EEb z&ZZuG0RJZRg1xu-sWhtWX#k%(@YRbTwsyX#ACk{u%HX5r^}W_i3LJ#+@~X5=P>puE zY<}^+4VRESMuo;rjS6iEUMD$LBGie%gG-$5WO0gu;68 zj7C3vtxTTPr?s*@2wmgzK#b4G!N#<$gKDN4#8i z$5YIp+aWTL)4|phaRs`mMx@9N6uS8Hp&(SyZJN47-m*=_1+-q-{nb5g?CwP0FAcLl z&u72jMWo)H97_*ej4Ase`kWpLenqAbB6%)D967MCotBgXm+NlRGzLAvnu^supq2%$ zG>c8P(=YaORJgv-jS`QeN;8A}6zi?J+!b)ftXN33c1E-;0rdp>b!d3=%NQY^h*nBe ziX6++6~1_jHMuP14V$d}fWa+ym6JgCh~(8^8JaiM1D;J)xlXOF8-ZUi8Tk8|8z?wF zDqnqYTf&T45*o{%hy6}c;BOS+jInB?cpYvt%}MnF9!vJ*<^ZFK+ba!8>x6HrO(TY8 z@I4}PtO{zOv**TI@t>Jv)F}yQ8C1L5M#D+8_zIVra+f|plhI1eq^g(hq$!-fp$px( zmzY}BO~~5~gL|i>?-yDL1-$l9$5I5p04z4CbS1XxB#Y^US&pD4fmq^{_U+0+~wI87V7xyujau{vdfQh(ttGa zTsN9SYN~7$Qm{@N-=_>2Vn`OW$%cbsrQ*qBwtL}i>IIAVN4%W= z5OYl!*F82lM+YR#;cCZgO8Hxanjx+wd_H6;BV(e=s~ z_&@o(Tq*w*3H(Q->jGW=+3-5CzY3Q#Lz>w+TK(zjj(|NS5C8xL2>z=7`UwyLfrLPS zEB*HX3IKjt0Egd_pa}RX(d+TABr5nnt3*LpHU7Cu?0;eji-}y_<{zts#l)^k`a=Q< zh+M(upAt~$Uq>Xu3~mQQ5M1B8vi#LS-g9xZhj9Y#!7VxeI!=B~M@J;!`lx deliverOnMainQueue).startStandalone(next: { [weak self] settings in if let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer { + var enableMultiselection = true + if strongSelf.presentationInterfaceState.interfaceState.postSuggestionState != nil { + enableMultiselection = false + } + strongSelf.chatDisplayNode.dismissInput() let controller = mediaPasteboardScreen( context: strongSelf.context, @@ -28,7 +33,7 @@ extension ChatControllerImpl { subjects: subjects, presentMediaPicker: { [weak self] subject, saveEditedPhotos, bannedSendPhotos, bannedSendVideos, present in if let strongSelf = self { - strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in + strongSelf.presentMediaPicker(subject: subject, saveEditedPhotos: saveEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: present, updateMediaPickerContext: { _ in }, completion: { [weak self] fromGallery, signals, silentPosting, scheduleTime, parameters, getAnimatedTransitionSource, completion in self?.enqueueMediaMessages(fromGallery: fromGallery, signals: signals, silentPosting: silentPosting, scheduleTime: scheduleTime, parameters: parameters, getAnimatedTransitionSource: getAnimatedTransitionSource, completion: completion) }) } diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index bd1148b6d0..b15b0863ee 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -1173,6 +1173,20 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let controller = self.context.sharedContext.makeStarsGiftScreen(context: self.context, message: EngineMessage(message)) self.push(controller) return true + case let .giftTon(_, _, _, _, transactionId): + Task { @MainActor [weak self] in + guard let self, let transactionId, let peerId = self.chatLocation.peerId else { + return + } + let transactionData = await self.context.engine.payments.getStarsTransaction(reference: StarsTransactionReference(peerId: self.context.account.peerId, id: transactionId, isRefund: false)).get() + let peer = await self.context.engine.data.get( + TelegramEngine.EngineData.Item.Peer.Peer(id: peerId) + ).get() + if let transactionData, let peer { + self.push(self.context.sharedContext.makeStarsTransactionScreen(context: self.context, transaction: transactionData, peer: peer)) + } + } + let _ = transactionId case let .giftCode(slug, _, _, _, _, _, _, _, _, _, _): self.openResolved(result: .premiumGiftCode(slug: slug), sourceMessageId: message.id, progress: params.progress) return true diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index b86ab95d31..62d5b14286 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -4472,6 +4472,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { } var effectivePresentationInterfaceState = self.chatPresentationInterfaceState + if let textInputPanelNode = self.textInputPanelNode { effectivePresentationInterfaceState = effectivePresentationInterfaceState.updatedInterfaceState { $0.withUpdatedEffectiveInputState(textInputPanelNode.inputTextState) } } diff --git a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift index d001fdf81a..7b2614ba8f 100644 --- a/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift +++ b/submodules/TelegramUI/Sources/ChatControllerOpenAttachmentMenu.swift @@ -61,6 +61,11 @@ extension ChatControllerImpl { var bannedSendVideos: (Int32, Bool)? var bannedSendFiles: (Int32, Bool)? + var enableMultiselection = true + if self.presentationInterfaceState.interfaceState.postSuggestionState != nil { + enableMultiselection = false + } + var canSendPolls = true var canSendTodos = true if let peer = self.presentationInterfaceState.renderedPeer?.peer { @@ -336,7 +341,7 @@ extension ChatControllerImpl { controller.prepareForReuse() return } - strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, present: { controller, mediaPickerContext in + strongSelf.presentMediaPicker(saveEditedPhotos: dataSettings.storeEditedPhotos, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, enableMultiselection: enableMultiselection, present: { controller, mediaPickerContext in let _ = currentMediaController.swap(controller) if !inputText.string.isEmpty { mediaPickerContext?.setCaption(inputText) @@ -1230,7 +1235,7 @@ extension ChatControllerImpl { self.present(actionSheet, in: .window(.root)) } - func presentMediaPicker(subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping (Bool, [Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { + func presentMediaPicker(subject: MediaPickerScreenImpl.Subject = .assets(nil, .default), saveEditedPhotos: Bool, bannedSendPhotos: (Int32, Bool)?, bannedSendVideos: (Int32, Bool)?, enableMultiselection: Bool, present: @escaping (MediaPickerScreenImpl, AttachmentMediaPickerContext?) -> Void, updateMediaPickerContext: @escaping (AttachmentMediaPickerContext?) -> Void, completion: @escaping (Bool, [Any], Bool, Int32?, ChatSendMessageActionSheetController.SendParameters?, @escaping (String) -> UIView?, @escaping () -> Void) -> Void) { var isScheduledMessages = false if case .scheduledMessages = self.presentationInterfaceState.subject { isScheduledMessages = true @@ -1248,6 +1253,7 @@ extension ChatControllerImpl { isScheduledMessages: isScheduledMessages, bannedSendPhotos: bannedSendPhotos, bannedSendVideos: bannedSendVideos, + enableMultiselection: enableMultiselection, canBoostToUnrestrict: (self.presentationInterfaceState.boostsToUnrestrict ?? 0) > 0 && bannedSendPhotos?.1 != true && bannedSendVideos?.1 != true, paidMediaAllowed: paidMediaAllowed, subject: subject, diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 03277f6fa0..5423350bac 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -1924,8 +1924,13 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } }), false)) } else if !isUnremovableAction { + var iconName: String = isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete" + if message.attributes.contains(where: { $0 is PublishedSuggestedPostMessageAttribute }) { + iconName = "Chat/Context Menu/DeletePaid" + } + actions.append(.action(ContextMenuActionItem(text: title, textColor: .destructive, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: isSending ? "Chat/Context Menu/Clear" : "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + return generateTintedImage(image: UIImage(bundleImageName: iconName), color: theme.actionSheet.destructiveActionTextColor) }, action: { controller, f in if isEditing { context.account.pendingUpdateMessageManager.cancel(messageId: message.id) From f79a244bf251981fbb1c642d98c8d88395b16a59 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 27 Jun 2025 17:36:37 +0200 Subject: [PATCH 7/7] Various improvements --- .../Sources/ServiceMessageStrings.swift | 67 +++++++- .../ChatMessageAnimatedStickerItemNode.swift | 4 +- .../Sources/ChatMessageStickerItemNode.swift | 4 +- .../Sources/ForumSettingsScreen.swift | 63 ++++++-- .../Sources/StarsWithdrawalScreen.swift | 148 ++++++++++-------- .../Sources/ChatTextInputPanelNode.swift | 7 + 6 files changed, 204 insertions(+), 89 deletions(-) diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index 63de4331de..11111d9eb3 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -1259,8 +1259,24 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, 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 case .channel = author { + var isUser = true + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isUser = false + } + } + + if isUser { + messagePeer = author + } else { + messagePeer = EnginePeer(threadPeer) + isOutgoing = true + } + } else { + isOutgoing = true + messagePeer = EnginePeer(threadPeer) + } } } @@ -1450,12 +1466,51 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } attributedString = NSAttributedString(string: string, font: titleFont, textColor: primaryTextColor) - case .suggestedPostSuccess: + case let .suggestedPostSuccess(amount): + var isUser = true + var channelName: String = "" + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + channelName = peer.title + if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isUser = false + } + } + let _ = isUser + //TODO:localize - attributedString = NSAttributedString(string: "Suggested post was posted", font: titleFont, textColor: primaryTextColor) - case .suggestedPostRefund: + let amountString: String + switch amount.currency { + case .stars: + if amount.amount.value == 1 { + amountString = "1 Star" + } else { + amountString = "\(amount.amount.value) Stars" + } + case .ton: + amountString = "\(formatTonAmountText(amount.amount.value, dateTimeFormat: dateTimeFormat)) TON" + } + attributedString = parseMarkdownIntoAttributedString("**\(channelName)** received **\(amountString)** for publishing this post", attributes: MarkdownAttributes(body: bodyAttributes, bold: boldAttributes, link: bodyAttributes, linkAttribute: { _ in return nil })) + case let .suggestedPostRefund(info): + var isUser = true + var channelName: String = "" + if let peer = message.peers[message.id.peerId] as? TelegramChannel { + channelName = peer.title + if peer.isMonoForum, let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = message.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.manageDirect) { + isUser = false + } + } + let _ = channelName + //TODO:localize - attributedString = NSAttributedString(string: "Suggested post was refunded", font: titleFont, textColor: primaryTextColor) + if info.isUserInitiated { + if isUser { + attributedString = NSAttributedString(string: "Suggested post was refunded because you didn't have enough funds", font: titleFont, textColor: primaryTextColor) + } else { + attributedString = NSAttributedString(string: "Suggested post was refunded because the user didn't have enough funds", font: titleFont, textColor: primaryTextColor) + } + } else { + attributedString = NSAttributedString(string: "Suggested post was refunded because the message was deleted", font: titleFont, textColor: primaryTextColor) + } case let .giftTon(currency, amount, _, _, _): attributedString = nil if !forAdditionalServiceMessage { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift index 8053656588..2e54b862be 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageAnimatedStickerItemNode/Sources/ChatMessageAnimatedStickerItemNode.swift @@ -1408,7 +1408,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { layoutSize.height += additionalTopHeight imageFrame.origin.y += additionalTopHeight - var headersOffset: CGFloat = 0.0 + var headersOffset: CGFloat = additionalTopHeight if let (threadInfoSize, _) = threadInfoApply { headersOffset += threadInfoSize.height + 10.0 } @@ -1625,7 +1625,7 @@ public class ChatMessageAnimatedStickerItemNode: ChatMessageItemView { } } - var headersOffset: CGFloat = 0.0 + var headersOffset: CGFloat = additionalTopHeight if let (threadInfoSize, threadInfoApply) = threadInfoApply { let threadInfoNode = threadInfoApply(synchronousLoads) if strongSelf.threadInfoNode == nil { diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift index 8e4d882243..55ad3cc6a7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageStickerItemNode/Sources/ChatMessageStickerItemNode.swift @@ -991,7 +991,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { baseShareButtonFrame.origin.x = dateAndStatusFrame.maxX + 8.0 } - var headersOffset: CGFloat = 0.0 + var headersOffset: CGFloat = additionalTopHeight if let (threadInfoSize, _) = threadInfoApply { headersOffset += threadInfoSize.height + 10.0 } @@ -1149,7 +1149,7 @@ public class ChatMessageStickerItemNode: ChatMessageItemView { } } - var headersOffset: CGFloat = 0.0 + var headersOffset: CGFloat = additionalTopHeight if let (threadInfoSize, threadInfoApply) = threadInfoApply { let threadInfoNode = threadInfoApply(synchronousLoads) if strongSelf.threadInfoNode == nil { diff --git a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift index ce2e1c43ee..aec7e0f1e8 100644 --- a/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift +++ b/submodules/TelegramUI/Components/ForumSettingsScreen/Sources/ForumSettingsScreen.swift @@ -172,24 +172,27 @@ final class ForumSettingsScreenComponent: Component { if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController { var viewControllers = navigationController.viewControllers - if self.isOn && self.mode == .list { - for i in 0 ..< viewControllers.count { - if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId { - let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) - viewControllers[i] = chatListController - } - } - navigationController.setViewControllers(viewControllers, animated: false) + if case .legacyGroup = peer { } else { - for i in (0 ..< viewControllers.count).reversed() { - if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) { - viewControllers.remove(at: i) + if self.isOn && self.mode == .list { + for i in 0 ..< viewControllers.count { + if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId { + let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: component.peerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + viewControllers[i] = chatListController + } + } + navigationController.setViewControllers(viewControllers, animated: false) + } else { + for i in (0 ..< viewControllers.count).reversed() { + if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) { + viewControllers.remove(at: i) + } + } + navigationController.setViewControllers(viewControllers, animated: false) + + if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController { + chatListController.resetForumStackIfOpen() } - } - navigationController.setViewControllers(viewControllers, animated: false) - - if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController { - chatListController.resetForumStackIfOpen() } } } @@ -232,6 +235,34 @@ final class ForumSettingsScreenComponent: Component { } if let resultPeerId { self.peerIdPromise.set(resultPeerId) + + let _ = component.context.engine.peers.setChannelForumMode(id: resultPeerId, isForum: true, displayForumAsTabs: self.mode == .tabs).startStandalone() + + if let controller = self.environment?.controller(), let navigationController = controller.navigationController as? NavigationController { + var viewControllers = navigationController.viewControllers + if self.mode == .list { + for i in 0 ..< viewControllers.count { + if let chatController = viewControllers[i] as? ChatController, chatController.chatLocation.peerId == component.peerId { + let chatListController = component.context.sharedContext.makeChatListController(context: component.context, location: .forum(peerId: resultPeerId), controlsHistoryPreload: false, hideNetworkActivityStatus: false, previewing: false, enableDebugActions: false) + viewControllers[i] = chatListController + } + } + navigationController.setViewControllers(viewControllers, animated: false) + } else { + for i in (0 ..< viewControllers.count).reversed() { + if let chatListController = viewControllers[i] as? ChatListController, chatListController.location == .forum(peerId: component.peerId) { + viewControllers.remove(at: i) + } else if let peerInfoScreen = viewControllers[i] as? PeerInfoScreen, peerInfoScreen.peerId == component.peerId { + viewControllers.remove(at: i) + } + } + navigationController.setViewControllers(viewControllers, animated: false) + + if let baseController = navigationController as? TelegramRootControllerInterface, let chatListController = baseController.getChatsController() as? ChatListController { + chatListController.resetForumStackIfOpen() + } + } + } } else { self.isOn = false self.state?.updated(transition: .easeInOut(duration: 0.2)) diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index d064c79f9b..7933167ced 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -299,69 +299,86 @@ private final class SheetContent: CombinedComponent { if let tonBalance = state.tonBalance { tonBalanceValue = tonBalance } - if case let .suggestedPost(mode, _, _, _) = component.mode, (state.currency == .ton || tonBalanceValue > StarsAmount.zero) { - //TODO:localize - let selectedId: AnyHashable = state.currency == .stars ? AnyHashable(0 as Int) : AnyHashable(1 as Int) - let starsTitle: String - let tonTitle: String + + if case let .suggestedPost(mode, _, _, _) = component.mode { + var displayCurrencySelector = false switch mode { - case .sender: - starsTitle = "Offer Stars" - tonTitle = "Offer TON" + case let .sender(_, isFromAdmin): + if isFromAdmin { + displayCurrencySelector = true + } else { + if state.currency == .ton || tonBalanceValue > StarsAmount.zero { + displayCurrencySelector = true + } + } case .admin: - starsTitle = "Request Stars" - tonTitle = "Request TON" + displayCurrencySelector = true } - 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))) + if displayCurrencySelector { + //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 ), - 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 + 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 + } + + let currency: CurrencyAmount.Currency + if id == AnyHashable(0) { + currency = .stars + } else { + currency = .ton + } + if state.currency != currency { + state.currency = currency + state.amount = nil + } + state.updated(transition: .spring(duration: 0.4)) } - - let currency: CurrencyAmount.Currency - if id == AnyHashable(0) { - currency = .stars - } else { - currency = .ton - } - if state.currency != currency { - state.currency = currency - state.amount = nil - } - 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 + ), + 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) @@ -506,7 +523,7 @@ private final class SheetContent: CombinedComponent { accentColor: theme.list.itemAccentColor, value: state.amount?.value, minValue: minAmount?.value, - maxValue: state.currency == .ton ? nil : maxAmount?.value, + maxValue: maxAmount?.value, placeholderText: amountPlaceholder, labelText: amountLabel, currency: state.currency, @@ -658,7 +675,7 @@ private final class SheetContent: CombinedComponent { //TODO:localize switch mode { case .sender: - if let amount = state.amount { + if let amount = state.amount, amount != .zero { let currencySymbol: String let currencyAmount: String switch state.currency { @@ -1164,9 +1181,14 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { // Convert and combine if let whole = Int64(wholeSlice), let frac = Int64(fractionStr) { + + let whole = min(whole, Int64.max / scale) + amount = whole * scale + frac } } else if let whole = Int64(text) { // string had no dot at all + let whole = min(whole, Int64.max / scale) + amount = whole * scale } } @@ -1241,7 +1263,7 @@ private final class AmountFieldStarsFormatter: NSObject, UITextFieldDelegate { case .stars: textField.text = "\(self.maxValue)" case .ton: - textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: self.dateTimeFormat))" + textField.text = "\(formatTonAmountText(self.maxValue, dateTimeFormat: PresentationDateTimeFormat(timeFormat: self.dateTimeFormat.timeFormat, dateFormat: self.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: "")))" } self.onTextChanged(text: self.textField.text ?? "") self.animateError() @@ -1396,13 +1418,13 @@ private final class AmountFieldComponent: Component { self.textField.textColor = component.textColor if self.component?.currency != component.currency { - if let value = component.value { + if let value = component.value, value != .zero { var text = "" switch component.currency { case .stars: text = "\(value)" case .ton: - text = "\(formatTonAmountText(value, dateTimeFormat: component.dateTimeFormat))" + text = "\(formatTonAmountText(value, dateTimeFormat: PresentationDateTimeFormat(timeFormat: component.dateTimeFormat.timeFormat, dateFormat: component.dateTimeFormat.dateFormat, dateSeparator: "", dateSuffix: "", requiresFullYear: false, decimalSeparator: ".", groupingSeparator: "")))" } self.textField.text = text } else { @@ -1461,7 +1483,7 @@ private final class AmountFieldComponent: Component { currency: component.currency, dateTimeFormat: component.dateTimeFormat, minValue: component.minValue ?? 0, - maxValue: component.maxValue ?? Int64.max, + maxValue: component.maxValue ?? 10000000, updated: { [weak self] value in guard let self, let component = self.component else { return diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index d2af18fb7d..a336b54b98 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -1523,6 +1523,13 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch if case let .media(value) = editMessageState.content { isEditingMedia = !value.isEmpty isMediaEnabled = !value.isEmpty + + if interfaceState.interfaceState.postSuggestionState != nil { + if value.contains(.file) { + isEditingMedia = false + isMediaEnabled = false + } + } } else { isMediaEnabled = true }