Various improvements

This commit is contained in:
Isaac 2025-06-11 13:37:58 +08:00
parent 400de1ef11
commit c42b2bd9c0
36 changed files with 936 additions and 166 deletions

View File

@ -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

View File

@ -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: {

View File

@ -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 {

View File

@ -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: {

View File

@ -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
}
}
}

View File

@ -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)
}
)

View File

@ -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

View File

@ -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,

View File

@ -3,15 +3,26 @@ import TelegramApi
import Postbox
import SwiftSignalKit
func _internal_getPaidMessagesRevenue(account: Account, peerId: PeerId) -> Signal<StarsAmount?, NoError> {
return account.postbox.transaction { transaction -> Api.InputUser? in
return transaction.getPeer(peerId).flatMap(apiInputUser)
func _internal_getPaidMessagesRevenue(account: Account, scopePeerId: PeerId, peerId: PeerId) -> Signal<StarsAmount?, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputUser?) in
return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser))
}
|> mapToSignal { inputUser -> Signal<StarsAmount?, NoError> in
|> mapToSignal { scopeInputPeer, inputUser -> Signal<StarsAmount?, NoError> 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<Api.account.PaidMessagesRevenue?, NoError> 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<Never, NoError> {
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<Never, NoError> {
return account.postbox.transaction { transaction -> (Api.InputPeer?, Api.InputUser?) in
return (transaction.getPeer(scopePeerId).flatMap(apiInputPeer), transaction.getPeer(peerId).flatMap(apiInputUser))
}
|> mapToSignal { inputUser -> Signal<Never, NoError> in
|> mapToSignal { scopeInputPeer, inputUser -> Signal<Never, NoError> 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<Api.Bool, NoError> 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
}

View File

@ -1494,12 +1494,12 @@ public extension TelegramEngine {
return _internal_applyChannelBoost(account: self.account, peerId: peerId, slots: slots)
}
public func getPaidMessagesRevenue(peerId: EnginePeer.Id) -> Signal<StarsAmount?, NoError> {
return _internal_getPaidMessagesRevenue(account: self.account, peerId: peerId)
public func getPaidMessagesRevenue(scopePeerId: EnginePeer.Id, peerId: EnginePeer.Id) -> Signal<StarsAmount?, NoError> {
return _internal_getPaidMessagesRevenue(account: self.account, scopePeerId: scopePeerId, peerId: peerId)
}
public func addNoPaidMessagesException(peerId: EnginePeer.Id, refundCharged: Bool) -> Signal<Never, NoError> {
return _internal_addNoPaidMessagesException(account: self.account, peerId: peerId, refundCharged: refundCharged)
public func addNoPaidMessagesException(scopePeerId: EnginePeer.Id, peerId: EnginePeer.Id, refundCharged: Bool) -> Signal<Never, NoError> {
return _internal_addNoPaidMessagesException(account: self.account, scopePeerId: scopePeerId, peerId: peerId, refundCharged: refundCharged)
}
public func updateChannelPaidMessagesStars(peerId: EnginePeer.Id, stars: StarsAmount?, broadcastMessagesAllowed: Bool) -> Signal<Never, NoError> {

View File

@ -224,6 +224,7 @@ public enum PresentationResourceKey: Int32 {
case chatInputTextFieldTimerImage
case chatInputTextFieldScheduleImage
case chatInputTextFieldGiftImage
case chatInputTextFieldSuggestPostImage
case chatInputSearchPanelUpImage
case chatInputSearchPanelUpDisabledImage

View File

@ -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)

View File

@ -228,7 +228,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
}
@objc private func suggestedPostPressed() {
self.interfaceInteraction?.openSuggestPost()
self.interfaceInteraction?.openMonoforum()
}
@objc private func buttonPressed() {

View File

@ -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
}

View File

@ -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

View File

@ -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: {

View File

@ -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",
],
)

View File

@ -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<RunDelegateData>.fromOpaque(dataRef).release()
},
getAscent: { dataRef in
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
return data.takeUnretainedValue().ascent
},
getDescent: { dataRef in
let data = Unmanaged<RunDelegateData>.fromOpaque(dataRef)
return data.takeUnretainedValue().descent
},
getWidth: { dataRef in
let data = Unmanaged<RunDelegateData>.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)
}
}
}
}

View File

@ -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 {

View File

@ -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

View File

@ -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: {

View File

@ -37,6 +37,7 @@ swift_library(
"//submodules/Components/BundleIconComponent",
"//submodules/PasswordSetupUI",
"//submodules/TelegramUI/Components/PeerManagement/OwnershipTransferController",
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
],
visibility = [
"//visibility:public",

View File

@ -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<EnvironmentType>(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,

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "suggest_30.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -0,0 +1,12 @@
{
"images" : [
{
"filename" : "suggest_24.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@ -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

View File

@ -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
})
}
)
))
}
}

View File

@ -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))

View File

@ -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,

View File

@ -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)
}

View File

@ -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:

View File

@ -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
}

View File

@ -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
}

View File

@ -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 {