diff --git a/submodules/AccountContext/Sources/AccountContext.swift b/submodules/AccountContext/Sources/AccountContext.swift index ba1a4f541d..8dbdc1b27c 100644 --- a/submodules/AccountContext/Sources/AccountContext.swift +++ b/submodules/AccountContext/Sources/AccountContext.swift @@ -1031,8 +1031,9 @@ public enum StarsWithdrawalScreenSubject { case postSuggestion } - case withdraw - case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind) + case withdraw(completion: (Int64) -> Void) + case enterAmount(current: StarsAmount, minValue: StarsAmount, fractionAfterCommission: Int, kind: PaidMessageKind, completion: (Int64) -> Void) + case postSuggestion(channel: EnginePeer, current: StarsAmount, timestamp: Int32?, completion: (Int64, Int32?) -> Void) } public protocol SharedAccountContext: AnyObject { @@ -1221,7 +1222,7 @@ public protocol SharedAccountContext: AnyObject { func makeStarsStatisticsScreen(context: AccountContext, peerId: EnginePeer.Id, revenueContext: StarsRevenueStatsContext) -> ViewController func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController - func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController + func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject) -> ViewController func makeStarGiftResellScreen(context: AccountContext, gift: StarGift.UniqueGift, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController func makeStarsGiveawayBoostScreen(context: AccountContext, peerId: EnginePeer.Id, boost: ChannelBoostersContext.State.Boost) -> ViewController diff --git a/submodules/AttachmentUI/Sources/AttachmentPanel.swift b/submodules/AttachmentUI/Sources/AttachmentPanel.swift index a731bf4f96..fc9bf4e43b 100644 --- a/submodules/AttachmentUI/Sources/AttachmentPanel.swift +++ b/submodules/AttachmentUI/Sources/AttachmentPanel.swift @@ -961,6 +961,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { }, shareSelectedMessages: { }, updateTextInputStateAndMode: { [weak self] f in if let strongSelf = self { @@ -1246,7 +1247,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { - }, openSuggestPost: { + }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in @@ -1264,6 +1265,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { + }, openSuggestPost: { }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift index 5ca920aa8d..da31ab68dc 100644 --- a/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift +++ b/submodules/ChatInterfaceState/Sources/ChatInterfaceState.swift @@ -495,6 +495,16 @@ public final class ChatInterfaceState: Codable, Equatable { } } + public struct PostSuggestionState: Codable, Equatable { + public var price: Int64 + public var timestamp: Int32? + + public init(price: Int64, timestamp: Int32?) { + self.price = price + self.timestamp = timestamp + } + } + public let timestamp: Int32 public let composeInputState: ChatTextInputState public let composeDisableUrlPreviews: [String] @@ -510,6 +520,7 @@ public final class ChatInterfaceState: Codable, Equatable { public let silentPosting: Bool public let inputLanguage: String? public let sendMessageEffect: Int64? + public let postSuggestionState: PostSuggestionState? public var synchronizeableInputState: SynchronizeableChatInputState? { if self.composeInputState.inputText.string.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && self.replyMessageSubject == nil { @@ -561,9 +572,10 @@ public final class ChatInterfaceState: Codable, Equatable { self.silentPosting = false self.inputLanguage = nil self.sendMessageEffect = nil + self.postSuggestionState = nil } - public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreviews: [String], replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, mediaDraftState: ChatInterfaceMediaDraftState?, silentPosting: Bool, inputLanguage: String?, sendMessageEffect: Int64?) { + public init(timestamp: Int32, composeInputState: ChatTextInputState, composeDisableUrlPreviews: [String], replyMessageSubject: ReplyMessageSubject?, forwardMessageIds: [EngineMessage.Id]?, forwardOptionsState: ChatInterfaceForwardOptionsState?, editMessage: ChatEditMessageState?, selectionState: ChatInterfaceSelectionState?, messageActionsState: ChatInterfaceMessageActionsState, historyScrollState: ChatInterfaceHistoryScrollState?, mediaRecordingMode: ChatTextInputMediaRecordingButtonMode, mediaDraftState: ChatInterfaceMediaDraftState?, silentPosting: Bool, inputLanguage: String?, sendMessageEffect: Int64?, postSuggestionState: PostSuggestionState?) { self.timestamp = timestamp self.composeInputState = composeInputState self.composeDisableUrlPreviews = composeDisableUrlPreviews @@ -579,6 +591,7 @@ public final class ChatInterfaceState: Codable, Equatable { self.silentPosting = silentPosting self.inputLanguage = inputLanguage self.sendMessageEffect = sendMessageEffect + self.postSuggestionState = postSuggestionState } public init(from decoder: Decoder) throws { @@ -650,8 +663,8 @@ public final class ChatInterfaceState: Codable, Equatable { self.silentPosting = ((try? container.decode(Int32.self, forKey: "sip")) ?? 0) != 0 self.inputLanguage = try? container.decodeIfPresent(String.self, forKey: "inputLanguage") - self.sendMessageEffect = try? container.decodeIfPresent(Int64.self, forKey: "sendMessageEffect") + self.postSuggestionState = try? container.decodeIfPresent(PostSuggestionState.self, forKey: "postSuggestionState") } public func encode(to encoder: Encoder) throws { @@ -710,6 +723,7 @@ public final class ChatInterfaceState: Codable, Equatable { } try container.encodeIfPresent(self.sendMessageEffect, forKey: "sendMessageEffect") + try container.encodeIfPresent(self.postSuggestionState, forKey: "postSuggestionState") } public static func ==(lhs: ChatInterfaceState, rhs: ChatInterfaceState) -> Bool { @@ -747,17 +761,20 @@ public final class ChatInterfaceState: Codable, Equatable { if lhs.sendMessageEffect != rhs.sendMessageEffect { return false } + if lhs.postSuggestionState != rhs.postSuggestionState { + return false + } return lhs.composeInputState == rhs.composeInputState && lhs.replyMessageSubject == rhs.replyMessageSubject && lhs.selectionState == rhs.selectionState && lhs.editMessage == rhs.editMessage } public func withUpdatedComposeInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { let updatedComposeInputState = inputState - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedComposeDisableUrlPreviews(_ disableUrlPreviews: [String]) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: disableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: disableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedEffectiveInputState(_ inputState: ChatTextInputState) -> ChatInterfaceState { @@ -769,19 +786,19 @@ public final class ChatInterfaceState: Codable, Equatable { updatedComposeInputState = inputState } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: updatedComposeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: updatedEditMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedReplyMessageSubject(_ replyMessageSubject: ReplyMessageSubject?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedForwardMessageIds(_ forwardMessageIds: [EngineMessage.Id]?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedForwardOptionsState(_ forwardOptionsState: ChatInterfaceForwardOptionsState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedSelectedMessages(_ messageIds: [EngineMessage.Id]) -> ChatInterfaceState { @@ -792,7 +809,7 @@ public final class ChatInterfaceState: Codable, Equatable { for messageId in messageIds { selectedIds.insert(messageId) } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withToggledSelectedMessages(_ messageIds: [EngineMessage.Id], value: Bool) -> ChatInterfaceState { @@ -807,47 +824,51 @@ public final class ChatInterfaceState: Codable, Equatable { selectedIds.remove(messageId) } } - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: ChatInterfaceSelectionState(selectedIds: selectedIds), messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withoutSelectionState() -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: nil, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedTimestamp(_ timestamp: Int32) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedEditMessage(_ editMessage: ChatEditMessageState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedMessageActionsState(_ f: (ChatInterfaceMessageActionsState) -> ChatInterfaceMessageActionsState) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: f(self.messageActionsState), historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedHistoryScrollState(_ historyScrollState: ChatInterfaceHistoryScrollState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedMediaRecordingMode(_ mediaRecordingMode: ChatTextInputMediaRecordingButtonMode) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedMediaDraftState(_ mediaDraftState: ChatInterfaceMediaDraftState?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedSilentPosting(_ silentPosting: Bool) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedInputLanguage(_ inputLanguage: String?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: inputLanguage, sendMessageEffect: self.sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: self.postSuggestionState) } public func withUpdatedSendMessageEffect(_ sendMessageEffect: Int64?) -> ChatInterfaceState { - return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: sendMessageEffect) + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: sendMessageEffect, postSuggestionState: self.postSuggestionState) + } + + public func withUpdatedPostSuggestionState(_ postSuggestionState: PostSuggestionState?) -> ChatInterfaceState { + return ChatInterfaceState(timestamp: self.timestamp, composeInputState: self.composeInputState, composeDisableUrlPreviews: self.composeDisableUrlPreviews, replyMessageSubject: self.replyMessageSubject, forwardMessageIds: self.forwardMessageIds, forwardOptionsState: self.forwardOptionsState, editMessage: self.editMessage, selectionState: self.selectionState, messageActionsState: self.messageActionsState, historyScrollState: self.historyScrollState, mediaRecordingMode: self.mediaRecordingMode, mediaDraftState: self.mediaDraftState, silentPosting: self.silentPosting, inputLanguage: self.inputLanguage, sendMessageEffect: self.sendMessageEffect, postSuggestionState: postSuggestionState) } public static func parse(_ state: OpaqueChatInterfaceState) -> ChatInterfaceState { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift index de76345dfc..5262e83b58 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatPanelInterfaceInteraction.swift @@ -78,6 +78,7 @@ public final class ChatPanelInterfaceInteraction { public let presentForwardOptions: (ASDisplayNode) -> Void public let presentReplyOptions: (ASDisplayNode) -> Void public let presentLinkOptions: (ASDisplayNode) -> Void + public let presentSuggestPostOptions: () -> Void public let shareSelectedMessages: () -> Void public let updateTextInputStateAndMode: (@escaping (ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void public let updateInputModeAndDismissedButtonKeyboardMessageId: ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void @@ -150,7 +151,7 @@ public final class ChatPanelInterfaceInteraction { public let joinGroupCall: (CachedChannelData.ActiveCall) -> Void public let presentInviteMembers: () -> Void public let presentGigagroupHelp: () -> Void - public let openSuggestPost: () -> Void + public let openMonoforum: () -> Void public let updateShowCommands: ((Bool) -> Bool) -> Void public let updateShowSendAsPeers: ((Bool) -> Bool) -> Void public let openInviteRequests: () -> Void @@ -167,6 +168,7 @@ public final class ChatPanelInterfaceInteraction { public let addDoNotTranslateLanguage: (String) -> Void public let hideTranslationPanel: () -> Void public let openPremiumGift: () -> Void + public let openSuggestPost: () -> Void public let openPremiumRequiredForMessaging: () -> Void public let openStarsPurchase: (Int64?) -> Void public let openMessagePayment: () -> Void @@ -199,6 +201,7 @@ public final class ChatPanelInterfaceInteraction { presentForwardOptions: @escaping (ASDisplayNode) -> Void, presentReplyOptions: @escaping (ASDisplayNode) -> Void, presentLinkOptions: @escaping (ASDisplayNode) -> Void, + presentSuggestPostOptions: @escaping () -> Void, shareSelectedMessages: @escaping () -> Void, updateTextInputStateAndMode: @escaping ((ChatTextInputState, ChatInputMode) -> (ChatTextInputState, ChatInputMode)) -> Void, updateInputModeAndDismissedButtonKeyboardMessageId: @escaping ((ChatPresentationInterfaceState) -> (ChatInputMode, MessageId?)) -> Void, @@ -270,7 +273,7 @@ public final class ChatPanelInterfaceInteraction { joinGroupCall: @escaping (CachedChannelData.ActiveCall) -> Void, presentInviteMembers: @escaping () -> Void, presentGigagroupHelp: @escaping () -> Void, - openSuggestPost: @escaping () -> Void, + openMonoforum: @escaping () -> Void, editMessageMedia: @escaping (MessageId, Bool) -> Void, updateShowCommands: @escaping ((Bool) -> Bool) -> Void, updateShowSendAsPeers: @escaping ((Bool) -> Bool) -> Void, @@ -288,6 +291,7 @@ public final class ChatPanelInterfaceInteraction { addDoNotTranslateLanguage: @escaping (String) -> Void, hideTranslationPanel: @escaping () -> Void, openPremiumGift: @escaping () -> Void, + openSuggestPost: @escaping () -> Void, openPremiumRequiredForMessaging: @escaping () -> Void, openStarsPurchase: @escaping (Int64?) -> Void, openMessagePayment: @escaping () -> Void, @@ -319,6 +323,7 @@ public final class ChatPanelInterfaceInteraction { self.presentForwardOptions = presentForwardOptions self.presentReplyOptions = presentReplyOptions self.presentLinkOptions = presentLinkOptions + self.presentSuggestPostOptions = presentSuggestPostOptions self.shareSelectedMessages = shareSelectedMessages self.updateTextInputStateAndMode = updateTextInputStateAndMode self.updateInputModeAndDismissedButtonKeyboardMessageId = updateInputModeAndDismissedButtonKeyboardMessageId @@ -391,7 +396,7 @@ public final class ChatPanelInterfaceInteraction { self.joinGroupCall = joinGroupCall self.presentInviteMembers = presentInviteMembers self.presentGigagroupHelp = presentGigagroupHelp - self.openSuggestPost = openSuggestPost + self.openMonoforum = openMonoforum self.updateShowCommands = updateShowCommands self.updateShowSendAsPeers = updateShowSendAsPeers self.openInviteRequests = openInviteRequests @@ -408,6 +413,7 @@ public final class ChatPanelInterfaceInteraction { self.addDoNotTranslateLanguage = addDoNotTranslateLanguage self.hideTranslationPanel = hideTranslationPanel self.openPremiumGift = openPremiumGift + self.openSuggestPost = openSuggestPost self.openPremiumRequiredForMessaging = openPremiumRequiredForMessaging self.openStarsPurchase = openStarsPurchase self.openMessagePayment = openMessagePayment @@ -447,6 +453,7 @@ public final class ChatPanelInterfaceInteraction { }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { }, shareSelectedMessages: { }, updateTextInputStateAndMode: updateTextInputStateAndMode, updateInputModeAndDismissedButtonKeyboardMessageId: updateInputModeAndDismissedButtonKeyboardMessageId, openStickers: { }, editMessage: { @@ -519,7 +526,7 @@ public final class ChatPanelInterfaceInteraction { }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { - }, openSuggestPost: { + }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in @@ -537,6 +544,7 @@ public final class ChatPanelInterfaceInteraction { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { + }, openSuggestPost: { }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift b/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift index 247f72656b..a3dff435f1 100644 --- a/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift +++ b/submodules/ChatPresentationInterfaceState/Sources/ChatTextInputPanelState.swift @@ -11,6 +11,7 @@ public enum ChatTextInputAccessoryItem: Equatable { case messageAutoremoveTimeout case scheduledMessages case gift + case suggestPost } public enum InputMode: Hashable { @@ -27,6 +28,7 @@ public enum ChatTextInputAccessoryItem: Equatable { case messageAutoremoveTimeout(Int32?) case scheduledMessages case gift + case suggestPost public var key: Key { switch self { @@ -44,6 +46,8 @@ public enum ChatTextInputAccessoryItem: Equatable { return .scheduledMessages case .gift: return .gift + case .suggestPost: + return .suggestPost } } } diff --git a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift index 006d640616..11d5cbe41c 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/IncomingMessagePrivacyScreen.swift @@ -376,13 +376,18 @@ public func incomingMessagePrivacyScreen(context: AccountContext, value: GlobalP if case let .paidMessages(value) = stateValue.with({ $0 }).updatedValue { currentAmount = value } - let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount(current: currentAmount, minValue: StarsAmount(value: 1, nanos: 0), fractionAfterCommission: 80, kind: .privacy), completion: { amount in - updateState { state in - var state = state - state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0)) - return state + let starsScreen = context.sharedContext.makeStarsWithdrawalScreen(context: context, subject: .enterAmount( + current: currentAmount, + minValue: StarsAmount(value: 1, nanos: 0), + fractionAfterCommission: 80, kind: .privacy, + completion: { amount in + updateState { state in + var state = state + state.updatedValue = .paidMessages(StarsAmount(value: amount, nanos: 0)) + return state + } } - }) + )) pushControllerImpl?(starsScreen) } ) diff --git a/submodules/TelegramCore/Sources/ForumChannels.swift b/submodules/TelegramCore/Sources/ForumChannels.swift index 2343e2d27a..a4414d7c74 100644 --- a/submodules/TelegramCore/Sources/ForumChannels.swift +++ b/submodules/TelegramCore/Sources/ForumChannels.swift @@ -68,6 +68,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { case isHidden case notificationSettings case isMarkedUnread + case isMessageFeeRemoved } public var creationDate: Int32 @@ -82,6 +83,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { public var isClosed: Bool public var isHidden: Bool public var notificationSettings: TelegramPeerNotificationSettings + public var isMessageFeeRemoved: Bool public init( creationDate: Int32, @@ -95,7 +97,8 @@ public struct MessageHistoryThreadData: Codable, Equatable { maxOutgoingReadId: Int32, isClosed: Bool, isHidden: Bool, - notificationSettings: TelegramPeerNotificationSettings + notificationSettings: TelegramPeerNotificationSettings, + isMessageFeeRemoved: Bool ) { self.creationDate = creationDate self.isOwnedByMe = isOwnedByMe @@ -109,6 +112,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.isClosed = isClosed self.isHidden = isHidden self.notificationSettings = notificationSettings + self.isMessageFeeRemoved = isMessageFeeRemoved } public init(from decoder: Decoder) throws { @@ -126,6 +130,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { self.isClosed = try container.decodeIfPresent(Bool.self, forKey: .isClosed) ?? false self.isHidden = try container.decodeIfPresent(Bool.self, forKey: .isHidden) ?? false self.notificationSettings = try container.decode(TelegramPeerNotificationSettings.self, forKey: .notificationSettings) + self.isMessageFeeRemoved = try container.decodeIfPresent(Bool.self, forKey: .isMessageFeeRemoved) ?? false } public func encode(to encoder: Encoder) throws { @@ -143,6 +148,7 @@ public struct MessageHistoryThreadData: Codable, Equatable { try container.encode(self.isClosed, forKey: .isClosed) try container.encode(self.isHidden, forKey: .isHidden) try container.encode(self.notificationSettings, forKey: .notificationSettings) + try container.encode(self.isMessageFeeRemoved, forKey: .isMessageFeeRemoved) } } @@ -668,7 +674,8 @@ public func _internal_fillSavedMessageHistory(accountPeerId: PeerId, postbox: Po maxOutgoingReadId: 0, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: false ), topMessage: message.id.id, unreadMentionsCount: 0, @@ -786,7 +793,8 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post maxOutgoingReadId: 0, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: false ) var topTimestamp: Int32 = 1 @@ -840,7 +848,8 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post maxOutgoingReadId: readOutboxMaxId, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: (flags & (1 << 4)) != 0 ) var topTimestamp: Int32 = 1 @@ -989,7 +998,8 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0, - notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) + notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings), + isMessageFeeRemoved: false ) var topTimestamp = date diff --git a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift index a24348e934..87b82a2ab2 100644 --- a/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/State/AccountStateManagementUtils.swift @@ -2107,7 +2107,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0, - notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) + notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings), + isMessageFeeRemoved: false ), topMessageId: topMessage, unreadMentionCount: unreadMentionsCount, @@ -2140,7 +2141,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: (flags & (1 << 4)) != 0 ), topMessageId: topMessage, unreadMentionCount: 0, @@ -2266,7 +2268,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0, - notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) + notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings), + isMessageFeeRemoved: false ) if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: Int64(id), info: entry) @@ -2296,7 +2299,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: (flags & (1 << 4)) != 0 ) if let entry = StoredMessageHistoryThreadInfo(data) { transaction.setMessageHistoryThreadInfo(peerId: peerId, threadId: peer.peerId.toInt64(), info: entry) @@ -2429,7 +2433,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: (flags & (1 << 2)) != 0, isHidden: (flags & (1 << 6)) != 0, - notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings) + notificationSettings: TelegramPeerNotificationSettings(apiSettings: notifySettings), + isMessageFeeRemoved: false ), topMessageId: topMessage, unreadMentionCount: unreadMentionsCount, @@ -2459,7 +2464,8 @@ func resolveForumThreads(accountPeerId: PeerId, postbox: Postbox, source: FetchM maxOutgoingReadId: readOutboxMaxId, isClosed: false, isHidden: false, - notificationSettings: TelegramPeerNotificationSettings.defaultSettings + notificationSettings: TelegramPeerNotificationSettings.defaultSettings, + isMessageFeeRemoved: (flags & (1 << 4)) != 0 ), topMessageId: topMessage, unreadMentionCount: 0, diff --git a/submodules/TelegramCore/Sources/State/PaidMessages.swift b/submodules/TelegramCore/Sources/State/PaidMessages.swift index 57930c939e..a8507fb621 100644 --- a/submodules/TelegramCore/Sources/State/PaidMessages.swift +++ b/submodules/TelegramCore/Sources/State/PaidMessages.swift @@ -3,15 +3,26 @@ import TelegramApi import Postbox import SwiftSignalKit -func _internal_getPaidMessagesRevenue(account: Account, peerId: PeerId) -> Signal { - return account.postbox.transaction { transaction -> Api.InputUser? in - return transaction.getPeer(peerId).flatMap(apiInputUser) +func _internal_getPaidMessagesRevenue(account: Account, scopePeerId: PeerId, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputUser?) in + return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser)) } - |> mapToSignal { inputUser -> Signal in + |> mapToSignal { scopeInputPeer, inputUser -> Signal in + if scopePeerId != account.peerId { + if scopeInputPeer == nil { + return .never() + } + } guard let inputUser else { return .single(nil) } - return account.network.request(Api.functions.account.getPaidMessagesRevenue(flags: 0, parentPeer: nil, userId: inputUser)) + + var flags: Int32 = 0 + if scopePeerId != account.peerId, scopeInputPeer != nil { + flags |= 1 << 0 + } + + return account.network.request(Api.functions.account.getPaidMessagesRevenue(flags: 0, parentPeer: scopeInputPeer, userId: inputUser)) |> map(Optional.init) |> `catch` { _ -> Signal in return .single(nil) @@ -28,11 +39,16 @@ func _internal_getPaidMessagesRevenue(account: Account, peerId: PeerId) -> Signa } } -func _internal_addNoPaidMessagesException(account: Account, peerId: PeerId, refundCharged: Bool) -> Signal { - return account.postbox.transaction { transaction -> Api.InputUser? in - return transaction.getPeer(peerId).flatMap(apiInputUser) +func _internal_addNoPaidMessagesException(account: Account, scopePeerId: PeerId, peerId: PeerId, refundCharged: Bool) -> Signal { + return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputUser?) in + return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser)) } - |> mapToSignal { inputUser -> Signal in + |> mapToSignal { scopeInputPeer, inputUser -> Signal in + if scopePeerId != account.peerId { + if scopeInputPeer == nil { + return .never() + } + } guard let inputUser else { return .never() } @@ -40,19 +56,33 @@ func _internal_addNoPaidMessagesException(account: Account, peerId: PeerId, refu if refundCharged { flags |= (1 << 0) } - return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: nil, userId: inputUser)) + if scopePeerId != account.peerId, scopeInputPeer != nil { + flags |= (1 << 1) + } + return account.network.request(Api.functions.account.toggleNoPaidMessagesException(flags: flags, parentPeer: scopeInputPeer, userId: inputUser)) |> `catch` { _ -> Signal in return .single(.boolFalse) } |> mapToSignal { _ in return account.postbox.transaction { transaction -> Void in - transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in - if let cachedData = cachedData as? CachedUserData { - var settings = cachedData.peerStatusSettings ?? .init() - settings.paidMessageStars = nil - return cachedData.withUpdatedPeerStatusSettings(settings) + if scopePeerId != account.peerId, scopeInputPeer != nil { + guard var data = transaction.getMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64())?.data.get(MessageHistoryThreadData.self) else { + return } - return cachedData - }) + data.isMessageFeeRemoved = true + + if let entry = StoredMessageHistoryThreadInfo(data) { + transaction.setMessageHistoryThreadInfo(peerId: scopePeerId, threadId: peerId.toInt64(), info: entry) + } + } else { + transaction.updatePeerCachedData(peerIds: Set([peerId]), update: { _, cachedData in + if let cachedData = cachedData as? CachedUserData { + var settings = cachedData.peerStatusSettings ?? .init() + settings.paidMessageStars = nil + return cachedData.withUpdatedPeerStatusSettings(settings) + } + return cachedData + }) + } } |> ignoreValues } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift index d05aa6dd5b..2c070b3e45 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Peers/TelegramEnginePeers.swift @@ -1494,12 +1494,12 @@ public extension TelegramEngine { return _internal_applyChannelBoost(account: self.account, peerId: peerId, slots: slots) } - public func getPaidMessagesRevenue(peerId: EnginePeer.Id) -> Signal { - return _internal_getPaidMessagesRevenue(account: self.account, peerId: peerId) + public func getPaidMessagesRevenue(scopePeerId: EnginePeer.Id, peerId: EnginePeer.Id) -> Signal { + return _internal_getPaidMessagesRevenue(account: self.account, scopePeerId: scopePeerId, peerId: peerId) } - public func addNoPaidMessagesException(peerId: EnginePeer.Id, refundCharged: Bool) -> Signal { - return _internal_addNoPaidMessagesException(account: self.account, peerId: peerId, refundCharged: refundCharged) + public func addNoPaidMessagesException(scopePeerId: EnginePeer.Id, peerId: EnginePeer.Id, refundCharged: Bool) -> Signal { + return _internal_addNoPaidMessagesException(account: self.account, scopePeerId: scopePeerId, peerId: peerId, refundCharged: refundCharged) } public func updateChannelPaidMessagesStars(peerId: EnginePeer.Id, stars: StarsAmount?, broadcastMessagesAllowed: Bool) -> Signal { diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift index 150061ba27..1abc3d3ac9 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourceKey.swift @@ -224,6 +224,7 @@ public enum PresentationResourceKey: Int32 { case chatInputTextFieldTimerImage case chatInputTextFieldScheduleImage case chatInputTextFieldGiftImage + case chatInputTextFieldSuggestPostImage case chatInputSearchPanelUpImage case chatInputSearchPanelUpDisabledImage diff --git a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift index 6826b80f08..76e5474a83 100644 --- a/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift +++ b/submodules/TelegramPresentationData/Sources/Resources/PresentationResourcesChat.swift @@ -562,6 +562,12 @@ public struct PresentationResourcesChat { }) } + public static func chatInputTextFieldSuggestPostImage(_ theme: PresentationTheme) -> UIImage? { + return theme.image(PresentationResourceKey.chatInputTextFieldSuggestPostImage.rawValue, { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconSuggestPost"), color: theme.chat.inputPanel.inputControlColor) + }) + } + public static func chatInputTextFieldKeyboardImage(_ theme: PresentationTheme) -> UIImage? { return theme.image(PresentationResourceKey.chatInputTextFieldKeyboardImage.rawValue, { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Text/AccessoryIconKeyboard"), color: theme.chat.inputPanel.inputControlColor) diff --git a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift index 885f479a43..c94503abf7 100644 --- a/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatChannelSubscriberInputPanelNode/Sources/ChatChannelSubscriberInputPanelNode.swift @@ -228,7 +228,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode { } @objc private func suggestedPostPressed() { - self.interfaceInteraction?.openSuggestPost() + self.interfaceInteraction?.openMonoforum() } @objc private func buttonPressed() { diff --git a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift index 3468dc3172..be73391e62 100644 --- a/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatLoadingNode/Sources/ChatLoadingNode.swift @@ -109,7 +109,7 @@ public final class ChatLoadingPlaceholderMessageContainer { } } - public func update(size: CGSize, hasAvatar: Bool, rect: CGRect, transition: ContainedViewLayoutTransition) { + public func update(size: CGSize, isSidebarOpen: Bool, hasAvatar: Bool, rect: CGRect, transition: ContainedViewLayoutTransition) { var avatarOffset: CGFloat = 0.0 if hasAvatar && self.avatarNode == nil { @@ -127,12 +127,24 @@ public final class ChatLoadingPlaceholderMessageContainer { } if let avatarNode = self.avatarNode, let avatarBorderNode = self.avatarBorderNode { - let avatarFrame = CGRect(origin: CGPoint(x: rect.minX + 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize) + var avatarFrame = CGRect(origin: CGPoint(x: rect.minX + 3.0, y: rect.maxY + 1.0 - avatarSize.height), size: avatarSize) + if isSidebarOpen { + avatarFrame.origin.x -= avatarFrame.width * 0.5 + avatarFrame.origin.y += avatarFrame.height * 0.5 + } - transition.updateFrame(node: avatarNode, frame: avatarFrame) - transition.updateFrame(node: avatarBorderNode, frame: avatarFrame) + transition.updatePosition(node: avatarNode, position: avatarFrame.center) + transition.updateBounds(node: avatarNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) + transition.updateTransformScale(node: avatarNode, scale: isSidebarOpen ? 0.001 : 1.0) + transition.updateAlpha(node: avatarNode, alpha: isSidebarOpen ? 0.0 : 1.0) + transition.updatePosition(node: avatarBorderNode, position: avatarFrame.center) + transition.updateBounds(node: avatarBorderNode, bounds: CGRect(origin: CGPoint(), size: avatarFrame.size)) + transition.updateTransformScale(node: avatarBorderNode, scale: isSidebarOpen ? 0.001 : 1.0) + transition.updateAlpha(node: avatarBorderNode, alpha: isSidebarOpen ? 0.0 : 1.0) - avatarOffset += avatarSize.width - 1.0 + if !isSidebarOpen { + avatarOffset += avatarSize.width - 1.0 + } } let bubbleFrame = CGRect(origin: CGPoint(x: rect.minX + 3.0 + avatarOffset, y: rect.origin.y), size: CGSize(width: rect.width, height: rect.height)) @@ -161,7 +173,7 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { private var absolutePosition: (CGRect, CGSize)? - private var validLayout: (CGSize, UIEdgeInsets, LayoutMetrics)? + private var validLayout: (CGSize, Bool, UIEdgeInsets, LayoutMetrics)? public init(context: AccountContext, theme: PresentationTheme, chatWallpaper: TelegramWallpaper, bubbleCorners: PresentationChatBubbleCorners, backgroundNode: WallpaperBackgroundNode) { self.context = context @@ -295,7 +307,7 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { private var didAnimateOut = false public func animateOut(_ historyNode: ListView, completion: @escaping () -> Void = {}) { - guard let (size, _, _) = self.validLayout else { + guard let (size, isSidebarOpen, _, _) = self.validLayout else { return } let listNode = historyNode @@ -376,7 +388,7 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { let messageContainer = self.messageContainers[k] let messageSize = messageContainer.frame.size - messageContainer.update(size: size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition) + messageContainer.update(size: size, isSidebarOpen: isSidebarOpen, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: 0.0, y: offset - messageSize.height), size: messageSize), transition: transition) offset -= messageSize.height } } @@ -439,14 +451,14 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { if self.chatType != chatType { self.chatType = chatType - if let (size, insets, metrics) = self.validLayout { - self.updateLayout(size: size, insets: insets, metrics: metrics, transition: .immediate) + if let (size, isSidebarOpen, insets, metrics) = self.validLayout { + self.updateLayout(size: size, isSidebarOpen: isSidebarOpen, insets: insets, metrics: metrics, transition: .immediate) } } } - public func updateLayout(size: CGSize, insets: UIEdgeInsets, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition) { - self.validLayout = (size, insets, metrics) + public func updateLayout(size: CGSize, isSidebarOpen: Bool, insets: UIEdgeInsets, metrics: LayoutMetrics, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, isSidebarOpen, insets, metrics) let bounds = CGRect(origin: .zero, size: size) @@ -500,7 +512,7 @@ public final class ChatLoadingPlaceholderNode: ASDisplayNode { for messageContainer in self.messageContainers { let messageSize = dimensions[index % 14] - messageContainer.update(size: bounds.size, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: insets.left, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition) + messageContainer.update(size: bounds.size, isSidebarOpen: isSidebarOpen, hasAvatar: self.chatType != .channel && self.chatType != .user, rect: CGRect(origin: CGPoint(x: insets.left, y: bounds.size.height - insets.bottom - offset - messageSize.height), size: messageSize), transition: transition) offset += messageSize.height index += 1 } diff --git a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift index b81a7cac0d..38d171727a 100644 --- a/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatMessageBubbleItemNode/Sources/ChatMessageBubbleItemNode.swift @@ -2436,7 +2436,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI if isSidePanelOpen && incoming { hasTitleAvatar = true - hasTitleTopicNavigation = item.chatLocation.threadId == nil + + if let channel = item.message.peers[item.message.id.peerId], channel.isMonoForum { + } else { + hasTitleTopicNavigation = item.chatLocation.threadId == nil + } } let inlineBotNameColor = messageTheme.accentTextColor diff --git a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift index 2250de1168..2e1d7dc66b 100644 --- a/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Components/Chat/ChatRecentActionsController/Sources/ChatRecentActionsController.swift @@ -72,6 +72,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { }, shareSelectedMessages: { }, updateTextInputStateAndMode: { _ in }, updateInputModeAndDismissedButtonKeyboardMessageId: { _ in @@ -146,7 +147,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { - }, openSuggestPost: { + }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in @@ -164,6 +165,7 @@ public final class ChatRecentActionsController: TelegramBaseController { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { + }, openSuggestPost: { }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD new file mode 100644 index 0000000000..885c055cd0 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/BUILD @@ -0,0 +1,37 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "SuggestPostAccessoryPanelNode", + module_name = "SuggestPostAccessoryPanelNode", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/AsyncDisplayKit", + "//submodules/TelegramCore", + "//submodules/Postbox", + "//submodules/SSignalKit/SwiftSignalKit", + "//submodules/Display", + "//submodules/TelegramPresentationData", + "//submodules/TelegramUIPreferences", + "//submodules/AccountContext", + "//submodules/LocalizedPeerData", + "//submodules/PhotoResources", + "//submodules/TelegramStringFormatting", + "//submodules/TextFormat", + "//submodules/ChatPresentationInterfaceState", + "//submodules/TelegramUI/Components/TextNodeWithEntities", + "//submodules/TelegramUI/Components/AnimationCache", + "//submodules/TelegramUI/Components/MultiAnimationRenderer", + "//submodules/TelegramUI/Components/Chat/AccessoryPanelNode", + "//submodules/TelegramUI/Components/CompositeTextNode", + "//submodules/TelegramNotices", + ], + visibility = [ + "//visibility:public", + ], +) + diff --git a/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift new file mode 100644 index 0000000000..f6159b5f94 --- /dev/null +++ b/submodules/TelegramUI/Components/Chat/SuggestPostAccessoryPanelNode/Sources/SuggestPostAccessoryPanelNode.swift @@ -0,0 +1,315 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import TelegramCore +import Postbox +import SwiftSignalKit +import Display +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import LocalizedPeerData +import PhotoResources +import TelegramStringFormatting +import TextFormat +import ChatPresentationInterfaceState +import TextNodeWithEntities +import AnimationCache +import MultiAnimationRenderer +import AccessoryPanelNode +import TelegramNotices +import AppBundle +import CompositeTextNode + +public final class SuggestPostAccessoryPanelNode: AccessoryPanelNode { + private var previousMediaReference: AnyMediaReference? + + public let closeButton: HighlightableButtonNode + public let lineNode: ASImageNode + public let iconView: UIImageView + public let titleNode: CompositeTextNode + public let textNode: ImmediateTextNodeWithEntities + + private let actionArea: AccessibilityAreaNode + + private let context: AccountContext + public var theme: PresentationTheme + public var strings: PresentationStrings + + private var textIsOptions: Bool = false + + private var validLayout: (size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState)? + + private var inlineTextStarImage: UIImage? + + public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, nameDisplayOrder: PresentationPersonNameOrder, dateTimeFormat: PresentationDateTimeFormat, animationCache: AnimationCache?, animationRenderer: MultiAnimationRenderer?) { + self.context = context + self.theme = theme + self.strings = strings + + self.closeButton = HighlightableButtonNode() + self.closeButton.accessibilityLabel = strings.VoiceOver_DiscardPreparedContent + self.closeButton.hitTestSlop = UIEdgeInsets(top: -8.0, left: -8.0, bottom: -8.0, right: -8.0) + self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) + self.closeButton.displaysAsynchronously = false + + self.lineNode = ASImageNode() + self.lineNode.displayWithoutProcessing = true + self.lineNode.displaysAsynchronously = false + self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme) + + self.iconView = UIImageView() + self.iconView.image = UIImage(bundleImageName: "Chat/Input/Accessory Panels/SuggestPostIcon")?.withRenderingMode(.alwaysTemplate) + self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor + + self.titleNode = CompositeTextNode() + + self.textNode = ImmediateTextNodeWithEntities() + self.textNode.maximumNumberOfLines = 1 + self.textNode.displaysAsynchronously = false + self.textNode.insets = UIEdgeInsets(top: 3.0, left: 0.0, bottom: 3.0, right: 0.0) + self.textNode.visibility = true + self.textNode.spoilerColor = self.theme.chat.inputPanel.secondaryTextColor + + if let animationCache = animationCache, let animationRenderer = animationRenderer { + self.textNode.arguments = TextNodeWithEntities.Arguments( + context: context, + cache: animationCache, + renderer: animationRenderer, + placeholderColor: theme.list.mediaPlaceholderColor, + attemptSynchronous: false + ) + } + + self.actionArea = AccessibilityAreaNode() + + super.init() + + self.closeButton.addTarget(self, action: #selector(self.closePressed), forControlEvents: [.touchUpInside]) + self.addSubnode(self.closeButton) + + self.addSubnode(self.lineNode) + self.view.addSubview(self.iconView) + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.actionArea) + + //TODO:localize + var titleText: [CompositeTextNode.Component] = [] + titleText.append(.text(NSAttributedString(string: "Suggest a post below", font: Font.medium(15.0), textColor: self.theme.chat.inputPanel.panelControlAccentColor))) + self.titleNode.components = titleText + } + + deinit { + } + + override public func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) + } + + override public func animateIn() { + self.iconView.layer.animateScale(from: 0.001, to: 1.0, duration: 0.2) + } + + override public func animateOut() { + self.iconView.layer.animateScale(from: 1.0, to: 0.001, duration: 0.2, removeOnCompletion: false) + } + + override public func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings) { + self.updateThemeAndStrings(theme: theme, strings: strings, force: false) + } + + private func updateThemeAndStrings(theme: PresentationTheme, strings: PresentationStrings, force: Bool) { + if self.theme !== theme || force { + self.theme = theme + + self.closeButton.setImage(PresentationResourcesChat.chatInputPanelCloseIconImage(theme), for: []) + + self.lineNode.image = PresentationResourcesChat.chatInputPanelVerticalSeparatorLineImage(theme) + self.iconView.tintColor = theme.chat.inputPanel.panelControlAccentColor + + self.titleNode.components = self.titleNode.components.map { item in + switch item { + case let .text(text): + let updatedText = NSMutableAttributedString(attributedString: text) + updatedText.addAttribute(.foregroundColor, value: theme.chat.inputPanel.panelControlAccentColor, range: NSRange(location: 0, length: updatedText.length)) + return .text(updatedText) + case let .icon(icon): + if let iconImage = generateTintedImage(image: icon, color: theme.chat.inputPanel.panelControlAccentColor) { + return .icon(iconImage) + } else { + return .icon(icon) + } + } + } + + if let text = self.textNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: text) + updatedText.addAttribute(.foregroundColor, value: self.textIsOptions ? self.theme.chat.inputPanel.secondaryTextColor : self.theme.chat.inputPanel.primaryTextColor, range: NSRange(location: 0, length: updatedText.length)) + self.textNode.attributedText = updatedText + } + self.textNode.spoilerColor = self.theme.chat.inputPanel.secondaryTextColor + + if let (size, inset, interfaceState) = self.validLayout { + self.updateState(size: size, inset: inset, interfaceState: interfaceState) + } + } + } + + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 45.0) + } + + override public func updateState(size: CGSize, inset: CGFloat, interfaceState: ChatPresentationInterfaceState) { + self.validLayout = (size, inset, interfaceState) + + let bounds = CGRect(origin: CGPoint(), size: CGSize(width: size.width, height: 45.0)) + let leftInset: CGFloat = 55.0 + inset + let textLineInset: CGFloat = 10.0 + let rightInset: CGFloat = 55.0 + let textRightInset: CGFloat = 20.0 + + let closeButtonSize = CGSize(width: 44.0, height: bounds.height) + let closeButtonFrame = CGRect(origin: CGPoint(x: bounds.width - closeButtonSize.width - inset, y: 2.0), size: closeButtonSize) + self.closeButton.frame = closeButtonFrame + + self.actionArea.frame = CGRect(origin: CGPoint(x: leftInset, y: 2.0), size: CGSize(width: closeButtonFrame.minX - leftInset, height: bounds.height)) + + if self.lineNode.supernode == self { + self.lineNode.frame = CGRect(origin: CGPoint(x: leftInset, y: 8.0), size: CGSize(width: 2.0, height: bounds.size.height - 10.0)) + } + + if let icon = self.iconView.image { + self.iconView.frame = CGRect(origin: CGPoint(x: 7.0 + inset, y: 10.0), size: icon.size) + } + + let imageTextInset: CGFloat = 0.0 + + let titleSize = self.titleNode.update(constrainedSize: CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height)) + if self.titleNode.supernode == self { + self.titleNode.frame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset, y: 7.0), size: titleSize) + } + + let textFont = Font.regular(15.0) + + var inlineTextStarImage: UIImage? + if let current = self.inlineTextStarImage { + inlineTextStarImage = current + } else { + if let image = UIImage(bundleImageName: "Premium/Stars/StarSmall") { + let starInsets = UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0) + inlineTextStarImage = generateImage(CGSize(width: starInsets.left + image.size.width + starInsets.right, height: image.size.height), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + UIGraphicsPushContext(context) + defer { + UIGraphicsPopContext() + } + + image.draw(at: CGPoint(x: starInsets.left, y: starInsets.top)) + })?.withRenderingMode(.alwaysOriginal) + self.inlineTextStarImage = inlineTextStarImage + } + } + + let textString: NSAttributedString + if let postSuggestionState = interfaceState.interfaceState.postSuggestionState, postSuggestionState.price != 0 { + if let timestamp = postSuggestionState.timestamp { + let timeString = humanReadableStringForTimestamp(strings: interfaceState.strings, dateTimeFormat: interfaceState.dateTimeFormat, timestamp: timestamp, alwaysShowTime: true, allowYesterday: false, format: HumanReadableStringFormat( + dateFormatString: { value in + return PresentationStrings.FormattedString(string: interfaceState.strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: []) + }, + tomorrowFormatString: { value in + return PresentationStrings.FormattedString(string: interfaceState.strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: []) + }, + todayFormatString: { value in + return PresentationStrings.FormattedString(string: interfaceState.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + }, + yesterdayFormatString: { value in + return PresentationStrings.FormattedString(string: interfaceState.strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + } + )).string + textString = NSAttributedString(string: "#\(postSuggestionState.price) 📅 \(timeString)", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) + } else { + textString = NSAttributedString(string: "#\(postSuggestionState.price) for publishing anytime", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) + } + } else { + textString = NSAttributedString(string: "Tap to offer a price for publishing", font: textFont, textColor: self.theme.chat.inputPanel.primaryTextColor) + } + + let mutableTextString = NSMutableAttributedString(attributedString: textString) + if let range = mutableTextString.string.range(of: "#"), let starImage = inlineTextStarImage { + final class RunDelegateData { + let ascent: CGFloat + let descent: CGFloat + let width: CGFloat + + init(ascent: CGFloat, descent: CGFloat, width: CGFloat) { + self.ascent = ascent + self.descent = descent + self.width = width + } + } + + let runDelegateData = RunDelegateData( + ascent: Font.regular(15.0).ascender, + descent: Font.regular(15.0).descender, + width: starImage.size.width + 2.0 + ) + var callbacks = CTRunDelegateCallbacks( + version: kCTRunDelegateCurrentVersion, + dealloc: { dataRef in + Unmanaged.fromOpaque(dataRef).release() + }, + getAscent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().ascent + }, + getDescent: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().descent + }, + getWidth: { dataRef in + let data = Unmanaged.fromOpaque(dataRef) + return data.takeUnretainedValue().width + } + ) + if let runDelegate = CTRunDelegateCreate(&callbacks, Unmanaged.passRetained(runDelegateData).toOpaque()) { + mutableTextString.addAttribute(NSAttributedString.Key(kCTRunDelegateAttributeName as String), value: runDelegate, range: NSRange(range, in: mutableTextString.string)) + } + mutableTextString.addAttribute(.attachment, value: starImage, range: NSRange(range, in: mutableTextString.string)) + mutableTextString.addAttribute(.foregroundColor, value: UIColor(rgb: 0xffffff), range: NSRange(range, in: mutableTextString.string)) + mutableTextString.addAttribute(.baselineOffset, value: 1.0, range: NSRange(range, in: mutableTextString.string)) + } + + self.textNode.attributedText = mutableTextString + + let textSize = self.textNode.updateLayout(CGSize(width: bounds.size.width - leftInset - textLineInset - rightInset - textRightInset - imageTextInset, height: bounds.size.height)) + let textFrame = CGRect(origin: CGPoint(x: leftInset + textLineInset + imageTextInset - self.textNode.insets.left, y: 25.0 - self.textNode.insets.top), size: textSize) + if self.textNode.supernode == self { + self.textNode.frame = textFrame + } + } + + @objc private func closePressed() { + if let dismiss = self.dismiss { + dismiss() + } + } + + private var previousTapTimestamp: Double? + @objc private func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + let timestamp = CFAbsoluteTimeGetCurrent() + if let previousTapTimestamp = self.previousTapTimestamp, previousTapTimestamp + 1.0 > timestamp { + return + } + self.previousTapTimestamp = CFAbsoluteTimeGetCurrent() + self.interfaceInteraction?.presentSuggestPostOptions() + Queue.mainQueue().after(1.5) { + self.updateThemeAndStrings(theme: self.theme, strings: self.strings, force: true) + } + } + } +} diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index a08700635c..f0b52601ef 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -335,6 +335,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, presentForwardOptions: { _ in }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { }, shareSelectedMessages: { shareMessages() }, updateTextInputStateAndMode: { _ in @@ -410,7 +411,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { - }, openSuggestPost: { + }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in @@ -429,6 +430,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { + }, openSuggestPost: { }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { @@ -1252,6 +1254,7 @@ private enum InfoSection: Int, CaseIterable { case peerInfoTrailing case peerSettings case peerMembers + case channelMonoforum case botAffiliateProgram } @@ -1698,6 +1701,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese let ItemMemberRequests = 8 let ItemBalance = 9 let ItemEdit = 10 + let ItemPeerPersonalChannel = 11 if let _ = data.threadData { let mainUsername: String @@ -1935,6 +1939,21 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese interaction.openEditing() })) } + + if 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 { + return nil + } + return interaction.getController() + }, action: { [weak interaction] in + guard let interaction else { + return + } + interaction.openChat(peerId) + })) + } } } } else if let group = data.peer as? TelegramGroup { diff --git a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift index 96596c87a5..1fa4af6465 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen/Sources/PostSuggestionsSettingsScreen.swift @@ -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.paidMessageCommissionPermille / 10, kind: .postSuggestion, completion: { [weak self] amount in guard let self else { return } @@ -382,7 +382,7 @@ final class PostSuggestionsSettingsScreenComponent: Component { if !self.isUpdating { self.state?.updated(transition: .immediate) } - }) + })) environment.controller()?.push(starsScreen) }, openPremiumInfo: nil diff --git a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift index a6655583a8..4c75661a30 100644 --- a/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift +++ b/submodules/TelegramUI/Components/PeerSelectionController/Sources/PeerSelectionControllerNode.swift @@ -594,6 +594,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { strongSelf.controller?.presentInGlobalOverlay(contextController) }, presentReplyOptions: { _ in }, presentLinkOptions: { _ in + }, presentSuggestPostOptions: { }, shareSelectedMessages: { }, updateTextInputStateAndMode: { [weak self] f in if let strongSelf = self { @@ -799,7 +800,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, joinGroupCall: { _ in }, presentInviteMembers: { }, presentGigagroupHelp: { - }, openSuggestPost: { + }, openMonoforum: { }, editMessageMedia: { _, _ in }, updateShowCommands: { _ in }, updateShowSendAsPeers: { _ in @@ -817,6 +818,7 @@ final class PeerSelectionControllerNode: ASDisplayNode { }, addDoNotTranslateLanguage: { _ in }, hideTranslationPanel: { }, openPremiumGift: { + }, openSuggestPost: { }, openPremiumRequiredForMessaging: { }, openStarsPurchase: { _ in }, openMessagePayment: { diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD index 05069f6b5c..a4e640935e 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/BUILD @@ -37,6 +37,7 @@ swift_library( "//submodules/Components/BundleIconComponent", "//submodules/PasswordSetupUI", "//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController", + "//submodules/TelegramUI/Components/ChatScheduleTimeController", ], visibility = [ "//visibility:public", diff --git a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift index cb6a9b1f03..c9e34fe12f 100644 --- a/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift +++ b/submodules/TelegramUI/Components/Stars/StarsWithdrawalScreen/Sources/StarsWithdrawalScreen.swift @@ -21,6 +21,8 @@ import PresentationDataUtils import ListSectionComponent import TelegramStringFormatting import UndoUI +import ListActionItemComponent +import ChatScheduleTimeController private let amountTag = GenericComponentViewTag() @@ -29,25 +31,22 @@ private final class SheetContent: CombinedComponent { let context: AccountContext let mode: StarsWithdrawScreen.Mode + let controller: () -> ViewController? let dismiss: () -> Void init( context: AccountContext, mode: StarsWithdrawScreen.Mode, + controller: @escaping () -> ViewController?, dismiss: @escaping () -> Void ) { self.context = context self.mode = mode + self.controller = controller self.dismiss = dismiss } static func ==(lhs: SheetContent, rhs: SheetContent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.mode != rhs.mode { - return false - } return true } @@ -56,6 +55,7 @@ private final class SheetContent: CombinedComponent { let title = Child(Text.self) let amountSection = Child(ListSectionComponent.self) let amountAdditionalLabel = Child(MultilineTextComponent.self) + let timestampSection = Child(ListSectionComponent.self) let button = Child(ButtonComponent.self) let balanceTitle = Child(MultilineTextComponent.self) let balanceValue = Child(MultilineTextComponent.self) @@ -66,6 +66,8 @@ private final class SheetContent: CombinedComponent { let component = context.component let state = context.state + state.component = component + let controller = environment.controller let theme = environment.theme.withModalBlocksBackground() @@ -111,7 +113,7 @@ private final class SheetContent: CombinedComponent { let resaleConfiguration = StarsSubscriptionConfiguration.with(appConfiguration: component.context.currentAppConfiguration.with { $0 }) switch component.mode { - case let .withdraw(status): + case let .withdraw(status, _): titleString = environment.strings.Stars_Withdraw_Title amountTitle = environment.strings.Stars_Withdraw_AmountTitle amountPlaceholder = environment.strings.Stars_Withdraw_AmountPlaceholder @@ -144,20 +146,28 @@ private final class SheetContent: CombinedComponent { minAmount = StarsAmount(value: 1, nanos: 0) maxAmount = withdrawConfiguration.maxPaidMediaAmount.flatMap { StarsAmount(value: $0, nanos: 0) } - case let .starGiftResell(_, update): + case let .starGiftResell(_, update, _): titleString = update ? environment.strings.Stars_SellGift_EditTitle : environment.strings.Stars_SellGift_Title amountTitle = environment.strings.Stars_SellGift_AmountTitle amountPlaceholder = environment.strings.Stars_SellGift_AmountPlaceholder minAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMinAmount, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.starGiftResaleMaxAmount, nanos: 0) - case let .paidMessages(_, minAmountValue, _, _): + case let .paidMessages(_, minAmountValue, _, _, _): titleString = environment.strings.Stars_SendMessage_AdjustmentTitle amountTitle = environment.strings.Stars_SendMessage_AdjustmentSectionHeader amountPlaceholder = environment.strings.Stars_SendMessage_AdjustmentPlaceholder minAmount = StarsAmount(value: minAmountValue, nanos: 0) maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) + case .suggestedPost: + //TODO:localize + titleString = "Suggest Terms" + amountTitle = "ENTER A PRICE IN STARS" + amountPlaceholder = environment.strings.Stars_SendMessage_AdjustmentPlaceholder + + minAmount = StarsAmount(value: 1, nanos: 0) + maxAmount = StarsAmount(value: resaleConfiguration.paidMessageMaxAmount, nanos: 0) } let title = title.update( @@ -176,7 +186,7 @@ private final class SheetContent: CombinedComponent { balance = state.balance } else if case .reaction = component.mode { balance = state.balance - } else if case let .withdraw(starsState) = component.mode { + } else if case let .withdraw(starsState, _) = component.mode { balance = starsState.balances.availableBalance } else { balance = nil @@ -264,7 +274,7 @@ private final class SheetContent: CombinedComponent { } } )) - case let .reaction(starsToTop): + case let .reaction(starsToTop, _): let amountInfoString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString(environment.strings.Stars_SendStars_AmountInfo("\(starsToTop ?? 0)").string, attributes: amountMarkdownAttributes, textAlignment: .natural)) amountFooter = AnyComponent(MultilineTextComponent( text: .plain(amountInfoString), @@ -288,7 +298,7 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) - case let .paidMessages(_, _, fractionAfterCommission, _): + case let .paidMessages(_, _, fractionAfterCommission, _, _): let amountInfoString: NSAttributedString if let value = state.amount?.value, value > 0 { let fullValue: Int64 = Int64(value) * 1_000_000_000 * Int64(fractionAfterCommission) / 100 @@ -301,6 +311,16 @@ private final class SheetContent: CombinedComponent { text: .plain(amountInfoString), maximumNumberOfLines: 0 )) + case let .suggestedPost(mode, _, _, _): + switch mode { + case let .sender(channel): + //TODO:localize + let amountInfoString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Choose how many Stars you want to offer \(channel.compactDisplayTitle) to publish this message.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + amountFooter = AnyComponent(MultilineTextComponent( + text: .plain(amountInfoString), + maximumNumberOfLines: 0 + )) + } default: amountFooter = nil } @@ -358,6 +378,103 @@ private final class SheetContent: CombinedComponent { context.add(amountAdditionalLabel .position(CGPoint(x: context.availableSize.width - amountAdditionalLabel.size.width / 2.0 - sideInset - 16.0, y: contentSize.height - amountAdditionalLabel.size.height / 2.0))) } + + if case .suggestedPost = component.mode { + contentSize.height += 24.0 + + //TODO:localize + let timestampFooterString = NSAttributedString(attributedString: parseMarkdownIntoAttributedString("Select the date and time you want your message to be published.", attributes: amountMarkdownAttributes, textAlignment: .natural)) + let timestampFooter = AnyComponent(MultilineTextComponent( + text: .plain(timestampFooterString), + maximumNumberOfLines: 0 + )) + + let timeString: String + if let timestamp = state.timestamp { + timeString = humanReadableStringForTimestamp(strings: strings, dateTimeFormat: presentationData.dateTimeFormat, timestamp: timestamp, alwaysShowTime: true, allowYesterday: true, format: HumanReadableStringFormat( + dateFormatString: { value in + return PresentationStrings.FormattedString(string: strings.SuggestPost_SetTimeFormat_Date(value).string, ranges: []) + }, + tomorrowFormatString: { value in + return PresentationStrings.FormattedString(string: strings.SuggestPost_SetTimeFormat_TomorrowAt(value).string, ranges: []) + }, + todayFormatString: { value in + return PresentationStrings.FormattedString(string: strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + }, + yesterdayFormatString: { value in + return PresentationStrings.FormattedString(string: strings.SuggestPost_SetTimeFormat_TodayAt(value).string, ranges: []) + } + )).string + } else { + timeString = "Anytime" + } + + let timestampSection = timestampSection.update( + component: ListSectionComponent( + theme: theme, + header: nil, + footer: timestampFooter, + items: [AnyComponentWithIdentity( + id: "timestamp", + component: AnyComponent(ListActionItemComponent( + theme: theme, + title: AnyComponent(VStack([ + AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: "Time", + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemPrimaryTextColor + )), + maximumNumberOfLines: 1 + ))), + ], alignment: .left, spacing: 2.0)), + icon: ListActionItemComponent.Icon(component: AnyComponentWithIdentity(id: 0, component: AnyComponent(MultilineTextComponent( + text: .plain(NSAttributedString( + string: timeString, + font: Font.regular(presentationData.listsFontSize.baseDisplaySize), + textColor: environment.theme.list.itemSecondaryTextColor + )), + maximumNumberOfLines: 1 + )))), + accessory: .arrow, + action: { [weak state] _ in + guard let state else { + return + } + let component = state.component + guard case let .suggestedPost(mode, _, _, _) = component.mode else { + return + } + guard case let .sender(channel) = mode else { + return + } + + 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) }), peerId: channel.id, mode: .suggestPost, style: .default, currentTime: state.timestamp, minimalTime: nil, dismissByTapOutside: true, completion: { [weak state] time in + guard let state else { + return + } + state.timestamp = time == 0 ? nil : time + state.updated(transition: .immediate) + }) + component.controller()?.view.endEditing(true) + component.controller()?.present(controller, in: .window(.root)) + } + )) + )] + ), + environment: {}, + availableSize: CGSize(width: context.availableSize.width - sideInset * 2.0, height: .greatestFiniteMagnitude), + transition: context.transition + ) + context.add(timestampSection + .position(CGPoint(x: context.availableSize.width / 2.0, y: contentSize.height + timestampSection.size.height / 2.0)) + .clipsToBounds(true) + .cornerRadius(10.0) + ) + contentSize.height += timestampSection.size.height + } + contentSize.height += 32.0 let buttonString: String @@ -371,6 +488,16 @@ private final class SheetContent: CombinedComponent { } } else if case .paidMessages = component.mode { buttonString = environment.strings.Stars_SendMessage_AdjustmentAction + } else if case let .suggestedPost(mode, _, _, _) = component.mode { + //TODO:localize + switch mode { + case .sender: + if let amount = state.amount { + buttonString = "Offer # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" + } else { + buttonString = "Offer" + } + } } else if let amount = state.amount { buttonString = "\(environment.strings.Stars_Withdraw_Withdraw) # \(presentationStringsFormattedNumber(amount, environment.dateTimeFormat.groupingSeparator))" } else { @@ -393,7 +520,7 @@ private final class SheetContent: CombinedComponent { let amount = state.amount ?? StarsAmount.zero if amount > StarsAmount.zero { isButtonEnabled = true - } else if case let .paidMessages(_, minValue, _, _) = context.component.mode { + } else if case let .paidMessages(_, minValue, _, _, _) = context.component.mode { if minValue <= 0 { isButtonEnabled = true } @@ -414,11 +541,27 @@ private final class SheetContent: CombinedComponent { isEnabled: isButtonEnabled, displaysProgress: false, action: { [weak state] in - if let controller = controller() as? StarsWithdrawScreen, let amount = state?.amount { + if let controller = controller() as? StarsWithdrawScreen, let state, let amount = state.amount { if let minAmount, amount < minAmount { controller.presentMinAmountTooltip(minAmount.value) } else { - controller.completion(amount.value) + switch state.mode { + case let .withdraw(_, completion): + completion(amount.value) + case let .accountWithdraw(completion): + completion(amount.value) + case let .paidMedia(_, completion): + completion(amount.value) + case let .reaction(_, completion): + completion(amount.value) + case let .starGiftResell(_, _, completion): + completion(amount.value) + case let .paidMessages(_, _, _, _, completion): + completion(amount.value) + case let .suggestedPost(_, _, _, completion): + completion(amount.value, state.timestamp) + } + controller.dismissAnimated() } } @@ -442,10 +585,13 @@ private final class SheetContent: CombinedComponent { } final class State: ComponentState { - private let context: AccountContext - private let mode: StarsWithdrawScreen.Mode + fileprivate let context: AccountContext + fileprivate let mode: StarsWithdrawScreen.Mode + + fileprivate var component: SheetContent fileprivate var amount: StarsAmount? + fileprivate var timestamp: Int32? fileprivate var balance: StarsAmount? private var stateDisposable: Disposable? @@ -454,27 +600,30 @@ private final class SheetContent: CombinedComponent { var cachedStarImage: (UIImage, PresentationTheme)? var cachedChevronImage: (UIImage, PresentationTheme)? - init( - context: AccountContext, - mode: StarsWithdrawScreen.Mode - ) { - self.context = context - self.mode = mode + init(component: SheetContent) { + self.context = component.context + self.mode = component.mode + self.component = component var amount: StarsAmount? switch mode { - case let .withdraw(stats): + case let .withdraw(stats, _): amount = StarsAmount(value: stats.balances.availableBalance.value, nanos: 0) case .accountWithdraw: amount = context.starsContext?.currentState.flatMap { StarsAmount(value: $0.balance.value, nanos: 0) } - case let .paidMedia(initialValue): + case let .paidMedia(initialValue, _): amount = initialValue.flatMap { StarsAmount(value: $0, nanos: 0) } case .reaction: amount = nil case .starGiftResell: amount = nil - case let .paidMessages(initialValue, _, _, _): + case let .paidMessages(initialValue, _, _, _, _): amount = StarsAmount(value: initialValue, nanos: 0) + case let .suggestedPost(_, initialValue, initialTimestamp, _): + if initialValue != 0 { + amount = StarsAmount(value: initialValue, nanos: 0) + } + self.timestamp = initialTimestamp } self.amount = amount @@ -491,7 +640,7 @@ private final class SheetContent: CombinedComponent { }) } - if case let .starGiftResell(giftToMatch, update) = self.mode { + if case let .starGiftResell(giftToMatch, update, _) = self.mode { if update { if let resellStars = giftToMatch.resellStars { self.amount = StarsAmount(value: resellStars, nanos: 0) @@ -528,7 +677,7 @@ private final class SheetContent: CombinedComponent { } func makeState() -> State { - return State(context: self.context, mode: self.mode) + return State(component: self) } } @@ -547,12 +696,6 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { } static func ==(lhs: StarsWithdrawSheetComponent, rhs: StarsWithdrawSheetComponent) -> Bool { - if lhs.context !== rhs.context { - return false - } - if lhs.mode != rhs.mode { - return false - } return true } @@ -570,6 +713,9 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { content: AnyComponent(SheetContent( context: context.component.context, mode: context.component.mode, + controller: { + return controller() + }, dismiss: { animateOut.invoke(Action { _ in if let controller = controller() { @@ -620,27 +766,29 @@ private final class StarsWithdrawSheetComponent: CombinedComponent { } public final class StarsWithdrawScreen: ViewControllerComponentContainer { - public enum Mode: Equatable { - case withdraw(StarsRevenueStats) - case accountWithdraw - case paidMedia(Int64?) - case reaction(Int64?) - case starGiftResell(StarGift.UniqueGift, Bool) - case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind) + public enum Mode { + public enum SuggestedPostMode { + case sender(channel: EnginePeer) + } + + case withdraw(StarsRevenueStats, completion: (Int64) -> Void) + case accountWithdraw(completion: (Int64) -> Void) + case paidMedia(Int64?, completion: (Int64) -> Void) + case reaction(Int64?, completion: (Int64) -> Void) + case starGiftResell(StarGift.UniqueGift, Bool, completion: (Int64) -> Void) + case paidMessages(current: Int64, minValue: Int64, fractionAfterCommission: Int, kind: StarsWithdrawalScreenSubject.PaidMessageKind, completion: (Int64) -> Void) + case suggestedPost(mode: SuggestedPostMode, price: Int64, timestamp: Int32?, completion: (Int64, Int32?) -> Void) } private let context: AccountContext private let mode: StarsWithdrawScreen.Mode - fileprivate let completion: (Int64) -> Void public init( context: AccountContext, - mode: StarsWithdrawScreen.Mode, - completion: @escaping (Int64) -> Void + mode: StarsWithdrawScreen.Mode ) { self.context = context self.mode = mode - self.completion = completion super.init( context: context, diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/Contents.json new file mode 100644 index 0000000000..f018f77608 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "suggest_30.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/suggest_30.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/suggest_30.pdf new file mode 100644 index 0000000000..0704a57ddd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Accessory Panels/SuggestPostIcon.imageset/suggest_30.pdf differ diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/Contents.json new file mode 100644 index 0000000000..5ddf615f15 --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "suggest_24.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/suggest_24.pdf b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/suggest_24.pdf new file mode 100644 index 0000000000..37c537f49b Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Chat/Input/Text/AccessoryIconSuggestPost.imageset/suggest_24.pdf differ diff --git a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift index 1f59a92e97..94eb9e5071 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatControllerLoadDisplayNode.swift @@ -306,6 +306,9 @@ extension ChatControllerImpl { if let requestsState = previousState.requestsState, requestsState.count > 0 && !previousInvitationRequestsPeersDismissed { didDisplayActionsPanel = true } + if previousState.removePaidMessageFeeData != nil { + didDisplayActionsPanel = true + } var displayActionsPanel = false if let contactStatus = contentData.state.contactStatus, !contactStatus.isEmpty, let peerStatusSettings = contactStatus.peerStatusSettings { @@ -339,6 +342,9 @@ extension ChatControllerImpl { if let requestsState = contentData.state.requestsState, requestsState.count > 0 && !invitationRequestsPeersDismissed { displayActionsPanel = true } + if contentData.state.removePaidMessageFeeData != nil { + displayActionsPanel = true + } if displayActionsPanel != didDisplayActionsPanel { animated = true @@ -1894,6 +1900,11 @@ extension ChatControllerImpl { return } presentChatLinkOptions(selfController: self, sourceNode: sourceNode) + }, presentSuggestPostOptions: { [weak self] in + guard let self else { + return + } + self.presentSuggestPostOptions() }, shareSelectedMessages: { [weak self] in if let strongSelf = self, let selectedIds = strongSelf.presentationInterfaceState.interfaceState.selectionState?.selectedIds, !selectedIds.isEmpty { strongSelf.commitPurposefulAction() @@ -3734,7 +3745,7 @@ extension ChatControllerImpl { if let strongSelf = self { strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .info(title: nil, text: strongSelf.presentationData.strings.Conversation_GigagroupDescription, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return true }), in: .current) } - }, openSuggestPost: { [weak self] in + }, openMonoforum: { [weak self] in guard let self else { return } @@ -4084,6 +4095,22 @@ extension ChatControllerImpl { }) self.push(controller) } + }, openSuggestPost: { [weak self] in + guard let self else { + return + } + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + price: 0, + timestamp: nil + )) + return interfaceState + } + return state + }) }, openPremiumRequiredForMessaging: { [weak self] in guard let self else { return diff --git a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift index 2f9a9a7201..e8c0d89a5f 100644 --- a/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift +++ b/submodules/TelegramUI/Sources/Chat/ChatMessageActionOptions.swift @@ -977,3 +977,39 @@ private func chatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASD func presentChatLinkOptions(selfController: ChatControllerImpl, sourceNode: ASDisplayNode) { presentChatInputOptions(selfController: selfController, sourceNode: sourceNode, initialId: .link) } + +extension ChatControllerImpl { + func presentSuggestPostOptions() { + guard let channel = self.presentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel else { + return + } + guard let postSuggestionState = self.presentationInterfaceState.interfaceState.postSuggestionState else { + return + } + self.push(self.context.sharedContext.makeStarsWithdrawalScreen( + context: self.context, + subject: .postSuggestion( + channel: .channel(channel), + current: StarsAmount(value: postSuggestionState.price, nanos: 0), + timestamp: postSuggestionState.timestamp, + completion: { [weak self] price, timestamp in + guard let self else { + return + } + self.updateChatPresentationInterfaceState(interactive: true, { state in + var state = state + state = state.updatedInterfaceState { interfaceState in + var interfaceState = interfaceState + interfaceState = interfaceState.withUpdatedPostSuggestionState(ChatInterfaceState.PostSuggestionState( + price: price, + timestamp: timestamp + )) + return interfaceState + } + return state + }) + } + ) + )) + } +} diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index a07c8b62af..badb2c55e6 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -4595,24 +4595,32 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let chatPeer = self.presentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer else { return } - let controller = chatMessageRemovePaymentAlertController( - context: self.context, - presentationData: self.presentationData, - updatedPresentationData: self.updatedPresentationData, - peer: removePaidMessageFeeData.peer, - chatPeer: EnginePeer(chatPeer), - amount: StarsAmount(value: 123, nanos: 0), //TODO:release - navigationController: self.navigationController as? NavigationController, - completion: { [weak self] refund in - guard let self else { - return - } - let _ = self + + let _ = (self.context.engine.peers.getPaidMessagesRevenue(scopePeerId: peer.id, peerId: removePaidMessageFeeData.peer.id) + |> deliverOnMainQueue).start(next: { [weak self] revenue in + guard let self else { + return } - ) - self.present(controller, in: .window(.root)) + + let controller = chatMessageRemovePaymentAlertController( + context: self.context, + presentationData: self.presentationData, + updatedPresentationData: self.updatedPresentationData, + peer: removePaidMessageFeeData.peer, + chatPeer: EnginePeer(chatPeer), + amount: revenue == StarsAmount(value: 0, nanos: 0) ? nil : revenue, + navigationController: self.navigationController as? NavigationController, + completion: { [weak self] refund in + guard let self else { + return + } + let _ = self.context.engine.peers.addNoPaidMessagesException(scopePeerId: peer.id, peerId: removePaidMessageFeeData.peer.id, refundCharged: refund).start() + } + ) + self.present(controller, in: .window(.root)) + }) } else { - let _ = (self.context.engine.peers.getPaidMessagesRevenue(peerId: peer.id) + let _ = (self.context.engine.peers.getPaidMessagesRevenue(scopePeerId: self.context.account.peerId, peerId: peer.id) |> deliverOnMainQueue).start(next: { [weak self] revenue in guard let self else { return @@ -4629,7 +4637,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G guard let self else { return } - let _ = self.context.engine.peers.addNoPaidMessagesException(peerId: peer.id, refundCharged: refund).start() + let _ = self.context.engine.peers.addNoPaidMessagesException(scopePeerId: self.context.account.peerId, peerId: peer.id, refundCharged: refund).start() } ) self.present(controller, in: .window(.root)) diff --git a/submodules/TelegramUI/Sources/ChatControllerContentData.swift b/submodules/TelegramUI/Sources/ChatControllerContentData.swift index ccfc57c342..132771cb13 100644 --- a/submodules/TelegramUI/Sources/ChatControllerContentData.swift +++ b/submodules/TelegramUI/Sources/ChatControllerContentData.swift @@ -1189,13 +1189,15 @@ extension ChatControllerImpl { savedMessagesPeerId = nil } - let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError> + let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?, isMonoforumFeeRemoved: Bool)?, NoError> if let savedMessagesPeerId { let threadPeerId = savedMessagesPeerId let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: []) let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil) - savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey]) - |> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in + let threadInfoKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: savedMessagesPeerId.toInt64()) + + savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey, threadInfoKey]) + |> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?, isMonoforumFeeRemoved: Bool)? in var peer: EnginePeer? var presence: EnginePeer.Presence? if let peerView = views.views[basicPeerKey] as? PeerView { @@ -1208,7 +1210,12 @@ extension ChatControllerImpl { messageCount += Int(count) } - return (peer, messageCount, presence) + var isMonoforumFeeRemoved = false + if let threadInfoView = views.views[threadInfoKey] as? MessageHistoryThreadInfoView, let threadInfo = threadInfoView.info?.data.get(MessageHistoryThreadData.self) { + isMonoforumFeeRemoved = threadInfo.isMessageFeeRemoved + } + + return (peer, messageCount, presence, isMonoforumFeeRemoved) } |> distinctUntilChanged(isEqual: { lhs, rhs in if lhs?.peer != rhs?.peer { @@ -1220,6 +1227,9 @@ extension ChatControllerImpl { if lhs?.presence != rhs?.presence { return false } + if lhs?.isMonoforumFeeRemoved != rhs?.isMonoforumFeeRemoved { + return false + } return true }) } else { @@ -1473,7 +1483,7 @@ extension ChatControllerImpl { } var removePaidMessageFeeData: ChatPresentationInterfaceState.RemovePaidMessageFeeData? - if !"".isEmpty, let peer = savedMessagesPeer?.peer, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, let sendPaidMessageStars = channel.sendPaidMessageStars, channel.isMonoForum { + if let savedMessagesPeer, !savedMessagesPeer.isMonoforumFeeRemoved, let peer = savedMessagesPeer.peer, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, let sendPaidMessageStars = channel.sendPaidMessageStars, channel.isMonoForum { if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) { removePaidMessageFeeData = ChatPresentationInterfaceState.RemovePaidMessageFeeData( peer: peer, diff --git a/submodules/TelegramUI/Sources/ChatControllerNode.swift b/submodules/TelegramUI/Sources/ChatControllerNode.swift index bfa90845ae..b54a9f3fec 100644 --- a/submodules/TelegramUI/Sources/ChatControllerNode.swift +++ b/submodules/TelegramUI/Sources/ChatControllerNode.swift @@ -32,6 +32,7 @@ import ChatInputPanelNode import ChatInputContextPanelNode import TextSelectionNode import ReplyAccessoryPanelNode +import SuggestPostAccessoryPanelNode import ChatMessageItemView import ChatMessageSelectionNode import ManagedDiceAnimationNode @@ -371,7 +372,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { let contentBounds = self.loadingNode.frame loadingPlaceholderNode.frame = contentBounds if let loadingPlaceholderNode = self.loadingPlaceholderNode, let validLayout = self.validLayout { - loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: self.loadingNodeInsets, metrics: validLayout.0.metrics, transition: .immediate) + loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: self.leftPanel != nil, insets: self.loadingNodeInsets, metrics: validLayout.0.metrics, transition: .immediate) loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: .immediate) } } @@ -1712,6 +1713,12 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { strongSelf.interfaceInteraction?.setupEditMessage(nil, { _ in }) } else if let _ = accessoryPanelNode as? WebpagePreviewAccessoryPanelNode { strongSelf.dismissUrlPreview() + } else if let _ = accessoryPanelNode as? SuggestPostAccessoryPanelNode { + strongSelf.requestUpdateChatInterfaceState(.animated(duration: 0.4, curve: .spring), false, { state in + var state = state + state = state.withUpdatedPostSuggestionState(nil) + return state + }) } } } @@ -2250,7 +2257,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate { self.loadingNode.updateLayout(size: contentBounds.size, insets: loadingNodeInsets, transition: transition) if let loadingPlaceholderNode = self.loadingPlaceholderNode { - loadingPlaceholderNode.updateLayout(size: contentBounds.size, insets: loadingNodeInsets, metrics: layout.metrics, transition: transition) + loadingPlaceholderNode.updateLayout(size: contentBounds.size, isSidebarOpen: leftPanelSize != nil, insets: loadingNodeInsets, metrics: layout.metrics, transition: transition) loadingPlaceholderNode.update(rect: contentBounds, within: contentBounds.size, transition: transition) } diff --git a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift index 43afa00b7a..7c25066142 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceInputContexts.swift @@ -233,6 +233,12 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte } } + if isTextEmpty, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isMonoForum, let mainChannel = chatPresentationInterfaceState.renderedPeer?.chatOrMonoforumMainPeer as? TelegramChannel, !mainChannel.hasPermission(.sendSomething) { + if chatPresentationInterfaceState.interfaceState.postSuggestionState == nil { + accessoryItems.append(.suggestPost) + } + } + if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject { switch customChatContents.kind { case .hashTagSearch: diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift index ac201532fe..177b5133b0 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateAccessoryPanels.swift @@ -8,6 +8,7 @@ import ChatControllerInteraction import AccessoryPanelNode import ForwardAccessoryPanelNode import ReplyAccessoryPanelNode +import SuggestPostAccessoryPanelNode func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: AccessoryPanelNode?, chatControllerInteraction: ChatControllerInteraction?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> AccessoryPanelNode? { if case .standard(.previewing) = chatPresentationInterfaceState.mode { @@ -92,6 +93,16 @@ func accessoryPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceS return nil } } + } else if chatPresentationInterfaceState.interfaceState.postSuggestionState != nil { + if let replyPanelNode = currentPanel as? SuggestPostAccessoryPanelNode { + replyPanelNode.interfaceInteraction = interfaceInteraction + replyPanelNode.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings) + return replyPanelNode + } else { + let panelNode = SuggestPostAccessoryPanelNode(context: context, theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings, nameDisplayOrder: chatPresentationInterfaceState.nameDisplayOrder, dateTimeFormat: chatPresentationInterfaceState.dateTimeFormat, animationCache: chatControllerInteraction?.presentationContext.animationCache, animationRenderer: chatControllerInteraction?.presentationContext.animationRenderer) + panelNode.interfaceInteraction = interfaceInteraction + return panelNode + } } else { return nil } diff --git a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift index a14de8739e..3b9f217e90 100644 --- a/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatTextInputPanelNode.swift @@ -157,6 +157,9 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode { } else { return (PresentationResourcesChat.chatInputTextFieldSilentPostOffImage(theme), nil, strings.VoiceOver_SilentPostOff, 1.0, UIEdgeInsets()) } + case .suggestPost: + //TODO:localize + return (PresentationResourcesChat.chatInputTextFieldSuggestPostImage(theme), nil, "Suggest post", 1.0, UIEdgeInsets()) case let .messageAutoremoveTimeout(timeout): if let timeout = timeout { return (nil, shortTimeIntervalString(strings: strings, value: timeout), strings.VoiceOver_SelfDestructTimerOn(timeIntervalString(strings: strings, value: timeout)).string, 1.0, UIEdgeInsets()) @@ -172,7 +175,7 @@ private final class AccessoryItemIconButtonNode: HighlightTrackingButtonNode { private static func calculateWidth(item: ChatTextInputAccessoryItem, image: UIImage?, text: String?, strings: PresentationStrings) -> CGFloat { switch item { - case .input, .botInput, .silentPost, .commands, .scheduledMessages, .gift: + case .input, .botInput, .silentPost, .commands, .scheduledMessages, .gift, .suggestPost: return 32.0 case let .messageAutoremoveTimeout(timeout): var imageWidth = (image?.size.width ?? 0.0) + CGFloat(8.0) @@ -4707,6 +4710,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch self.interfaceInteraction?.openScheduledMessages() case .gift: self.interfaceInteraction?.openPremiumGift() + case .suggestPost: + self.interfaceInteraction?.openSuggestPost() } break } diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index d6bc87a1f4..5ebb3a0069 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -3715,26 +3715,28 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func makeStarsAmountScreen(context: AccountContext, initialValue: Int64?, completion: @escaping (Int64) -> Void) -> ViewController { - return StarsWithdrawScreen(context: context, mode: .paidMedia(initialValue), completion: completion) + return StarsWithdrawScreen(context: context, mode: .paidMedia(initialValue, completion: completion)) } public func makeStarsWithdrawalScreen(context: AccountContext, stats: StarsRevenueStats, completion: @escaping (Int64) -> Void) -> ViewController { - return StarsWithdrawScreen(context: context, mode: .withdraw(stats), completion: completion) + return StarsWithdrawScreen(context: context, mode: .withdraw(stats, completion: completion)) } - public func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject, completion: @escaping (Int64) -> Void) -> ViewController { + public func makeStarsWithdrawalScreen(context: AccountContext, subject: StarsWithdrawalScreenSubject) -> ViewController { let mode: StarsWithdrawScreen.Mode switch subject { - case .withdraw: - mode = .accountWithdraw - case let .enterAmount(current, minValue, fractionAfterCommission, kind): - mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind) + case let .withdraw(completion): + mode = .accountWithdraw(completion: completion) + case let .enterAmount(current, minValue, fractionAfterCommission, kind, completion): + mode = .paidMessages(current: current.value, minValue: minValue.value, fractionAfterCommission: fractionAfterCommission, kind: kind, completion: completion) + case let .postSuggestion(channel, current, timestamp, completion): + mode = .suggestedPost(mode: .sender(channel: channel), price: current.value, timestamp: timestamp, completion: completion) } - return StarsWithdrawScreen(context: context, mode: mode, completion: completion) + return StarsWithdrawScreen(context: context, mode: mode) } public func makeStarGiftResellScreen(context: AccountContext, gift: StarGift.UniqueGift, update: Bool, completion: @escaping (Int64) -> Void) -> ViewController { - return StarsWithdrawScreen(context: context, mode: .starGiftResell(gift, update), completion: completion) + return StarsWithdrawScreen(context: context, mode: .starGiftResell(gift, update, completion: completion)) } public func makeStarsGiftScreen(context: AccountContext, message: EngineMessage) -> ViewController {