mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45:19 +00:00
[WIP] Post suggestions
This commit is contained in:
parent
96a5df0b68
commit
603d5754db
@ -1212,9 +1212,10 @@ public protocol SharedAccountContext: AnyObject {
|
||||
func makeGalleryController(context: AccountContext, source: GalleryControllerItemSource, streamSingleVideo: Bool, isPreview: Bool) -> ViewController
|
||||
|
||||
func makeAccountFreezeInfoScreen(context: AccountContext) -> ViewController
|
||||
|
||||
func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController
|
||||
|
||||
func makePostSuggestionsSettingsScreen(context: AccountContext) -> ViewController
|
||||
|
||||
func makeDebugSettingsController(context: AccountContext?) -> ViewController?
|
||||
|
||||
func openCreateGroupCallUI(context: AccountContext, peerIds: [EnginePeer.Id], parentController: ViewController)
|
||||
|
@ -1166,6 +1166,7 @@ public enum ChatCustomContentsKind: Equatable {
|
||||
case quickReplyMessageInput(shortcut: String, shortcutType: ChatQuickReplyShortcutType)
|
||||
case businessLinkSetup(link: TelegramBusinessChatLinks.Link)
|
||||
case hashTagSearch(publicPosts: Bool)
|
||||
case postSuggestions(price: StarsAmount)
|
||||
}
|
||||
|
||||
public protocol ChatCustomContentsProtocol: AnyObject {
|
||||
|
@ -1243,6 +1243,7 @@ final class AttachmentPanel: ASDisplayNode, ASScrollViewDelegate {
|
||||
}, joinGroupCall: { _ in
|
||||
}, presentInviteMembers: {
|
||||
}, presentGigagroupHelp: {
|
||||
}, openSuggestPost: {
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, updateShowCommands: { _ in
|
||||
}, updateShowSendAsPeers: { _ in
|
||||
|
@ -151,6 +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 updateShowCommands: ((Bool) -> Bool) -> Void
|
||||
public let updateShowSendAsPeers: ((Bool) -> Bool) -> Void
|
||||
public let openInviteRequests: () -> Void
|
||||
@ -267,6 +268,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
joinGroupCall: @escaping (CachedChannelData.ActiveCall) -> Void,
|
||||
presentInviteMembers: @escaping () -> Void,
|
||||
presentGigagroupHelp: @escaping () -> Void,
|
||||
openSuggestPost: @escaping () -> Void,
|
||||
editMessageMedia: @escaping (MessageId, Bool) -> Void,
|
||||
updateShowCommands: @escaping ((Bool) -> Bool) -> Void,
|
||||
updateShowSendAsPeers: @escaping ((Bool) -> Bool) -> Void,
|
||||
@ -384,6 +386,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
self.joinGroupCall = joinGroupCall
|
||||
self.presentInviteMembers = presentInviteMembers
|
||||
self.presentGigagroupHelp = presentGigagroupHelp
|
||||
self.openSuggestPost = openSuggestPost
|
||||
self.updateShowCommands = updateShowCommands
|
||||
self.updateShowSendAsPeers = updateShowSendAsPeers
|
||||
self.openInviteRequests = openInviteRequests
|
||||
@ -507,6 +510,7 @@ public final class ChatPanelInterfaceInteraction {
|
||||
}, joinGroupCall: { _ in
|
||||
}, presentInviteMembers: {
|
||||
}, presentGigagroupHelp: {
|
||||
}, openSuggestPost: {
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, updateShowCommands: { _ in
|
||||
}, updateShowSendAsPeers: { _ in
|
||||
|
@ -228,6 +228,7 @@ private var declaredEncodables: Void = {
|
||||
declareEncodable(DerivedDataMessageAttribute.self, f: { DerivedDataMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramApplicationIcons.self, f: { TelegramApplicationIcons(decoder: $0) })
|
||||
declareEncodable(OutgoingQuickReplyMessageAttribute.self, f: { OutgoingQuickReplyMessageAttribute(decoder: $0) })
|
||||
declareEncodable(OutgoingSuggestedPostMessageAttribute.self, f: { OutgoingSuggestedPostMessageAttribute(decoder: $0) })
|
||||
declareEncodable(EffectMessageAttribute.self, f: { EffectMessageAttribute(decoder: $0) })
|
||||
declareEncodable(FactCheckMessageAttribute.self, f: { FactCheckMessageAttribute(decoder: $0) })
|
||||
declareEncodable(TelegramMediaPaidContent.self, f: { TelegramMediaPaidContent(decoder: $0) })
|
||||
|
@ -236,6 +236,8 @@ private func filterMessageAttributesForOutgoingMessage(_ attributes: [MessageAtt
|
||||
return true
|
||||
case _ as OutgoingQuickReplyMessageAttribute:
|
||||
return true
|
||||
case _ as OutgoingSuggestedPostMessageAttribute:
|
||||
return true
|
||||
case _ as EmbeddedMediaStickersMessageAttribute:
|
||||
return true
|
||||
case _ as EmojiSearchQueryMessageAttribute:
|
||||
@ -275,6 +277,8 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt
|
||||
return true
|
||||
case _ as OutgoingQuickReplyMessageAttribute:
|
||||
return true
|
||||
case _ as OutgoingSuggestedPostMessageAttribute:
|
||||
return true
|
||||
case _ as ForwardOptionsMessageAttribute:
|
||||
return true
|
||||
case _ as SendAsMessageAttribute:
|
||||
@ -733,6 +737,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
} else if attribute is OutgoingQuickReplyMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.QuickReplyLocal
|
||||
effectiveTimestamp = 0
|
||||
} else if attribute is OutgoingSuggestedPostMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.SuggestedPostLocal
|
||||
effectiveTimestamp = 0
|
||||
} else if let attribute = attribute as? SendAsMessageAttribute {
|
||||
if let peer = transaction.getPeer(attribute.peerId) {
|
||||
sendAsPeer = peer
|
||||
@ -769,6 +776,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
if messageNamespace != Namespaces.Message.QuickReplyLocal {
|
||||
attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute })
|
||||
}
|
||||
if messageNamespace != Namespaces.Message.SuggestedPostLocal {
|
||||
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
|
||||
}
|
||||
|
||||
if let peer = peer as? TelegramChannel {
|
||||
switch peer.info {
|
||||
@ -1003,6 +1013,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
} else if attribute is OutgoingQuickReplyMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.QuickReplyLocal
|
||||
effectiveTimestamp = 0
|
||||
} else if attribute is OutgoingSuggestedPostMessageAttribute {
|
||||
messageNamespace = Namespaces.Message.SuggestedPostLocal
|
||||
effectiveTimestamp = 0
|
||||
} else if let attribute = attribute as? ReplyMessageAttribute {
|
||||
if let threadMessageId = attribute.threadMessageId {
|
||||
threadId = Int64(threadMessageId.id)
|
||||
@ -1035,6 +1048,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId,
|
||||
if messageNamespace != Namespaces.Message.QuickReplyLocal {
|
||||
attributes.removeAll(where: { $0 is OutgoingQuickReplyMessageAttribute })
|
||||
}
|
||||
if messageNamespace != Namespaces.Message.SuggestedPostLocal {
|
||||
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
|
||||
}
|
||||
|
||||
let (tags, globalTags) = tagsForStoreMessage(incoming: false, attributes: attributes, media: sourceMessage.media, textEntities: entitiesAttribute?.entities, isPinned: false)
|
||||
|
||||
|
@ -179,6 +179,9 @@ private func requestEditMessageInternal(accountPeerId: PeerId, postbox: Postbox,
|
||||
if messageId.namespace == Namespaces.Message.QuickReplyCloud {
|
||||
quickReplyShortcutId = Int32(clamping: message.threadId ?? 0)
|
||||
flags |= Int32(1 << 17)
|
||||
} else if messageId.namespace == Namespaces.Message.SuggestedPostLocal {
|
||||
//TODO:release
|
||||
preconditionFailure()
|
||||
}
|
||||
|
||||
return network.request(Api.functions.messages.editMessage(flags: flags, peer: inputPeer, id: messageId.id, message: text, media: inputMedia, replyMarkup: nil, entities: apiEntities, scheduleDate: effectiveScheduleTime, quickReplyShortcutId: quickReplyShortcutId))
|
||||
|
@ -72,6 +72,8 @@ private func fetchWebpage(account: Account, messageId: MessageId, threadId: Int6
|
||||
targetMessageNamespace = Namespaces.Message.ScheduledCloud
|
||||
} else if Namespaces.Message.allQuickReply.contains(messageId.namespace) {
|
||||
targetMessageNamespace = Namespaces.Message.QuickReplyCloud
|
||||
} else if Namespaces.Message.allSuggestedPost.contains(messageId.namespace) {
|
||||
targetMessageNamespace = Namespaces.Message.SuggestedPostCloud
|
||||
} else {
|
||||
targetMessageNamespace = Namespaces.Message.Cloud
|
||||
}
|
||||
@ -1071,6 +1073,10 @@ public final class AccountViewTracker {
|
||||
} else {
|
||||
fetchSignal = .never()
|
||||
}
|
||||
} else if let messageId = messageIds.first, messageId.namespace == Namespaces.Message.SuggestedPostCloud {
|
||||
//TODO:release
|
||||
assertionFailure()
|
||||
fetchSignal = .never()
|
||||
} else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudUser || peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudGroup {
|
||||
fetchSignal = account.network.request(Api.functions.messages.getMessages(id: messageIds.map { Api.InputMessage.inputMessageID(id: $0.id) }))
|
||||
} else if peerIdAndThreadId.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
@ -2120,6 +2126,36 @@ public final class AccountViewTracker {
|
||||
}
|
||||
return signal
|
||||
}
|
||||
|
||||
public func postSuggestionsViewForLocation(peerId: EnginePeer.Id, additionalData: [AdditionalMessageHistoryViewData] = []) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
guard let account = self.account else {
|
||||
return .never()
|
||||
}
|
||||
let chatLocation: ChatLocationInput = .peer(peerId: peerId, threadId: nil)
|
||||
let signal = account.postbox.aroundMessageHistoryViewForLocation(chatLocation, anchor: .upperBound, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 200, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: [], tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .just(Namespaces.Message.allSuggestedPost), orderStatistics: [], additionalData: additionalData)
|
||||
return withState(signal, { [weak self] () -> Int32 in
|
||||
if let strongSelf = self {
|
||||
return OSAtomicIncrement32(&strongSelf.nextViewId)
|
||||
} else {
|
||||
return -1
|
||||
}
|
||||
}, next: { [weak self] next, viewId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.queue.async {
|
||||
let (messageIds, localWebpages) = pendingWebpages(entries: next.0.entries)
|
||||
strongSelf.updatePendingWebpages(viewId: viewId, threadId: nil, messageIds: messageIds, localWebpages: localWebpages)
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: next.0, location: chatLocation)
|
||||
}
|
||||
}
|
||||
}, disposed: { [weak self] viewId in
|
||||
if let strongSelf = self {
|
||||
strongSelf.queue.async {
|
||||
strongSelf.updatePendingWebpages(viewId: viewId, threadId: nil, messageIds: [], localWebpages: [:])
|
||||
strongSelf.historyViewStateValidationContexts.updateView(id: viewId, view: nil, location: nil)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func aroundMessageOfInterestHistoryViewForLocation(_ chatLocation: ChatLocationInput, ignoreMessagesInTimestampRange: ClosedRange<Int32>? = nil, ignoreMessageIds: Set<MessageId> = Set(), count: Int, tag: HistoryViewInputTag? = nil, appendMessagesFromTheSameGroup: Bool = false, orderStatistics: MessageHistoryViewOrderStatistics = [], additionalData: [AdditionalMessageHistoryViewData] = [], useRootInterfaceStateForThread: Bool = false) -> Signal<(MessageHistoryView, ViewUpdateType, InitialMessageHistoryData?), NoError> {
|
||||
if let account = self.account {
|
||||
|
@ -198,6 +198,14 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
}
|
||||
}
|
||||
}
|
||||
if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
|
||||
for i in 0 ..< updatedAttributes.count {
|
||||
if updatedAttributes[i] is OutgoingSuggestedPostMessageAttribute {
|
||||
updatedAttributes.remove(at: i)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attributes = updatedAttributes
|
||||
text = currentMessage.text
|
||||
@ -220,6 +228,8 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
}
|
||||
if Namespaces.Message.allQuickReply.contains(message.id.namespace) {
|
||||
namespace = Namespaces.Message.QuickReplyCloud
|
||||
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
|
||||
namespace = Namespaces.Message.SuggestedPostCloud
|
||||
} else if let updatedTimestamp = updatedTimestamp {
|
||||
if attributes.contains(where: { $0 is PendingProcessingMessageAttribute }) {
|
||||
namespace = Namespaces.Message.ScheduledCloud
|
||||
@ -243,6 +253,8 @@ func applyUpdateMessage(postbox: Postbox, stateManager: AccountStateManager, mes
|
||||
if let threadId {
|
||||
_internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId))
|
||||
}
|
||||
} else if attribute is OutgoingSuggestedPostMessageAttribute {
|
||||
//TODO:release
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,6 +409,8 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
||||
var namespace = Namespaces.Message.Cloud
|
||||
if Namespaces.Message.allQuickReply.contains(messages[0].id.namespace) {
|
||||
namespace = Namespaces.Message.QuickReplyCloud
|
||||
} else if Namespaces.Message.allSuggestedPost.contains(messages[0].id.namespace) {
|
||||
namespace = Namespaces.Message.SuggestedPostCloud
|
||||
} else if let message = messages.first, let apiMessage = result.messages.first {
|
||||
if message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
|
||||
namespace = Namespaces.Message.ScheduledCloud
|
||||
@ -474,6 +488,8 @@ func applyUpdateGroupMessages(postbox: Postbox, stateManager: AccountStateManage
|
||||
if let threadId = updatedMessage.threadId {
|
||||
_internal_applySentQuickReplyMessage(transaction: transaction, shortcut: attribute.shortcut, quickReplyId: Int32(clamping: threadId))
|
||||
}
|
||||
} else if attribute is OutgoingSuggestedPostMessageAttribute {
|
||||
//TODO:release
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,25 @@ func cloudChatAddClearHistoryOperation(transaction: Transaction, peerId: PeerId,
|
||||
} else if case .forEveryone = type {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: .max), threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
|
||||
}
|
||||
} else if type == .suggestedPostMessages {
|
||||
var messageIds: [MessageId] = []
|
||||
transaction.withAllMessages(peerId: peerId, namespace: Namespaces.Message.SuggestedPostCloud) { message -> Bool in
|
||||
messageIds.append(message.id)
|
||||
return true
|
||||
}
|
||||
cloudChatAddRemoveMessagesOperation(transaction: transaction, peerId: peerId, threadId: threadId, messageIds: messageIds, type: .forLocalPeer)
|
||||
|
||||
let topMessageId: MessageId?
|
||||
if let explicitTopMessageId = explicitTopMessageId {
|
||||
topMessageId = explicitTopMessageId
|
||||
} else {
|
||||
topMessageId = transaction.getTopPeerMessageId(peerId: peerId, namespace: Namespaces.Message.SuggestedPostCloud)
|
||||
}
|
||||
if let topMessageId = topMessageId {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: topMessageId, threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
|
||||
} else if case .forEveryone = type {
|
||||
transaction.operationLogAddEntry(peerId: peerId, tag: OperationLogTags.CloudChatRemoveMessages, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: CloudChatClearHistoryOperation(peerId: peerId, topMessageId: MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: .max), threadId: threadId, minTimestamp: minTimestamp, maxTimestamp: maxTimestamp, type: type))
|
||||
}
|
||||
} else {
|
||||
let topMessageId: MessageId?
|
||||
if let explicitTopMessageId = explicitTopMessageId {
|
||||
|
@ -128,6 +128,7 @@ func managedCloudChatRemoveMessagesOperations(postbox: Postbox, network: Network
|
||||
private func removeMessages(postbox: Postbox, network: Network, stateManager: AccountStateManager, peer: Peer, operation: CloudChatRemoveMessagesOperation) -> Signal<Void, NoError> {
|
||||
var isScheduled = false
|
||||
var isQuickReply = false
|
||||
var isSuggestedPost = false
|
||||
for id in operation.messageIds {
|
||||
if id.namespace == Namespaces.Message.ScheduledCloud {
|
||||
isScheduled = true
|
||||
@ -135,6 +136,9 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac
|
||||
} else if id.namespace == Namespaces.Message.QuickReplyCloud {
|
||||
isQuickReply = true
|
||||
break
|
||||
} else if id.namespace == Namespaces.Message.SuggestedPostCloud {
|
||||
isSuggestedPost = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -190,6 +194,10 @@ private func removeMessages(postbox: Postbox, network: Network, stateManager: Ac
|
||||
} else {
|
||||
return .complete()
|
||||
}
|
||||
} else if isSuggestedPost {
|
||||
//TODO:release
|
||||
assertionFailure()
|
||||
return .complete()
|
||||
} else if peer.id.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let inputChannel = apiInputChannel(peer) {
|
||||
var signal: Signal<Void, NoError> = .complete()
|
||||
|
@ -858,11 +858,15 @@ public final class PendingMessageManager {
|
||||
var videoTimestamp: Int32?
|
||||
var sendAsPeerId: PeerId?
|
||||
var quickReply: OutgoingQuickReplyMessageAttribute?
|
||||
var suggestedPost: OutgoingSuggestedPostMessageAttribute?
|
||||
var messageEffect: EffectMessageAttribute?
|
||||
var allowPaidStars: Int64?
|
||||
|
||||
var flags: Int32 = 0
|
||||
|
||||
//TODO:release
|
||||
let _ = suggestedPost
|
||||
|
||||
for attribute in messages[0].0.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
replyMessageId = replyAttribute.messageId.id
|
||||
@ -890,6 +894,8 @@ public final class PendingMessageManager {
|
||||
sendAsPeerId = attribute.peerId
|
||||
} else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute {
|
||||
quickReply = attribute
|
||||
} else if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute
|
||||
} else if let attribute = attribute as? EffectMessageAttribute {
|
||||
messageEffect = attribute
|
||||
} else if let _ = attribute as? InvertMediaMessageAttribute {
|
||||
@ -1322,10 +1328,14 @@ public final class PendingMessageManager {
|
||||
var sendAsPeerId: PeerId?
|
||||
var bubbleUpEmojiOrStickersets = false
|
||||
var quickReply: OutgoingQuickReplyMessageAttribute?
|
||||
var suggestedPost: OutgoingSuggestedPostMessageAttribute?
|
||||
var messageEffect: EffectMessageAttribute?
|
||||
var allowPaidStars: Int64?
|
||||
|
||||
var flags: Int32 = 0
|
||||
|
||||
//TODO:release
|
||||
let _ = suggestedPost
|
||||
|
||||
for attribute in message.attributes {
|
||||
if let replyAttribute = attribute as? ReplyMessageAttribute {
|
||||
@ -1360,6 +1370,8 @@ public final class PendingMessageManager {
|
||||
sendAsPeerId = attribute.peerId
|
||||
} else if let attribute = attribute as? OutgoingQuickReplyMessageAttribute {
|
||||
quickReply = attribute
|
||||
} else if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
|
||||
suggestedPost = attribute
|
||||
} else if let attribute = attribute as? EffectMessageAttribute {
|
||||
messageEffect = attribute
|
||||
} else if let attribute = attribute as? ForwardVideoTimestampAttribute {
|
||||
@ -1803,6 +1815,8 @@ public final class PendingMessageManager {
|
||||
targetNamespace = Namespaces.Message.ScheduledCloud
|
||||
} else if Namespaces.Message.allQuickReply.contains(message.id.namespace) {
|
||||
targetNamespace = Namespaces.Message.QuickReplyCloud
|
||||
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
|
||||
targetNamespace = Namespaces.Message.SuggestedPostCloud
|
||||
} else {
|
||||
targetNamespace = Namespaces.Message.Cloud
|
||||
}
|
||||
@ -1854,6 +1868,8 @@ public final class PendingMessageManager {
|
||||
if let message = messages.first {
|
||||
if message.id.namespace == Namespaces.Message.QuickReplyLocal {
|
||||
namespace = Namespaces.Message.QuickReplyCloud
|
||||
} else if Namespaces.Message.allSuggestedPost.contains(message.id.namespace) {
|
||||
namespace = Namespaces.Message.SuggestedPostCloud
|
||||
} else if let apiMessage = result.messages.first, message.scheduleTime != nil && message.scheduleTime == apiMessage.timestamp {
|
||||
namespace = Namespaces.Message.ScheduledCloud
|
||||
} else if let apiMessage = result.messages.first, case let .message(_, flags2, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _) = apiMessage, (flags2 & (1 << 4)) != 0 {
|
||||
|
@ -0,0 +1,37 @@
|
||||
import Foundation
|
||||
import Postbox
|
||||
import TelegramApi
|
||||
|
||||
public final class OutgoingSuggestedPostMessageAttribute: Equatable, MessageAttribute {
|
||||
public let price: StarsAmount
|
||||
public let timestamp: Int32?
|
||||
|
||||
public init(price: StarsAmount, timestamp: Int32?) {
|
||||
self.price = price
|
||||
self.timestamp = timestamp
|
||||
}
|
||||
|
||||
required public init(decoder: PostboxDecoder) {
|
||||
self.price = decoder.decodeCodable(StarsAmount.self, forKey: "s") ?? StarsAmount(value: 0, nanos: 0)
|
||||
self.timestamp = decoder.decodeOptionalInt32ForKey("t")
|
||||
}
|
||||
|
||||
public func encode(_ encoder: PostboxEncoder) {
|
||||
encoder.encodeCodable(self.price, forKey: "s")
|
||||
if let timestamp = self.timestamp {
|
||||
encoder.encodeInt32(timestamp, forKey: "t")
|
||||
} else {
|
||||
encoder.encodeNil(forKey: "t")
|
||||
}
|
||||
}
|
||||
|
||||
public static func ==(lhs: OutgoingSuggestedPostMessageAttribute, rhs: OutgoingSuggestedPostMessageAttribute) -> Bool {
|
||||
if lhs.price != rhs.price {
|
||||
return false
|
||||
}
|
||||
if lhs.timestamp != rhs.timestamp {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ public enum CloudChatClearHistoryType: Int32 {
|
||||
case forEveryone
|
||||
case scheduledMessages
|
||||
case quickReplyMessages
|
||||
case suggestedPostMessages
|
||||
}
|
||||
|
||||
public enum InteractiveHistoryClearingType: Int32 {
|
||||
|
@ -10,10 +10,13 @@ public struct Namespaces {
|
||||
public static let ScheduledLocal: Int32 = 4
|
||||
public static let QuickReplyCloud: Int32 = 5
|
||||
public static let QuickReplyLocal: Int32 = 6
|
||||
public static let SuggestedPostLocal: Int32 = 7
|
||||
public static let SuggestedPostCloud: Int32 = 8
|
||||
|
||||
public static let allScheduled: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal])
|
||||
public static let allQuickReply: Set<Int32> = Set([Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal])
|
||||
public static let allNonRegular: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal])
|
||||
public static let allSuggestedPost: Set<Int32> = Set([Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal])
|
||||
public static let allNonRegular: Set<Int32> = Set([Namespaces.Message.ScheduledCloud, Namespaces.Message.ScheduledLocal, Namespaces.Message.QuickReplyCloud, Namespaces.Message.QuickReplyLocal, Namespaces.Message.SuggestedPostCloud, Namespaces.Message.SuggestedPostLocal])
|
||||
public static let allLocal: [Int32] = [
|
||||
Namespaces.Message.Local,
|
||||
Namespaces.Message.SecretIncoming,
|
||||
|
@ -701,6 +701,9 @@ func fetchRemoteMessage(accountPeerId: PeerId, postbox: Postbox, source: FetchMe
|
||||
} else {
|
||||
signal = .never()
|
||||
}
|
||||
} else if id.namespace == Namespaces.Message.SuggestedPostCloud {
|
||||
//TODO:release
|
||||
signal = .never()
|
||||
} else if id.peerId.namespace == Namespaces.Peer.CloudChannel {
|
||||
if let channel = peer.inputChannel {
|
||||
signal = source.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: id.id)]))
|
||||
|
@ -49,6 +49,7 @@ swift_library(
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//third-party/recaptcha:RecaptchaEnterprise",
|
||||
"//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit",
|
||||
"//submodules/SSignalKit/SSignalKit:SSignalKit",
|
||||
"//submodules/AsyncDisplayKit:AsyncDisplayKit",
|
||||
@ -474,7 +475,7 @@ swift_library(
|
||||
"//submodules/Components/BlurredBackgroundComponent",
|
||||
"//submodules/TelegramUI/Components/CheckComponent",
|
||||
"//submodules/TelegramUI/Components/MarqueeComponent",
|
||||
"//third-party/recaptcha:RecaptchaEnterprise",
|
||||
"//submodules/TelegramUI/Components/PeerInfo/PostSuggestionsSettingsScreen",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -149,6 +149,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
private let helpButton: HighlightableButtonNode
|
||||
private let giftButton: HighlightableButtonNode
|
||||
private let suggestedPostButton: HighlightableButtonNode
|
||||
|
||||
private var action: SubscriberAction?
|
||||
|
||||
@ -182,6 +183,8 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
self.helpButton.isHidden = true
|
||||
self.giftButton = HighlightableButtonNode()
|
||||
self.giftButton.isHidden = true
|
||||
self.suggestedPostButton = HighlightableButtonNode()
|
||||
self.suggestedPostButton.isHidden = true
|
||||
|
||||
self.discussButton.addSubnode(self.discussButtonText)
|
||||
self.discussButton.addSubnode(self.badgeBackground)
|
||||
@ -196,11 +199,12 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
self.view.addSubview(self.activityIndicator)
|
||||
self.addSubnode(self.helpButton)
|
||||
self.addSubnode(self.giftButton)
|
||||
|
||||
self.addSubnode(self.suggestedPostButton)
|
||||
self.button.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
self.discussButton.addTarget(self, action: #selector(self.discussPressed), forControlEvents: .touchUpInside)
|
||||
self.helpButton.addTarget(self, action: #selector(self.helpPressed), forControlEvents: .touchUpInside)
|
||||
self.giftButton.addTarget(self, action: #selector(self.giftPressed), forControlEvents: .touchUpInside)
|
||||
self.suggestedPostButton.addTarget(self, action: #selector(self.suggestedPostPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
deinit {
|
||||
@ -222,6 +226,10 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
@objc private func helpPressed() {
|
||||
self.interfaceInteraction?.presentGigagroupHelp()
|
||||
}
|
||||
|
||||
@objc private func suggestedPostPressed() {
|
||||
self.interfaceInteraction?.openSuggestPost()
|
||||
}
|
||||
|
||||
@objc private func buttonPressed() {
|
||||
guard let context = self.context, let action = self.action, let presentationInterfaceState = self.presentationInterfaceState, let peer = presentationInterfaceState.renderedPeer?.peer else {
|
||||
@ -369,6 +377,7 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
if previousState?.theme !== interfaceState.theme {
|
||||
self.badgeBackground.image = PresentationResourcesChatList.badgeBackgroundActive(interfaceState.theme, diameter: 20.0)
|
||||
self.helpButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Help"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
self.suggestedPostButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
self.giftButton.setImage(generateTintedImage(image: UIImage(bundleImageName: "Chat/Input/Accessory Panels/Gift"), color: interfaceState.theme.chat.inputPanel.panelControlAccentColor), for: .normal)
|
||||
}
|
||||
|
||||
@ -420,18 +429,26 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
|
||||
self.giftButton.isHidden = false
|
||||
self.helpButton.isHidden = true
|
||||
|
||||
//TODO:release
|
||||
self.suggestedPostButton.isHidden = false
|
||||
self.presentGiftTooltip()
|
||||
} else if case .broadcast = peer.info {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = false
|
||||
} else if peer.flags.contains(.isGigagroup), self.action == .muteNotifications || self.action == .unmuteNotifications {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = false
|
||||
self.suggestedPostButton.isHidden = true
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
}
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
}
|
||||
if let action = self.action, action == .muteNotifications || action == .unmuteNotifications {
|
||||
let buttonWidth = self.button.calculateSizeThatFits(CGSize(width: width, height: panelHeight)).width + 24.0
|
||||
@ -441,9 +458,11 @@ public final class ChatChannelSubscriberInputPanelNode: ChatInputPanelNode {
|
||||
}
|
||||
self.giftButton.frame = CGRect(x: width - rightInset - panelHeight - 5.0, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
self.helpButton.frame = CGRect(x: width - rightInset - panelHeight, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
self.suggestedPostButton.frame = CGRect(x: leftInset + 5.0, y: 0.0, width: panelHeight, height: panelHeight)
|
||||
} else {
|
||||
self.giftButton.isHidden = true
|
||||
self.helpButton.isHidden = true
|
||||
self.suggestedPostButton.isHidden = true
|
||||
|
||||
let availableWidth = min(600.0, width - leftInset - rightInset)
|
||||
let leftOffset = floor((width - availableWidth) / 2.0)
|
||||
|
@ -780,6 +780,10 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
|
||||
insets.top = -9.0
|
||||
imageSpacing = 4.0
|
||||
titleSpacing = 5.0
|
||||
case .postSuggestions:
|
||||
insets.top = 10.0
|
||||
imageSpacing = 5.0
|
||||
titleSpacing = 5.0
|
||||
case .hashTagSearch:
|
||||
break
|
||||
}
|
||||
@ -841,7 +845,7 @@ private final class ChatEmptyNodeCloudChatContent: ASDisplayNode, ChatEmptyNodeC
|
||||
}
|
||||
|
||||
self.businessLink = link
|
||||
case .hashTagSearch:
|
||||
case .hashTagSearch, .postSuggestions:
|
||||
titleString = ""
|
||||
strings = []
|
||||
}
|
||||
@ -1297,7 +1301,11 @@ public final class ChatEmptyNodePremiumRequiredChatContent: ASDisplayNode, ChatE
|
||||
if let amount = self.stars {
|
||||
let starsString = presentationStringsFormattedNumber(Int32(amount), interfaceState.dateTimeFormat.groupingSeparator)
|
||||
let rawText: String
|
||||
if self.isPremiumDisabled {
|
||||
|
||||
if case let .customChatContents(customChatContents) = interfaceState.subject, case .postSuggestions = customChatContents.kind {
|
||||
//TODO:localize
|
||||
rawText = "\(peerTitle) charges $ \(starsString) per message suggestion."
|
||||
} else if self.isPremiumDisabled {
|
||||
rawText = interfaceState.strings.Chat_EmptyStatePaidMessagingDisabled_Text(peerTitle, " $ \(starsString)").string
|
||||
} else {
|
||||
rawText = interfaceState.strings.Chat_EmptyStatePaidMessaging_Text(peerTitle, " $ \(starsString)").string
|
||||
@ -1427,6 +1435,7 @@ private enum ChatEmptyNodeContentType: Equatable {
|
||||
case topic
|
||||
case premiumRequired
|
||||
case starsRequired(Int64)
|
||||
case postSuggestions(Int64)
|
||||
}
|
||||
|
||||
private final class EmptyAttachedDescriptionNode: HighlightTrackingButtonNode {
|
||||
@ -1795,8 +1804,12 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
case let .emptyChat(emptyType):
|
||||
if case .customGreeting = emptyType {
|
||||
contentType = .greeting
|
||||
} else if case .customChatContents = interfaceState.subject {
|
||||
contentType = .cloud
|
||||
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
|
||||
if case let .postSuggestions(postSuggestions) = customChatContents.kind {
|
||||
contentType = .postSuggestions(postSuggestions.value)
|
||||
} else {
|
||||
contentType = .cloud
|
||||
}
|
||||
} else if case .replyThread = interfaceState.chatLocation {
|
||||
if case .topic = emptyType {
|
||||
contentType = .topic
|
||||
@ -1883,6 +1896,8 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: nil)
|
||||
case let .starsRequired(stars):
|
||||
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
|
||||
case let .postSuggestions(stars):
|
||||
node = ChatEmptyNodePremiumRequiredChatContent(context: self.context, interaction: self.interaction, stars: stars)
|
||||
}
|
||||
self.content = (contentType, node)
|
||||
self.addSubnode(node)
|
||||
@ -1894,7 +1909,7 @@ public final class ChatEmptyNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
switch contentType {
|
||||
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud:
|
||||
case .peerNearby, .greeting, .premiumRequired, .starsRequired, .cloud, .postSuggestions:
|
||||
self.isUserInteractionEnabled = true
|
||||
default:
|
||||
self.isUserInteractionEnabled = false
|
||||
|
@ -88,6 +88,7 @@ swift_library(
|
||||
"//submodules/AnimatedStickerNode",
|
||||
"//submodules/TelegramAnimatedStickerNode",
|
||||
"//submodules/TelegramUI/Components/LottieMetal",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -618,6 +618,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
private let shadowNode: ChatMessageShadowNode
|
||||
private var clippingNode: ChatMessageBubbleClippingNode
|
||||
|
||||
private var suggestedPostInfoNode: ChatMessageSuggestedPostInfoNode?
|
||||
|
||||
override public var extractedBackgroundNode: ASDisplayNode? {
|
||||
return self.shadowNode
|
||||
}
|
||||
@ -1422,6 +1424,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
let weakSelf = Weak(self)
|
||||
|
||||
let makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout = ChatMessageSuggestedPostInfoNode.asyncLayout(self.suggestedPostInfoNode)
|
||||
|
||||
return { item, params, mergedTop, mergedBottom, dateHeaderAtBottom in
|
||||
let layoutConstants = chatMessageItemLayoutConstants(layoutConstants, params: params, presentationData: item.presentationData)
|
||||
return ChatMessageBubbleItemNode.beginLayout(selfReference: weakSelf, item, params, mergedTop, mergedBottom, dateHeaderAtBottom,
|
||||
@ -1438,6 +1442,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
unlockButtonLayout: unlockButtonLayout,
|
||||
mediaInfoLayout: mediaInfoLayout,
|
||||
mosaicStatusLayout: mosaicStatusLayout,
|
||||
makeSuggestedPostInfoNodeLayout: makeSuggestedPostInfoNodeLayout,
|
||||
layoutConstants: layoutConstants,
|
||||
currentItem: currentItem,
|
||||
currentForwardInfo: currentForwardInfo,
|
||||
@ -1466,6 +1471,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
unlockButtonLayout: (ChatMessageUnlockMediaNode.Arguments) -> (CGSize, (Bool) -> ChatMessageUnlockMediaNode),
|
||||
mediaInfoLayout: (ChatMessageStarsMediaInfoNode.Arguments) -> (CGSize, (Bool) -> ChatMessageStarsMediaInfoNode),
|
||||
mosaicStatusLayout: (ChatMessageDateAndStatusNode.Arguments) -> (CGFloat, (CGFloat) -> (CGSize, (ListViewItemUpdateAnimation) -> ChatMessageDateAndStatusNode)),
|
||||
makeSuggestedPostInfoNodeLayout: ChatMessageSuggestedPostInfoNode.AsyncLayout,
|
||||
layoutConstants: ChatMessageItemLayoutConstants,
|
||||
currentItem: ChatMessageItem?,
|
||||
currentForwardInfo: (Peer?, String?)?,
|
||||
@ -2935,6 +2941,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
var totalContentNodesHeight: CGFloat = 0.0
|
||||
var currentContainerGroupOverlap: CGFloat = 0.0
|
||||
var detachedContentNodesHeight: CGFloat = 0.0
|
||||
var additionalTopHeight: CGFloat = 0.0
|
||||
|
||||
var mosaicStatusOrigin: CGPoint?
|
||||
var unlockButtonPosition: CGPoint?
|
||||
@ -3110,6 +3117,19 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
}
|
||||
reactionButtonsSizeAndApply = reactionButtonsFinalize(maxContentWidth)
|
||||
}
|
||||
|
||||
var suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
|
||||
let _ = attribute
|
||||
let suggestedPostInfoNodeLayoutValue = makeSuggestedPostInfoNodeLayout(item, baseWidth)
|
||||
suggestedPostInfoNodeLayout = suggestedPostInfoNodeLayoutValue
|
||||
}
|
||||
}
|
||||
|
||||
if let suggestedPostInfoNodeLayout {
|
||||
additionalTopHeight += 4.0 + suggestedPostInfoNodeLayout.0.height + 8.0
|
||||
}
|
||||
|
||||
let minimalContentSize: CGSize
|
||||
if hideBackground {
|
||||
@ -3130,7 +3150,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
let contentUpperRightCorner: CGPoint
|
||||
switch alignment {
|
||||
case .none:
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset) : (params.width - params.rightInset - layoutBubbleSize.width - layoutConstants.bubble.edgeInset - deliveryFailedInset), y: detachedContentNodesHeight), size: layoutBubbleSize)
|
||||
backgroundFrame = CGRect(origin: CGPoint(x: incoming ? (params.leftInset + layoutConstants.bubble.edgeInset + avatarInset) : (params.width - params.rightInset - layoutBubbleSize.width - layoutConstants.bubble.edgeInset - deliveryFailedInset), y: detachedContentNodesHeight + additionalTopHeight), size: layoutBubbleSize)
|
||||
contentOrigin = CGPoint(x: backgroundFrame.origin.x + (incoming ? layoutConstants.bubble.contentInsets.left : layoutConstants.bubble.contentInsets.right), y: backgroundFrame.origin.y + layoutConstants.bubble.contentInsets.top + headerSize.height + contentVerticalOffset)
|
||||
contentUpperRightCorner = CGPoint(x: backgroundFrame.maxX - (incoming ? layoutConstants.bubble.contentInsets.right : layoutConstants.bubble.contentInsets.left), y: backgroundFrame.origin.y + layoutConstants.bubble.contentInsets.top + headerSize.height)
|
||||
case .center:
|
||||
@ -3147,7 +3167,8 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
let bubbleContentWidth = maxContentWidth - layoutConstants.bubble.edgeInset * 2.0 - (layoutConstants.bubble.contentInsets.right + layoutConstants.bubble.contentInsets.left)
|
||||
|
||||
var layoutSize = CGSize(width: params.width, height: layoutBubbleSize.height + detachedContentNodesHeight)
|
||||
var layoutSize = CGSize(width: params.width, height: layoutBubbleSize.height + detachedContentNodesHeight + additionalTopHeight)
|
||||
|
||||
if let reactionButtonsSizeAndApply = reactionButtonsSizeAndApply {
|
||||
layoutSize.height += 4.0 + reactionButtonsSizeAndApply.0.height + 2.0
|
||||
}
|
||||
@ -3207,7 +3228,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
nameNodeSizeApply: nameNodeSizeApply,
|
||||
viaWidth: viaWidth,
|
||||
contentOrigin: contentOrigin,
|
||||
nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight,
|
||||
nameNodeOriginY: nameNodeOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||
authorNameColor: authorNameColor,
|
||||
layoutConstants: layoutConstants,
|
||||
currentCredibilityIcon: currentCredibilityIcon,
|
||||
@ -3215,11 +3236,11 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
boostNodeSizeApply: boostNodeSizeApply,
|
||||
contentUpperRightCorner: contentUpperRightCorner,
|
||||
threadInfoSizeApply: threadInfoSizeApply,
|
||||
threadInfoOriginY: threadInfoOriginY + detachedContentNodesHeight,
|
||||
threadInfoOriginY: threadInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||
forwardInfoSizeApply: forwardInfoSizeApply,
|
||||
forwardInfoOriginY: forwardInfoOriginY + detachedContentNodesHeight,
|
||||
forwardInfoOriginY: forwardInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||
replyInfoSizeApply: replyInfoSizeApply,
|
||||
replyInfoOriginY: replyInfoOriginY + detachedContentNodesHeight,
|
||||
replyInfoOriginY: replyInfoOriginY + detachedContentNodesHeight + additionalTopHeight,
|
||||
removedContentNodeIndices: removedContentNodeIndices,
|
||||
updatedContentNodeOrder: updatedContentNodeOrder,
|
||||
addedContentNodes: addedContentNodes,
|
||||
@ -3237,6 +3258,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
avatarOffset: avatarOffset,
|
||||
hidesHeaders: hidesHeaders,
|
||||
disablesComments: disablesComments,
|
||||
suggestedPostInfoNodeLayout: suggestedPostInfoNodeLayout,
|
||||
alignment: alignment
|
||||
)
|
||||
})
|
||||
@ -3297,6 +3319,7 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
avatarOffset: CGFloat?,
|
||||
hidesHeaders: Bool,
|
||||
disablesComments: Bool,
|
||||
suggestedPostInfoNodeLayout: (CGSize, () -> ChatMessageSuggestedPostInfoNode)?,
|
||||
alignment: ChatMessageBubbleContentAlignment
|
||||
) -> Void {
|
||||
guard let strongSelf = selfReference.value else {
|
||||
@ -3379,6 +3402,22 @@ public class ChatMessageBubbleItemNode: ChatMessageItemView, ChatMessagePreviewI
|
||||
|
||||
strongSelf.backgroundNode.backgroundFrame = backgroundFrame
|
||||
|
||||
if let (suggestedPostInfoSize, suggestedPostInfoApply) = suggestedPostInfoNodeLayout {
|
||||
let suggestedPostInfoNode = suggestedPostInfoApply()
|
||||
if suggestedPostInfoNode !== strongSelf.suggestedPostInfoNode {
|
||||
strongSelf.suggestedPostInfoNode?.removeFromSupernode()
|
||||
strongSelf.suggestedPostInfoNode = suggestedPostInfoNode
|
||||
strongSelf.mainContextSourceNode.contentNode.addSubnode(suggestedPostInfoNode)
|
||||
|
||||
let suggestedPostInfoFrame = CGRect(origin: CGPoint(x: floor((params.width - suggestedPostInfoSize.width) * 0.5), y: 4.0), size: suggestedPostInfoSize)
|
||||
suggestedPostInfoNode.frame = suggestedPostInfoFrame
|
||||
//animation.animator.updateFrame(layer: suggestedPostInfoNode.layer, frame: suggestedPostInfoFrame, completion: nil)
|
||||
}
|
||||
} else if let suggestedPostInfoNode = strongSelf.suggestedPostInfoNode {
|
||||
strongSelf.suggestedPostInfoNode = nil
|
||||
suggestedPostInfoNode.removeFromSupernode()
|
||||
}
|
||||
|
||||
if let avatarOffset = avatarOffset {
|
||||
strongSelf.updateAttachedAvatarNodeOffset(offset: avatarOffset, transition: .animated(duration: 0.3, curve: .spring))
|
||||
}
|
||||
|
@ -0,0 +1,163 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import Display
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import TextFormat
|
||||
import AccountContext
|
||||
import WallpaperBackgroundNode
|
||||
import ChatMessageItem
|
||||
import TelegramStringFormatting
|
||||
|
||||
public final class ChatMessageSuggestedPostInfoNode: ASDisplayNode {
|
||||
private var titleNode: TextNode?
|
||||
private var priceLabelNode: TextNode?
|
||||
private var priceValueNode: TextNode?
|
||||
private var timeLabelNode: TextNode?
|
||||
private var timeValueNode: TextNode?
|
||||
|
||||
private var backgroundNode: WallpaperBubbleBackgroundNode?
|
||||
|
||||
override public init() {
|
||||
super.init()
|
||||
}
|
||||
|
||||
public typealias AsyncLayout = (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode)
|
||||
|
||||
public static func asyncLayout(_ node: ChatMessageSuggestedPostInfoNode?) -> (ChatMessageItem, CGFloat) -> (CGSize, () -> ChatMessageSuggestedPostInfoNode) {
|
||||
let makeTitleLayout = TextNode.asyncLayout(node?.titleNode)
|
||||
let makePriceLabelLayout = TextNode.asyncLayout(node?.priceLabelNode)
|
||||
let makePriceValueLayout = TextNode.asyncLayout(node?.priceValueNode)
|
||||
let makeTimeLabelLayout = TextNode.asyncLayout(node?.timeLabelNode)
|
||||
let makeTimeValueLayout = TextNode.asyncLayout(node?.timeValueNode)
|
||||
|
||||
return { item, maxWidth in
|
||||
let insets = UIEdgeInsets(
|
||||
top: 12.0,
|
||||
left: 12.0,
|
||||
bottom: 12.0,
|
||||
right: 12.0
|
||||
)
|
||||
|
||||
let titleSpacing: CGFloat = 8.0
|
||||
let labelSpacing: CGFloat = 8.0
|
||||
let valuesVerticalSpacing: CGFloat = 2.0
|
||||
|
||||
var amount: Int64 = 0
|
||||
var timestamp: Int32?
|
||||
for attribute in item.message.attributes {
|
||||
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
|
||||
amount = attribute.price.value
|
||||
timestamp = attribute.timestamp
|
||||
}
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
let amountString: String
|
||||
if amount == 0 {
|
||||
amountString = "Free"
|
||||
} else if amount == 1 {
|
||||
amountString = "1 Star"
|
||||
} else {
|
||||
amountString = "\(amount) Stars"
|
||||
}
|
||||
|
||||
var timestampString: String
|
||||
if let timestamp {
|
||||
timestampString = humanReadableStringForTimestamp(strings: item.presentationData.strings, dateTimeFormat: PresentationDateTimeFormat(), timestamp: timestamp, alwaysShowTime: true).string
|
||||
if timestampString.count > 1 {
|
||||
timestampString = String(timestampString[timestampString.startIndex]).capitalized + timestampString[timestampString.index(after: timestampString.startIndex)...]
|
||||
}
|
||||
} else {
|
||||
timestampString = "Anytime"
|
||||
}
|
||||
|
||||
let serviceColor = serviceMessageColorComponents(theme: item.presentationData.theme.theme, wallpaper: item.presentationData.theme.wallpaper)
|
||||
|
||||
//TODO:localize
|
||||
let titleLayout = makeTitleLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "You suggest to post\nthis message.", font: Font.regular(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .center, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let priceLabelLayout = makePriceLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Price", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let timeLabelLayout = makeTimeLabelLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: "Time", font: Font.regular(13.0), textColor: serviceColor.primaryText.withMultipliedAlpha(0.5)), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
let priceValueLayout = makePriceValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: amountString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
let timeValueLayout = makeTimeValueLayout(TextNodeLayoutArguments(attributedString: NSAttributedString(string: timestampString, font: Font.semibold(13.0), textColor: serviceColor.primaryText), backgroundColor: nil, maximumNumberOfLines: 0, truncationType: .end, constrainedSize: CGSize(width: maxWidth - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude), alignment: .natural, cutout: nil, insets: UIEdgeInsets()))
|
||||
|
||||
var maxContentWidth: CGFloat = 0.0
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
maxContentWidth = max(maxContentWidth, titleLayout.0.size.width)
|
||||
|
||||
contentHeight += titleLayout.0.size.height
|
||||
contentHeight += titleSpacing
|
||||
|
||||
maxContentWidth = max(maxContentWidth, priceLabelLayout.0.size.width + labelSpacing + priceValueLayout.0.size.width)
|
||||
contentHeight += priceLabelLayout.0.size.height + valuesVerticalSpacing
|
||||
|
||||
maxContentWidth = max(maxContentWidth, timeLabelLayout.0.size.width + labelSpacing + timeValueLayout.0.size.width)
|
||||
contentHeight += timeLabelLayout.0.size.height
|
||||
|
||||
let size = CGSize(width: insets.left + insets.right + maxContentWidth, height: insets.top + insets.bottom + contentHeight)
|
||||
|
||||
return (size, {
|
||||
let node = node ?? ChatMessageSuggestedPostInfoNode()
|
||||
|
||||
if node.backgroundNode == nil {
|
||||
if let backgroundNode = item.controllerInteraction.presentationContext.backgroundNode?.makeBubbleBackground(for: .free) {
|
||||
node.backgroundNode = backgroundNode
|
||||
backgroundNode.layer.masksToBounds = true
|
||||
backgroundNode.layer.cornerRadius = 15.0
|
||||
node.insertSubnode(backgroundNode, at: 0)
|
||||
}
|
||||
}
|
||||
|
||||
if let backgroundNode = node.backgroundNode {
|
||||
backgroundNode.frame = CGRect(origin: CGPoint(), size: size)
|
||||
}
|
||||
|
||||
let titleNode = titleLayout.1()
|
||||
if node.titleNode !== titleNode {
|
||||
node.titleNode = titleNode
|
||||
node.addSubnode(titleNode)
|
||||
}
|
||||
let priceLabelNode = priceLabelLayout.1()
|
||||
if node.priceLabelNode !== priceLabelNode {
|
||||
node.priceLabelNode = priceLabelNode
|
||||
node.addSubnode(priceLabelNode)
|
||||
}
|
||||
let priceValueNode = priceValueLayout.1()
|
||||
if node.priceValueNode !== priceValueNode {
|
||||
node.priceValueNode = priceValueNode
|
||||
node.addSubnode(priceValueNode)
|
||||
}
|
||||
let timeLabelNode = timeLabelLayout.1()
|
||||
if node.timeLabelNode !== timeLabelNode {
|
||||
node.timeLabelNode = timeLabelNode
|
||||
node.addSubnode(timeLabelNode)
|
||||
}
|
||||
let timeValueNode = timeValueLayout.1()
|
||||
if node.timeValueNode !== timeValueNode {
|
||||
node.timeValueNode = timeValueNode
|
||||
node.addSubnode(timeValueNode)
|
||||
}
|
||||
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((size.width - titleLayout.0.size.width) * 0.5), y: insets.top), size: titleLayout.0.size)
|
||||
titleNode.frame = titleFrame
|
||||
|
||||
let priceLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: titleFrame.maxY + titleSpacing), size: priceLabelLayout.0.size)
|
||||
priceLabelNode.frame = priceLabelFrame
|
||||
priceValueNode.frame = CGRect(origin: CGPoint(x: priceLabelFrame.maxX + labelSpacing, y: priceLabelFrame.minY), size: priceValueLayout.0.size)
|
||||
|
||||
let timeLabelFrame = CGRect(origin: CGPoint(x: insets.left, y: priceLabelFrame.maxY + valuesVerticalSpacing), size: timeLabelLayout.0.size)
|
||||
timeLabelNode.frame = timeLabelFrame
|
||||
timeValueNode.frame = CGRect(origin: CGPoint(x: timeLabelFrame.maxX + labelSpacing, y: timeLabelFrame.minY), size: timeValueLayout.0.size)
|
||||
|
||||
return node
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -95,6 +95,17 @@ public func stringForMessageTimestampStatus(accountPeerId: PeerId, message: Mess
|
||||
dateText = " "
|
||||
}
|
||||
|
||||
for attribute in message.attributes {
|
||||
if let attribute = attribute as? OutgoingSuggestedPostMessageAttribute {
|
||||
if let timestamp = attribute.timestamp {
|
||||
dateText = stringForMessageTimestamp(timestamp: timestamp, dateTimeFormat: dateTimeFormat)
|
||||
} else {
|
||||
//TODO:localize
|
||||
dateText = "Anytime"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if message.id.namespace == Namespaces.Message.ScheduledCloud, let _ = message.pendingProcessingAttribute {
|
||||
return "appx. \(dateText)"
|
||||
}
|
||||
|
@ -367,6 +367,10 @@ public final class ChatMessageItemImpl: ChatMessageItem, CustomStringConvertible
|
||||
}
|
||||
}
|
||||
|
||||
if let subject = associatedData.subject, case let .customChatContents(contents) = subject, case .postSuggestions = contents.kind {
|
||||
hasAvatar = false
|
||||
}
|
||||
|
||||
if hasAvatar {
|
||||
if let effectiveAuthor = effectiveAuthor {
|
||||
var storyStats: PeerStoryStats?
|
||||
|
@ -147,6 +147,7 @@ public final class ChatRecentActionsController: TelegramBaseController {
|
||||
}, joinGroupCall: { _ in
|
||||
}, presentInviteMembers: {
|
||||
}, presentGigagroupHelp: {
|
||||
}, openSuggestPost: {
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, updateShowCommands: { _ in
|
||||
}, updateShowSendAsPeers: { _ in
|
||||
|
@ -35,6 +35,8 @@ swift_library(
|
||||
"//submodules/CheckNode",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/Stars/StarsBalanceOverlayComponent",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
"//submodules/TelegramUI/Components/ChatScheduleTimeController",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1816,6 +1816,8 @@ public final class ChatEntityKeyboardInputNode: ChatInputNode {
|
||||
case .businessLinkSetup:
|
||||
stickerContent = nil
|
||||
gifContent = nil
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,7 @@ import TelegramPresentationData
|
||||
public enum ChatScheduleTimeControllerMode {
|
||||
case scheduledMessages(sendWhenOnlineAvailable: Bool)
|
||||
case reminders
|
||||
case suggestPost
|
||||
}
|
||||
|
||||
public enum ChatScheduleTimeControllerStyle {
|
||||
@ -82,7 +83,11 @@ public final class ChatScheduleTimeController: ViewController {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.completion(time == scheduleWhenOnlineTimestamp ? time : time + 5)
|
||||
if time == 0 {
|
||||
strongSelf.completion(time)
|
||||
} else {
|
||||
strongSelf.completion(time == scheduleWhenOnlineTimestamp ? time : time + 5)
|
||||
}
|
||||
strongSelf.dismiss()
|
||||
}
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
|
@ -94,10 +94,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
|
||||
let title: String
|
||||
switch mode {
|
||||
case .scheduledMessages:
|
||||
title = self.presentationData.strings.Conversation_ScheduleMessage_Title
|
||||
case .reminders:
|
||||
title = self.presentationData.strings.Conversation_SetReminder_Title
|
||||
case .scheduledMessages:
|
||||
title = self.presentationData.strings.Conversation_ScheduleMessage_Title
|
||||
case .reminders:
|
||||
title = self.presentationData.strings.Conversation_SetReminder_Title
|
||||
case .suggestPost:
|
||||
//TODO:localize
|
||||
title = "Time"
|
||||
}
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
@ -113,7 +116,13 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
self.doneButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(theme: self.presentationData.theme), height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
|
||||
self.onlineButton = SolidRoundedButtonNode(theme: SolidRoundedButtonTheme(backgroundColor: buttonColor, foregroundColor: buttonTextColor), font: .regular, height: 52.0, cornerRadius: 11.0, gloss: false)
|
||||
self.onlineButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendWhenOnline
|
||||
switch mode {
|
||||
case .suggestPost:
|
||||
//TODO:localize
|
||||
self.onlineButton.title = "Send Anytime"
|
||||
default:
|
||||
self.onlineButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendWhenOnline
|
||||
}
|
||||
|
||||
self.dateFormatter = DateFormatter()
|
||||
self.dateFormatter.timeStyle = .none
|
||||
@ -141,6 +150,8 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
self.contentContainerNode.addSubnode(self.doneButton)
|
||||
if case .scheduledMessages(true) = self.mode {
|
||||
self.contentContainerNode.addSubnode(self.onlineButton)
|
||||
} else if case .suggestPost = self.mode {
|
||||
self.contentContainerNode.addSubnode(self.onlineButton)
|
||||
}
|
||||
|
||||
self.cancelButton.addTarget(self, action: #selector(self.cancelButtonPressed), forControlEvents: .touchUpInside)
|
||||
@ -159,7 +170,12 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
self.onlineButton.pressed = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.onlineButton.isUserInteractionEnabled = false
|
||||
strongSelf.completion?(scheduleWhenOnlineTimestamp)
|
||||
switch strongSelf.mode {
|
||||
case .suggestPost:
|
||||
strongSelf.completion?(0)
|
||||
default:
|
||||
strongSelf.completion?(scheduleWhenOnlineTimestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,22 +289,30 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
|
||||
let time = stringForMessageTimestamp(timestamp: Int32(date.timeIntervalSince1970), dateTimeFormat: self.presentationData.dateTimeFormat)
|
||||
switch mode {
|
||||
case .scheduledMessages:
|
||||
if calendar.isDateInToday(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendToday(time).string
|
||||
} else if calendar.isDateInTomorrow(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendTomorrow(time).string
|
||||
} else {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string
|
||||
}
|
||||
case .reminders:
|
||||
if calendar.isDateInToday(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindToday(time).string
|
||||
} else if calendar.isDateInTomorrow(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindTomorrow(time).string
|
||||
} else {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string
|
||||
}
|
||||
case .scheduledMessages:
|
||||
if calendar.isDateInToday(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendToday(time).string
|
||||
} else if calendar.isDateInTomorrow(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendTomorrow(time).string
|
||||
} else {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string
|
||||
}
|
||||
case .reminders:
|
||||
if calendar.isDateInToday(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindToday(time).string
|
||||
} else if calendar.isDateInTomorrow(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindTomorrow(time).string
|
||||
} else {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_SetReminder_RemindOn(self.dateFormatter.string(from: date), time).string
|
||||
}
|
||||
case .suggestPost:
|
||||
if calendar.isDateInToday(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendToday(time).string
|
||||
} else if calendar.isDateInTomorrow(date) {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendTomorrow(time).string
|
||||
} else {
|
||||
self.doneButton.title = self.presentationData.strings.Conversation_ScheduleMessage_SendOn(self.dateFormatter.string(from: date), time).string
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,6 +406,8 @@ class ChatScheduleTimeControllerNode: ViewControllerTracingNode, ASScrollViewDel
|
||||
var buttonOffset: CGFloat = 0.0
|
||||
if case .scheduledMessages(true) = self.mode {
|
||||
buttonOffset += 64.0
|
||||
} else if case .suggestPost = self.mode {
|
||||
buttonOffset += 64.0
|
||||
}
|
||||
|
||||
let bottomInset: CGFloat = 10.0 + cleanInsets.bottom
|
||||
|
@ -15,14 +15,16 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
public let selectedIndex: Int
|
||||
public let minSelectedIndex: Int?
|
||||
public let title: String?
|
||||
public let secondaryTitle: String?
|
||||
public let selectedIndexUpdated: (Int) -> Void
|
||||
|
||||
public init(values: [String], markPositions: Bool, selectedIndex: Int, minSelectedIndex: Int? = nil, title: String?, selectedIndexUpdated: @escaping (Int) -> Void) {
|
||||
public init(values: [String], markPositions: Bool, selectedIndex: Int, minSelectedIndex: Int? = nil, title: String?, secondaryTitle: String? = nil, selectedIndexUpdated: @escaping (Int) -> Void) {
|
||||
self.values = values
|
||||
self.markPositions = markPositions
|
||||
self.selectedIndex = selectedIndex
|
||||
self.minSelectedIndex = minSelectedIndex
|
||||
self.title = title
|
||||
self.secondaryTitle = secondaryTitle
|
||||
self.selectedIndexUpdated = selectedIndexUpdated
|
||||
}
|
||||
|
||||
@ -42,6 +44,9 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
if lhs.title != rhs.title {
|
||||
return false
|
||||
}
|
||||
if lhs.secondaryTitle != rhs.secondaryTitle {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
@ -112,6 +117,7 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
public final class View: UIView, ListSectionComponent.ChildView {
|
||||
private var titles: [Int: ComponentView<Empty>] = [:]
|
||||
private var mainTitle: ComponentView<Empty>?
|
||||
private var secondaryTitle: ComponentView<Empty>?
|
||||
private var slider = ComponentView<Empty>()
|
||||
|
||||
private var component: ListItemSliderSelectorComponent?
|
||||
@ -140,10 +146,12 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
|
||||
var validIds: [Int] = []
|
||||
var mainTitleValue: String?
|
||||
var secondaryTitleValue: String?
|
||||
|
||||
switch component.content {
|
||||
case let .discrete(discrete):
|
||||
mainTitleValue = discrete.title
|
||||
secondaryTitleValue = discrete.secondaryTitle
|
||||
|
||||
for i in 0 ..< discrete.values.count {
|
||||
if discrete.title != nil {
|
||||
@ -254,7 +262,44 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let mainTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - mainTitleSize.width) * 0.5), y: 10.0), size: mainTitleSize)
|
||||
|
||||
var secondaryTitleView: ComponentView<Empty>?
|
||||
var secondaryTitleSize: CGSize?
|
||||
if let secondaryTitleValue {
|
||||
let secondaryTitle: ComponentView<Empty>
|
||||
if let current = self.secondaryTitle {
|
||||
secondaryTitle = current
|
||||
} else {
|
||||
secondaryTitle = ComponentView()
|
||||
mainTitleTransition = mainTitleTransition.withAnimation(.none)
|
||||
self.secondaryTitle = secondaryTitle
|
||||
}
|
||||
secondaryTitleView = secondaryTitle
|
||||
|
||||
secondaryTitleSize = secondaryTitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: secondaryTitleValue, font: Font.regular(12.0), textColor: component.theme.list.itemSecondaryTextColor))
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
} else {
|
||||
mainTitleTransition = mainTitleTransition.withAnimation(.none)
|
||||
|
||||
if let secondaryTitle = self.secondaryTitle {
|
||||
self.secondaryTitle = nil
|
||||
secondaryTitle.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
var mainTitleContentWidth = mainTitleSize.width
|
||||
let secondaryTitleSpacing: CGFloat = 2.0
|
||||
if let secondaryTitleSize {
|
||||
mainTitleContentWidth += secondaryTitleSpacing + secondaryTitleSize.width
|
||||
}
|
||||
|
||||
let mainTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - mainTitleContentWidth) * 0.5), y: 10.0), size: mainTitleSize)
|
||||
if let mainTitleView = mainTitle.view {
|
||||
if mainTitleView.superview == nil {
|
||||
self.addSubview(mainTitleView)
|
||||
@ -262,11 +307,26 @@ public final class ListItemSliderSelectorComponent: Component {
|
||||
mainTitleView.bounds = CGRect(origin: CGPoint(), size: mainTitleFrame.size)
|
||||
mainTitleTransition.setPosition(view: mainTitleView, position: mainTitleFrame.center)
|
||||
}
|
||||
|
||||
if let secondaryTitleView, let secondaryTitleSize {
|
||||
let secondaryTitleFrame = CGRect(origin: CGPoint(x: mainTitleFrame.maxX + secondaryTitleSpacing, y: mainTitleFrame.minY + floorToScreenPixels((mainTitleFrame.height - secondaryTitleSize.height) * 0.5)), size: secondaryTitleSize)
|
||||
if let secondaryTitleComponentView = secondaryTitleView.view {
|
||||
if secondaryTitleComponentView.superview == nil {
|
||||
self.addSubview(secondaryTitleComponentView)
|
||||
}
|
||||
secondaryTitleComponentView.bounds = CGRect(origin: CGPoint(), size: secondaryTitleFrame.size)
|
||||
mainTitleTransition.setPosition(view: secondaryTitleComponentView, position: secondaryTitleFrame.center)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let mainTitle = self.mainTitle {
|
||||
self.mainTitle = nil
|
||||
mainTitle.view?.removeFromSuperview()
|
||||
}
|
||||
if let secondaryTitle = self.secondaryTitle {
|
||||
self.secondaryTitle = nil
|
||||
secondaryTitle.view?.removeFromSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
let sliderSize: CGSize
|
||||
|
@ -0,0 +1,22 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "ListSwitchItemComponent",
|
||||
module_name = "ListSwitchItemComponent",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/TelegramUI/Components/SwitchComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -6,13 +6,13 @@ import ComponentFlow
|
||||
import ComponentDisplayAdapters
|
||||
import SwitchComponent
|
||||
|
||||
final class ListSwitchItemComponent: Component {
|
||||
public final class ListSwitchItemComponent: Component {
|
||||
let theme: PresentationTheme
|
||||
let title: String
|
||||
let value: Bool
|
||||
let valueUpdated: (Bool) -> Void
|
||||
|
||||
init(
|
||||
public init(
|
||||
theme: PresentationTheme,
|
||||
title: String,
|
||||
value: Bool,
|
||||
@ -24,7 +24,7 @@ final class ListSwitchItemComponent: Component {
|
||||
self.valueUpdated = valueUpdated
|
||||
}
|
||||
|
||||
static func ==(lhs: ListSwitchItemComponent, rhs: ListSwitchItemComponent) -> Bool {
|
||||
public static func ==(lhs: ListSwitchItemComponent, rhs: ListSwitchItemComponent) -> Bool {
|
||||
if lhs.theme !== rhs.theme {
|
||||
return false
|
||||
}
|
||||
@ -37,7 +37,7 @@ final class ListSwitchItemComponent: Component {
|
||||
return true
|
||||
}
|
||||
|
||||
final class View: UIView {
|
||||
public final class View: UIView {
|
||||
private let title = ComponentView<Empty>()
|
||||
private let switchView = ComponentView<Empty>()
|
||||
|
||||
@ -106,11 +106,11 @@ final class ListSwitchItemComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
public func makeView() -> View {
|
||||
return View(frame: CGRect())
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<Empty>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
@ -35,6 +35,7 @@ swift_library(
|
||||
"//submodules/Components/HierarchyTrackingLayer",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/ListSwitchItemComponent",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -23,6 +23,7 @@ import AudioToolbox
|
||||
import PremiumLockButtonSubtitleComponent
|
||||
import ListSectionComponent
|
||||
import ListItemSliderSelectorComponent
|
||||
import ListSwitchItemComponent
|
||||
|
||||
final class PeerAllowedReactionsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
@ -411,6 +411,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode {
|
||||
}, joinGroupCall: { _ in
|
||||
}, presentInviteMembers: {
|
||||
}, presentGigagroupHelp: {
|
||||
}, openSuggestPost: {
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, updateShowCommands: { _ in
|
||||
}, updateShowSendAsPeers: { _ in
|
||||
@ -566,6 +567,7 @@ private final class PeerInfoInteraction {
|
||||
let editingOpenNameColorSetup: () -> Void
|
||||
let editingOpenInviteLinksSetup: () -> Void
|
||||
let editingOpenDiscussionGroupSetup: () -> Void
|
||||
let editingOpenPostSuggestionsSetup: () -> Void
|
||||
let editingOpenRevenue: () -> Void
|
||||
let editingOpenStars: () -> Void
|
||||
let openParticipantsSection: (PeerInfoParticipantsSection) -> Void
|
||||
@ -636,6 +638,7 @@ private final class PeerInfoInteraction {
|
||||
editingOpenNameColorSetup: @escaping () -> Void,
|
||||
editingOpenInviteLinksSetup: @escaping () -> Void,
|
||||
editingOpenDiscussionGroupSetup: @escaping () -> Void,
|
||||
editingOpenPostSuggestionsSetup: @escaping () -> Void,
|
||||
editingOpenRevenue: @escaping () -> Void,
|
||||
editingOpenStars: @escaping () -> Void,
|
||||
openParticipantsSection: @escaping (PeerInfoParticipantsSection) -> Void,
|
||||
@ -705,6 +708,7 @@ private final class PeerInfoInteraction {
|
||||
self.editingOpenNameColorSetup = editingOpenNameColorSetup
|
||||
self.editingOpenInviteLinksSetup = editingOpenInviteLinksSetup
|
||||
self.editingOpenDiscussionGroupSetup = editingOpenDiscussionGroupSetup
|
||||
self.editingOpenPostSuggestionsSetup = editingOpenPostSuggestionsSetup
|
||||
self.editingOpenRevenue = editingOpenRevenue
|
||||
self.editingOpenStars = editingOpenStars
|
||||
self.openParticipantsSection = openParticipantsSection
|
||||
@ -2154,6 +2158,7 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
||||
let ItemBanned = 11
|
||||
let ItemRecentActions = 12
|
||||
let ItemAffiliatePrograms = 13
|
||||
let ItemPostSuggestionsSettings = 14
|
||||
|
||||
let isCreator = channel.flags.contains(.isCreator)
|
||||
|
||||
@ -2200,6 +2205,11 @@ private func editingItems(data: PeerInfoScreenData?, state: PeerInfoState, chatL
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemDiscussionGroup, label: .text(discussionGroupTitle), text: presentationData.strings.Channel_DiscussionGroup, icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: {
|
||||
interaction.editingOpenDiscussionGroupSetup()
|
||||
}))
|
||||
|
||||
//TODO:localize
|
||||
items[.peerSettings]!.append(PeerInfoScreenDisclosureItem(id: ItemPostSuggestionsSettings, label: .text("Off"), additionalBadgeLabel: presentationData.strings.Settings_New, text: "Post Suggestions", icon: UIImage(bundleImageName: "Chat/Info/GroupDiscussionIcon"), action: {
|
||||
interaction.editingOpenPostSuggestionsSetup()
|
||||
}))
|
||||
}
|
||||
|
||||
if isCreator || (channel.adminRights?.rights.contains(.canChangeInfo) == true) {
|
||||
@ -2996,6 +3006,9 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
editingOpenDiscussionGroupSetup: { [weak self] in
|
||||
self?.editingOpenDiscussionGroupSetup()
|
||||
},
|
||||
editingOpenPostSuggestionsSetup: { [weak self] in
|
||||
self?.editingOpenPostSuggestionsSetup()
|
||||
},
|
||||
editingOpenRevenue: { [weak self] in
|
||||
self?.editingOpenRevenue()
|
||||
},
|
||||
@ -9093,6 +9106,14 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, PeerInfoScreenNodePro
|
||||
self.controller?.push(channelDiscussionGroupSetupController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData, peerId: peer.id))
|
||||
}
|
||||
|
||||
private func editingOpenPostSuggestionsSetup() {
|
||||
guard let data = self.data, let peer = data.peer else {
|
||||
return
|
||||
}
|
||||
let _ = peer
|
||||
self.controller?.push(self.context.sharedContext.makePostSuggestionsSettingsScreen(context: self.context))
|
||||
}
|
||||
|
||||
private func editingOpenRevenue() {
|
||||
guard let revenueContext = self.data?.revenueStatsContext else {
|
||||
return
|
||||
|
@ -0,0 +1,38 @@
|
||||
load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library")
|
||||
|
||||
swift_library(
|
||||
name = "PostSuggestionsSettingsScreen",
|
||||
module_name = "PostSuggestionsSettingsScreen",
|
||||
srcs = glob([
|
||||
"Sources/**/*.swift",
|
||||
]),
|
||||
copts = [
|
||||
"-warnings-as-errors",
|
||||
],
|
||||
deps = [
|
||||
"//submodules/Display",
|
||||
"//submodules/TelegramPresentationData",
|
||||
"//submodules/ComponentFlow",
|
||||
"//submodules/Components/ComponentDisplayAdapters",
|
||||
"//submodules/AppBundle",
|
||||
"//submodules/Components/ViewControllerComponent",
|
||||
"//submodules/SSignalKit/SwiftSignalKit",
|
||||
"//submodules/TelegramCore",
|
||||
"//submodules/Postbox",
|
||||
"//submodules/AccountContext",
|
||||
"//submodules/TelegramUI/Components/SwitchComponent",
|
||||
"//submodules/Components/MultilineTextComponent",
|
||||
"//submodules/Markdown",
|
||||
"//submodules/TelegramUI/Components/ButtonComponent",
|
||||
"//submodules/Components/BundleIconComponent",
|
||||
"//submodules/TextFormat",
|
||||
"//submodules/TelegramUI/Components/ListSectionComponent",
|
||||
"//submodules/TelegramUI/Components/ListItemSliderSelectorComponent",
|
||||
"//submodules/TelegramUI/Components/ListSwitchItemComponent",
|
||||
"//submodules/TelegramUI/Components/ListActionItemComponent",
|
||||
"//submodules/TelegramStringFormatting",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
@ -0,0 +1,153 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import AccountContext
|
||||
|
||||
public final class PostSuggestionsChatContents: ChatCustomContentsProtocol {
|
||||
private final class Impl {
|
||||
let queue: Queue
|
||||
let context: AccountContext
|
||||
|
||||
private var peerId: EnginePeer.Id
|
||||
|
||||
private(set) var mergedHistoryView: MessageHistoryView?
|
||||
private var sourceHistoryView: MessageHistoryView?
|
||||
|
||||
private var historyViewDisposable: Disposable?
|
||||
private var pendingHistoryViewDisposable: Disposable?
|
||||
let historyViewStream = ValuePipe<(MessageHistoryView, ViewUpdateType)>()
|
||||
private var nextUpdateIsHoleFill: Bool = false
|
||||
|
||||
init(queue: Queue, context: AccountContext, peerId: EnginePeer.Id) {
|
||||
self.queue = queue
|
||||
self.context = context
|
||||
self.peerId = peerId
|
||||
|
||||
self.updateHistoryViewRequest(reload: false)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.historyViewDisposable?.dispose()
|
||||
self.pendingHistoryViewDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateHistoryViewRequest(reload: Bool) {
|
||||
self.pendingHistoryViewDisposable?.dispose()
|
||||
self.pendingHistoryViewDisposable = nil
|
||||
|
||||
if self.historyViewDisposable == nil || reload {
|
||||
self.historyViewDisposable?.dispose()
|
||||
|
||||
self.historyViewDisposable = (self.context.account.viewTracker.postSuggestionsViewForLocation(peerId: self.peerId)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] view, update, _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
if update == .FillHole {
|
||||
self.nextUpdateIsHoleFill = true
|
||||
self.updateHistoryViewRequest(reload: true)
|
||||
return
|
||||
}
|
||||
|
||||
let nextUpdateIsHoleFill = self.nextUpdateIsHoleFill
|
||||
self.nextUpdateIsHoleFill = false
|
||||
|
||||
self.sourceHistoryView = view
|
||||
|
||||
self.updateHistoryView(updateType: nextUpdateIsHoleFill ? .FillHole : .Generic)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private func updateHistoryView(updateType: ViewUpdateType) {
|
||||
var entries = self.sourceHistoryView?.entries ?? []
|
||||
entries.sort(by: { $0.message.index < $1.message.index })
|
||||
|
||||
let mergedHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allSuggestedPost), entries: entries, holeEarlier: false, holeLater: false, isLoading: false)
|
||||
self.mergedHistoryView = mergedHistoryView
|
||||
|
||||
self.historyViewStream.putNext((mergedHistoryView, updateType))
|
||||
}
|
||||
|
||||
func enqueueMessages(messages: [EnqueueMessage]) {
|
||||
let _ = (TelegramCore.enqueueMessages(account: self.context.account, peerId: self.peerId, messages: messages.compactMap { message -> EnqueueMessage? in
|
||||
if !message.attributes.contains(where: { $0 is OutgoingSuggestedPostMessageAttribute }) {
|
||||
return nil
|
||||
}
|
||||
return message
|
||||
})
|
||||
|> deliverOn(self.queue)).startStandalone()
|
||||
}
|
||||
|
||||
func deleteMessages(ids: [EngineMessage.Id]) {
|
||||
let _ = self.context.engine.messages.deleteMessagesInteractively(messageIds: ids, type: .forEveryone).startStandalone()
|
||||
}
|
||||
|
||||
func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) {
|
||||
}
|
||||
}
|
||||
|
||||
public let peerId: EnginePeer.Id
|
||||
public var kind: ChatCustomContentsKind
|
||||
|
||||
public var historyView: Signal<(MessageHistoryView, ViewUpdateType), NoError> {
|
||||
return self.impl.signalWith({ impl, subscriber in
|
||||
if let mergedHistoryView = impl.mergedHistoryView {
|
||||
subscriber.putNext((mergedHistoryView, .Initial))
|
||||
}
|
||||
return impl.historyViewStream.signal().start(next: subscriber.putNext)
|
||||
})
|
||||
}
|
||||
|
||||
public var messageLimit: Int? {
|
||||
return 20
|
||||
}
|
||||
|
||||
private let queue: Queue
|
||||
private let impl: QueueLocalObject<Impl>
|
||||
|
||||
public init(context: AccountContext, peerId: EnginePeer.Id) {
|
||||
self.peerId = peerId
|
||||
self.kind = .postSuggestions(price: StarsAmount(value: 250, nanos: 0))
|
||||
|
||||
let queue = Queue()
|
||||
self.queue = queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return Impl(queue: queue, context: context, peerId: peerId)
|
||||
})
|
||||
}
|
||||
|
||||
public func enqueueMessages(messages: [EnqueueMessage]) {
|
||||
self.impl.with { impl in
|
||||
impl.enqueueMessages(messages: messages)
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteMessages(ids: [EngineMessage.Id]) {
|
||||
self.impl.with { impl in
|
||||
impl.deleteMessages(ids: ids)
|
||||
}
|
||||
}
|
||||
|
||||
public func editMessage(id: EngineMessage.Id, text: String, media: RequestEditMessageMedia, entities: TextEntitiesMessageAttribute?, webpagePreviewAttribute: WebpagePreviewMessageAttribute?, disableUrlPreview: Bool) {
|
||||
self.impl.with { impl in
|
||||
impl.editMessage(id: id, text: text, media: media, entities: entities, webpagePreviewAttribute: webpagePreviewAttribute, disableUrlPreview: disableUrlPreview)
|
||||
}
|
||||
}
|
||||
|
||||
public func quickReplyUpdateShortcut(value: String) {
|
||||
}
|
||||
|
||||
public func businessLinkUpdate(message: String, entities: [MessageTextEntity], title: String?) {
|
||||
}
|
||||
|
||||
public func loadMore() {
|
||||
}
|
||||
|
||||
public func hashtagSearchUpdate(query: String) {
|
||||
}
|
||||
|
||||
public var hashtagSearchResultsUpdate: ((SearchMessagesResult, SearchMessagesState)) -> Void = { _ in }
|
||||
}
|
@ -0,0 +1,487 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import Display
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import TelegramPresentationData
|
||||
import TelegramUIPreferences
|
||||
import PresentationDataUtils
|
||||
import AccountContext
|
||||
import ComponentFlow
|
||||
import ViewControllerComponent
|
||||
import MultilineTextComponent
|
||||
import BalancedTextComponent
|
||||
import ListSectionComponent
|
||||
import BundleIconComponent
|
||||
import LottieComponent
|
||||
import ListSwitchItemComponent
|
||||
import ListItemSliderSelectorComponent
|
||||
import ListSwitchItemComponent
|
||||
import ListActionItemComponent
|
||||
import Markdown
|
||||
import TelegramStringFormatting
|
||||
|
||||
final class PostSuggestionsSettingsScreenComponent: Component {
|
||||
typealias EnvironmentType = ViewControllerComponentContainer.Environment
|
||||
|
||||
let context: AccountContext
|
||||
let completion: () -> Void
|
||||
|
||||
init(
|
||||
context: AccountContext,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
self.completion = completion
|
||||
}
|
||||
|
||||
static func ==(lhs: PostSuggestionsSettingsScreenComponent, rhs: PostSuggestionsSettingsScreenComponent) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
private final class ScrollView: UIScrollView {
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
final class View: UIView, UIScrollViewDelegate {
|
||||
private let topOverscrollLayer = SimpleLayer()
|
||||
private let scrollView: ScrollView
|
||||
|
||||
private let navigationTitle = ComponentView<Empty>()
|
||||
private let icon = ComponentView<Empty>()
|
||||
private let subtitle = ComponentView<Empty>()
|
||||
private let switchSection = ComponentView<Empty>()
|
||||
private let contentSection = ComponentView<Empty>()
|
||||
|
||||
private var isUpdating: Bool = false
|
||||
|
||||
private var component: PostSuggestionsSettingsScreenComponent?
|
||||
private(set) weak var state: EmptyComponentState?
|
||||
private var environment: EnvironmentType?
|
||||
|
||||
private var areSuggestionsEnabled: Bool = false
|
||||
private var starCount: Int = 0
|
||||
|
||||
override init(frame: CGRect) {
|
||||
self.scrollView = ScrollView()
|
||||
self.scrollView.showsVerticalScrollIndicator = true
|
||||
self.scrollView.showsHorizontalScrollIndicator = false
|
||||
self.scrollView.scrollsToTop = false
|
||||
self.scrollView.delaysContentTouches = false
|
||||
self.scrollView.canCancelContentTouches = true
|
||||
self.scrollView.contentInsetAdjustmentBehavior = .never
|
||||
if #available(iOS 13.0, *) {
|
||||
self.scrollView.automaticallyAdjustsScrollIndicatorInsets = false
|
||||
}
|
||||
self.scrollView.alwaysBounceVertical = true
|
||||
|
||||
super.init(frame: frame)
|
||||
|
||||
self.scrollView.delegate = self
|
||||
self.addSubview(self.scrollView)
|
||||
|
||||
self.scrollView.layer.addSublayer(self.topOverscrollLayer)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
func scrollToTop() {
|
||||
self.scrollView.setContentOffset(CGPoint(), animated: true)
|
||||
}
|
||||
|
||||
func attemptNavigation(complete: @escaping () -> Void) -> Bool {
|
||||
guard let component = self.component, let environment = self.environment else {
|
||||
return true
|
||||
}
|
||||
|
||||
let _ = component
|
||||
let _ = environment
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
self.updateScrolling(transition: .immediate)
|
||||
}
|
||||
|
||||
var scrolledUp = true
|
||||
private func updateScrolling(transition: ComponentTransition) {
|
||||
let navigationRevealOffsetY: CGFloat = 0.0
|
||||
|
||||
let navigationAlphaDistance: CGFloat = 16.0
|
||||
let navigationAlpha: CGFloat = max(0.0, min(1.0, (self.scrollView.contentOffset.y - navigationRevealOffsetY) / navigationAlphaDistance))
|
||||
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
|
||||
transition.setAlpha(layer: navigationBar.backgroundNode.layer, alpha: navigationAlpha)
|
||||
transition.setAlpha(layer: navigationBar.stripeNode.layer, alpha: navigationAlpha)
|
||||
}
|
||||
|
||||
var scrolledUp = false
|
||||
if navigationAlpha < 0.5 {
|
||||
scrolledUp = true
|
||||
} else if navigationAlpha > 0.5 {
|
||||
scrolledUp = false
|
||||
}
|
||||
|
||||
if self.scrolledUp != scrolledUp {
|
||||
self.scrolledUp = scrolledUp
|
||||
if !self.isUpdating {
|
||||
self.state?.updated()
|
||||
}
|
||||
}
|
||||
|
||||
if let navigationTitleView = self.navigationTitle.view {
|
||||
transition.setAlpha(view: navigationTitleView, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
func update(component: PostSuggestionsSettingsScreenComponent, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
self.isUpdating = true
|
||||
defer {
|
||||
self.isUpdating = false
|
||||
}
|
||||
|
||||
if self.component == nil {
|
||||
self.starCount = 20
|
||||
}
|
||||
|
||||
let environment = environment[EnvironmentType.self].value
|
||||
let themeUpdated = self.environment?.theme !== environment.theme
|
||||
self.environment = environment
|
||||
|
||||
self.component = component
|
||||
self.state = state
|
||||
|
||||
let alphaTransition: ComponentTransition
|
||||
if !transition.animation.isImmediate {
|
||||
alphaTransition = .easeInOut(duration: 0.25)
|
||||
} else {
|
||||
alphaTransition = .immediate
|
||||
}
|
||||
|
||||
if themeUpdated {
|
||||
self.backgroundColor = environment.theme.list.blocksBackgroundColor
|
||||
}
|
||||
|
||||
let presentationData = component.context.sharedContext.currentPresentationData.with { $0 }
|
||||
|
||||
//TODO:localize
|
||||
let navigationTitleSize = self.navigationTitle.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(string: "Post Suggestion", font: Font.semibold(17.0), textColor: environment.theme.rootController.navigationBar.primaryTextColor)),
|
||||
horizontalAlignment: .center
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width, height: 100.0)
|
||||
)
|
||||
let navigationTitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - navigationTitleSize.width) / 2.0), y: environment.statusBarHeight + floor((environment.navigationHeight - environment.statusBarHeight - navigationTitleSize.height) / 2.0)), size: navigationTitleSize)
|
||||
if let navigationTitleView = self.navigationTitle.view {
|
||||
if navigationTitleView.superview == nil {
|
||||
if let controller = self.environment?.controller(), let navigationBar = controller.navigationBar {
|
||||
navigationBar.view.addSubview(navigationTitleView)
|
||||
}
|
||||
}
|
||||
transition.setFrame(view: navigationTitleView, frame: navigationTitleFrame)
|
||||
}
|
||||
|
||||
let bottomContentInset: CGFloat = 24.0
|
||||
let sideInset: CGFloat = 16.0 + environment.safeInsets.left
|
||||
let sectionSpacing: CGFloat = 24.0
|
||||
|
||||
var contentHeight: CGFloat = 0.0
|
||||
|
||||
contentHeight += environment.navigationHeight
|
||||
|
||||
let iconSize = self.icon.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(LottieComponent(
|
||||
content: LottieComponent.AppBundleContent(name: "LampEmoji"),
|
||||
loop: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: 100.0, height: 100.0)
|
||||
)
|
||||
let iconFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - iconSize.width) * 0.5), y: contentHeight + 11.0), size: iconSize)
|
||||
if let iconView = self.icon.view as? LottieComponent.View {
|
||||
if iconView.superview == nil {
|
||||
self.scrollView.addSubview(iconView)
|
||||
iconView.playOnce()
|
||||
}
|
||||
transition.setPosition(view: iconView, position: iconFrame.center)
|
||||
iconView.bounds = CGRect(origin: CGPoint(), size: iconFrame.size)
|
||||
}
|
||||
|
||||
contentHeight += 129.0
|
||||
|
||||
//TODO:localize
|
||||
let subtitleString = NSMutableAttributedString(attributedString: parseMarkdownIntoAttributedString("Allow users to suggest posts for your channel.", attributes: MarkdownAttributes(
|
||||
body: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.freeTextColor),
|
||||
bold: MarkdownAttributeSet(font: Font.semibold(15.0), textColor: environment.theme.list.freeTextColor),
|
||||
link: MarkdownAttributeSet(font: Font.regular(15.0), textColor: environment.theme.list.itemAccentColor),
|
||||
linkAttribute: { attributes in
|
||||
return ("URL", "")
|
||||
}), textAlignment: .center
|
||||
))
|
||||
|
||||
let subtitleSize = self.subtitle.update(
|
||||
transition: .immediate,
|
||||
component: AnyComponent(BalancedTextComponent(
|
||||
text: .plain(subtitleString),
|
||||
horizontalAlignment: .center,
|
||||
maximumNumberOfLines: 0,
|
||||
lineSpacing: 0.25,
|
||||
highlightColor: environment.theme.list.itemAccentColor.withMultipliedAlpha(0.1),
|
||||
highlightAction: { attributes in
|
||||
if let _ = attributes[NSAttributedString.Key(rawValue: "URL")] {
|
||||
return NSAttributedString.Key(rawValue: "URL")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
},
|
||||
tapAction: { [weak self] _, _ in
|
||||
guard let self, let component = self.component else {
|
||||
return
|
||||
}
|
||||
let _ = component
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 1000.0)
|
||||
)
|
||||
let subtitleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - subtitleSize.width) * 0.5), y: contentHeight), size: subtitleSize)
|
||||
if let subtitleView = self.subtitle.view {
|
||||
if subtitleView.superview == nil {
|
||||
self.scrollView.addSubview(subtitleView)
|
||||
}
|
||||
transition.setPosition(view: subtitleView, position: subtitleFrame.center)
|
||||
subtitleView.bounds = CGRect(origin: CGPoint(), size: subtitleFrame.size)
|
||||
}
|
||||
contentHeight += subtitleSize.height
|
||||
contentHeight += 27.0
|
||||
|
||||
var switchSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
switchSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListActionItemComponent(
|
||||
theme: environment.theme,
|
||||
title: AnyComponent(VStack([
|
||||
AnyComponentWithIdentity(id: AnyHashable(0), component: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Allow Post Suggestions",
|
||||
font: Font.regular(presentationData.listsFontSize.baseDisplaySize),
|
||||
textColor: environment.theme.list.itemPrimaryTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 1
|
||||
))),
|
||||
], alignment: .left, spacing: 2.0)),
|
||||
accessory: .toggle(ListActionItemComponent.Toggle(style: .regular, isOn: self.areSuggestionsEnabled, isInteractive: false)),
|
||||
action: { [weak self] _ in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.areSuggestionsEnabled = !self.areSuggestionsEnabled
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
}
|
||||
))))
|
||||
|
||||
let switchSectionSize = self.switchSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: nil,
|
||||
footer: nil,
|
||||
items: switchSectionItems
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let switchSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: switchSectionSize)
|
||||
if let switchSectionView = self.switchSection.view {
|
||||
if switchSectionView.superview == nil {
|
||||
self.scrollView.addSubview(switchSectionView)
|
||||
self.switchSection.parentState = state
|
||||
}
|
||||
transition.setFrame(view: switchSectionView, frame: switchSectionFrame)
|
||||
}
|
||||
contentHeight += switchSectionSize.height
|
||||
contentHeight += sectionSpacing
|
||||
|
||||
var contentSectionItems: [AnyComponentWithIdentity<Empty>] = []
|
||||
|
||||
let sliderValueList = (0 ... 10000).map { i -> String in
|
||||
return "\(i)"
|
||||
}
|
||||
//TODO:localize
|
||||
let sliderTitle: String
|
||||
let sliderSecondaryTitle: String?
|
||||
let usdAmount = Double(self.starCount) * 0.013
|
||||
let usdAmountString = formatCurrencyAmount(Int64(usdAmount * 100.0), currency: "USD")
|
||||
if self.starCount == 0 {
|
||||
sliderTitle = "Free"
|
||||
sliderSecondaryTitle = nil
|
||||
} else if self.starCount == 1 {
|
||||
sliderTitle = "\(self.starCount) Star"
|
||||
sliderSecondaryTitle = "~\(usdAmountString)"
|
||||
} else {
|
||||
sliderTitle = "\(self.starCount) Stars"
|
||||
sliderSecondaryTitle = "~\(usdAmountString)"
|
||||
}
|
||||
|
||||
contentSectionItems.append(AnyComponentWithIdentity(id: 0, component: AnyComponent(ListItemSliderSelectorComponent(
|
||||
theme: environment.theme,
|
||||
content: .discrete(ListItemSliderSelectorComponent.Discrete(
|
||||
values: sliderValueList.map { item in
|
||||
return item
|
||||
},
|
||||
markPositions: false,
|
||||
selectedIndex: max(0, min(sliderValueList.count - 1, self.starCount - 1)),
|
||||
title: sliderTitle,
|
||||
secondaryTitle: sliderSecondaryTitle,
|
||||
selectedIndexUpdated: { [weak self] index in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let index = max(0, min(sliderValueList.count, index))
|
||||
self.starCount = index
|
||||
self.state?.updated(transition: .immediate)
|
||||
}
|
||||
))
|
||||
))))
|
||||
|
||||
let contentSectionSize = self.contentSection.update(
|
||||
transition: transition,
|
||||
component: AnyComponent(ListSectionComponent(
|
||||
theme: environment.theme,
|
||||
header: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "PRICE FOR EACH SUGGESTION",
|
||||
font: Font.regular(13.0),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
footer: AnyComponent(MultilineTextComponent(
|
||||
text: .plain(NSAttributedString(
|
||||
string: "Charge users for the ability to suggest one post for your channel. You're not required to publish any suggestions by charging this. You'll receive 85% of the selected fee for each incoming suggestion.",
|
||||
font: Font.regular(13.0),
|
||||
textColor: environment.theme.list.freeTextColor
|
||||
)),
|
||||
maximumNumberOfLines: 0
|
||||
)),
|
||||
items: contentSectionItems,
|
||||
displaySeparators: false
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: availableSize.width - sideInset * 2.0, height: 10000.0)
|
||||
)
|
||||
let contentSectionFrame = CGRect(origin: CGPoint(x: sideInset, y: contentHeight), size: contentSectionSize)
|
||||
if let contentSectionView = self.contentSection.view {
|
||||
if contentSectionView.superview == nil {
|
||||
self.scrollView.addSubview(contentSectionView)
|
||||
}
|
||||
transition.setFrame(view: contentSectionView, frame: contentSectionFrame)
|
||||
alphaTransition.setAlpha(view: contentSectionView, alpha: self.areSuggestionsEnabled ? 1.0 : 0.0)
|
||||
}
|
||||
|
||||
if self.areSuggestionsEnabled {
|
||||
contentHeight += contentSectionSize.height
|
||||
}
|
||||
|
||||
contentHeight += bottomContentInset
|
||||
contentHeight += environment.safeInsets.bottom
|
||||
|
||||
let previousBounds = self.scrollView.bounds
|
||||
|
||||
let contentSize = CGSize(width: availableSize.width, height: contentHeight)
|
||||
if self.scrollView.frame != CGRect(origin: CGPoint(), size: availableSize) {
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(), size: availableSize)
|
||||
}
|
||||
if self.scrollView.contentSize != contentSize {
|
||||
self.scrollView.contentSize = contentSize
|
||||
}
|
||||
let scrollInsets = UIEdgeInsets(top: environment.navigationHeight, left: 0.0, bottom: 0.0, right: 0.0)
|
||||
if self.scrollView.scrollIndicatorInsets != scrollInsets {
|
||||
self.scrollView.scrollIndicatorInsets = scrollInsets
|
||||
}
|
||||
|
||||
if !previousBounds.isEmpty, !transition.animation.isImmediate {
|
||||
let bounds = self.scrollView.bounds
|
||||
if bounds.maxY != previousBounds.maxY {
|
||||
let offsetY = previousBounds.maxY - bounds.maxY
|
||||
transition.animateBoundsOrigin(view: self.scrollView, from: CGPoint(x: 0.0, y: offsetY), to: CGPoint(), additive: true)
|
||||
}
|
||||
}
|
||||
|
||||
self.topOverscrollLayer.frame = CGRect(origin: CGPoint(x: 0.0, y: -3000.0), size: CGSize(width: availableSize.width, height: 3000.0))
|
||||
|
||||
self.updateScrolling(transition: transition)
|
||||
|
||||
return availableSize
|
||||
}
|
||||
}
|
||||
|
||||
func makeView() -> View {
|
||||
return View()
|
||||
}
|
||||
|
||||
func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment<EnvironmentType>, transition: ComponentTransition) -> CGSize {
|
||||
return view.update(component: self, availableSize: availableSize, state: state, environment: environment, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
public final class PostSuggestionsSettingsScreen: ViewControllerComponentContainer {
|
||||
private let context: AccountContext
|
||||
|
||||
public init(
|
||||
context: AccountContext,
|
||||
completion: @escaping () -> Void
|
||||
) {
|
||||
self.context = context
|
||||
|
||||
super.init(context: context, component: PostSuggestionsSettingsScreenComponent(
|
||||
context: context,
|
||||
completion: completion
|
||||
), navigationBarAppearance: .default, theme: .default, updatedPresentationData: nil)
|
||||
|
||||
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.title = ""
|
||||
self.navigationItem.backBarButtonItem = UIBarButtonItem(title: presentationData.strings.Common_Back, style: .plain, target: nil, action: nil)
|
||||
|
||||
self.scrollToTop = { [weak self] in
|
||||
guard let self, let componentView = self.node.hostView.componentView as? PostSuggestionsSettingsScreenComponent.View else {
|
||||
return
|
||||
}
|
||||
componentView.scrollToTop()
|
||||
}
|
||||
|
||||
self.attemptNavigation = { [weak self] complete in
|
||||
guard let self, let componentView = self.node.hostView.componentView as? PostSuggestionsSettingsScreenComponent.View else {
|
||||
return true
|
||||
}
|
||||
|
||||
return componentView.attemptNavigation(complete: complete)
|
||||
}
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
@objc private func cancelPressed() {
|
||||
self.dismiss()
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
@ -761,6 +761,7 @@ final class PeerSelectionControllerNode: ASDisplayNode {
|
||||
}, joinGroupCall: { _ in
|
||||
}, presentInviteMembers: {
|
||||
}, presentGigagroupHelp: {
|
||||
}, openSuggestPost: {
|
||||
}, editMessageMedia: { _, _ in
|
||||
}, updateShowCommands: { _ in
|
||||
}, updateShowSendAsPeers: { _ in
|
||||
|
@ -95,12 +95,6 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
|
||||
|
||||
self.updateHistoryView(updateType: nextUpdateIsHoleFill ? .FillHole : .Generic)
|
||||
})
|
||||
|
||||
/*if self.sourceHistoryView == nil {
|
||||
let sourceHistoryView = MessageHistoryView(tag: nil, namespaces: .just(Namespaces.Message.allQuickReply), entries: [], holeEarlier: false, holeLater: false, isLoading: false)
|
||||
self.sourceHistoryView = sourceHistoryView
|
||||
self.updateHistoryView(updateType: .Initial)
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@ -217,6 +211,8 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
|
||||
initialShortcut = ""
|
||||
case .hashTagSearch:
|
||||
initialShortcut = ""
|
||||
case .postSuggestions:
|
||||
initialShortcut = ""
|
||||
}
|
||||
|
||||
let queue = Queue()
|
||||
@ -255,6 +251,8 @@ final class AutomaticBusinessMessageSetupChatContents: ChatCustomContentsProtoco
|
||||
break
|
||||
case .hashTagSearch:
|
||||
break
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
BIN
submodules/TelegramUI/Resources/Animations/LampEmoji.tgs
Normal file
BIN
submodules/TelegramUI/Resources/Animations/LampEmoji.tgs
Normal file
Binary file not shown.
@ -123,6 +123,8 @@ import PeerNameColorScreen
|
||||
import ChatEmptyNode
|
||||
import ChatMediaInputStickerGridItem
|
||||
import AdsInfoScreen
|
||||
import PostSuggestionsSettingsScreen
|
||||
import ChatSendStarsScreen
|
||||
|
||||
extension ChatControllerImpl {
|
||||
func loadDisplayNodeImpl() {
|
||||
@ -1417,6 +1419,46 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .succeed(text: strongSelf.presentationData.strings.Business_Links_EditLinkToastSaved, timeout: nil, customUndoText: nil), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||
case let .postSuggestions(postSuggestions):
|
||||
if let customChatContents = customChatContents as? PostSuggestionsChatContents {
|
||||
//TODO:release
|
||||
strongSelf.chatDisplayNode.dismissInput()
|
||||
|
||||
let _ = (ChatSendStarsScreen.initialData(context: strongSelf.context, peerId: customChatContents.peerId, suggestMessageAmount: postSuggestions, completion: { [weak strongSelf] amount, timestamp in
|
||||
guard let strongSelf else {
|
||||
return
|
||||
}
|
||||
guard case let .customChatContents(customChatContents) = strongSelf.subject else {
|
||||
return
|
||||
}
|
||||
if amount == 0 {
|
||||
return
|
||||
}
|
||||
let messages = messages.map { message in
|
||||
return message.withUpdatedAttributes { attributes in
|
||||
var attributes = attributes
|
||||
attributes.removeAll(where: { $0 is OutgoingSuggestedPostMessageAttribute })
|
||||
attributes.append(OutgoingSuggestedPostMessageAttribute(
|
||||
price: StarsAmount(value: amount, nanos: 0),
|
||||
timestamp: timestamp
|
||||
))
|
||||
return attributes
|
||||
}
|
||||
}
|
||||
customChatContents.enqueueMessages(messages: messages)
|
||||
strongSelf.chatDisplayNode.historyNode.scrollToEndOfHistory()
|
||||
})
|
||||
|> deliverOnMainQueue).startStandalone(next: { [weak strongSelf] initialData in
|
||||
guard let strongSelf, let initialData else {
|
||||
return
|
||||
}
|
||||
let sendStarsScreen = ChatSendStarsScreen(
|
||||
context: strongSelf.context,
|
||||
initialData: initialData
|
||||
)
|
||||
strongSelf.push(sendStarsScreen)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
strongSelf.updateChatPresentationInterfaceState(interactive: true, { $0.updatedShowCommands(false) })
|
||||
@ -4082,6 +4124,29 @@ 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
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
guard let peerId = self.chatLocation.peerId else {
|
||||
return
|
||||
}
|
||||
|
||||
let contents = PostSuggestionsChatContents(
|
||||
context: self.context,
|
||||
peerId: peerId
|
||||
)
|
||||
let chatController = self.context.sharedContext.makeChatController(
|
||||
context: self.context,
|
||||
chatLocation: .customChatContents,
|
||||
subject: .customChatContents(contents: contents),
|
||||
botStart: nil,
|
||||
mode: .standard(.default),
|
||||
params: nil
|
||||
)
|
||||
chatController.navigationPresentation = .modal
|
||||
|
||||
self.push(chatController)
|
||||
}, editMessageMedia: { [weak self] messageId, draw in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerInteraction?.editMessageMedia(messageId, draw)
|
||||
|
@ -230,6 +230,8 @@ func updateChatPresentationInterfaceStateImpl(
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
canHaveUrlPreview = false
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -196,6 +196,8 @@ final class ChatBusinessLinkTitlePanelNode: ChatTitleAccessoryPanelNode {
|
||||
self.link = link
|
||||
case .hashTagSearch:
|
||||
break
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -135,6 +135,7 @@ import AdUI
|
||||
import ChatMessagePaymentAlertController
|
||||
import TelegramCallsUI
|
||||
import QuickShareScreen
|
||||
import PostSuggestionsSettingsScreen
|
||||
|
||||
public enum ChatControllerPeekActions {
|
||||
case standard
|
||||
@ -776,6 +777,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
case .hashTagSearch:
|
||||
break
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -882,6 +885,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
|
||||
return false
|
||||
}
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -5222,7 +5227,14 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
|
||||
self.avatarNode = avatarNode
|
||||
case .customChatContents:
|
||||
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
|
||||
if case let .customChatContents(customChatContents) = self.subject, case .postSuggestions = customChatContents.kind {
|
||||
let avatarNode = ChatAvatarNavigationNode()
|
||||
chatInfoButtonItem = UIBarButtonItem(customDisplayNode: avatarNode)!
|
||||
chatInfoButtonItem.isEnabled = false
|
||||
self.avatarNode = avatarNode
|
||||
} else {
|
||||
chatInfoButtonItem = UIBarButtonItem(title: "", style: .plain, target: nil, action: nil)
|
||||
}
|
||||
}
|
||||
chatInfoButtonItem.target = self
|
||||
chatInfoButtonItem.action = #selector(self.rightNavigationButtonAction)
|
||||
@ -6805,6 +6817,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
self.reportIrrelvantGeoNoticePromise.set(.single(nil))
|
||||
self.titleDisposable.set(nil)
|
||||
|
||||
var peerView: Signal<PeerView?, NoError> = .single(nil)
|
||||
|
||||
if case let .customChatContents(customChatContents) = self.subject {
|
||||
switch customChatContents.kind {
|
||||
case .hashTagSearch:
|
||||
@ -6827,15 +6841,56 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
}
|
||||
|
||||
self.chatTitleView?.titleContent = .custom(link.title ?? self.presentationData.strings.Business_Links_EditLinkTitle, linkUrl, false)
|
||||
case .postSuggestions:
|
||||
if let customChatContents = customChatContents as? PostSuggestionsChatContents {
|
||||
peerView = context.account.viewTracker.peerView(customChatContents.peerId) |> map(Optional.init)
|
||||
}
|
||||
|
||||
//TODO:localize
|
||||
self.chatTitleView?.titleContent = .custom("Message Suggestions", nil, false)
|
||||
}
|
||||
} else {
|
||||
self.chatTitleView?.titleContent = .custom(" ", nil, false)
|
||||
}
|
||||
|
||||
if !self.didSetChatLocationInfoReady {
|
||||
self.didSetChatLocationInfoReady = true
|
||||
self._chatLocationInfoReady.set(.single(true))
|
||||
}
|
||||
self.peerDisposable.set((peerView
|
||||
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
var renderedPeer: RenderedPeer?
|
||||
if let peerView, let peer = peerView.peers[peerView.peerId] {
|
||||
var peers = SimpleDictionary<PeerId, Peer>()
|
||||
peers[peer.id] = peer
|
||||
if let associatedPeerId = peer.associatedPeerId, let associatedPeer = peerView.peers[associatedPeerId] {
|
||||
peers[associatedPeer.id] = associatedPeer
|
||||
}
|
||||
renderedPeer = RenderedPeer(peerId: peer.id, peers: peers, associatedMedia: peerView.media)
|
||||
|
||||
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.setPeer(context: self.context, theme: self.presentationData.theme, peer: EnginePeer(peer), overrideImage: nil)
|
||||
}
|
||||
|
||||
self.peerView = peerView
|
||||
|
||||
if self.isNodeLoaded {
|
||||
self.chatDisplayNode.overlayTitle = self.overlayTitle
|
||||
}
|
||||
(self.chatInfoNavigationButton?.buttonItem.customDisplayNode as? ChatAvatarNavigationNode)?.contextActionIsEnabled = false
|
||||
|
||||
self.updateChatPresentationInterfaceState(animated: false, interactive: false, {
|
||||
return $0.updatedPeer { _ in
|
||||
return renderedPeer
|
||||
}.updatedInterfaceState { interfaceState in
|
||||
return interfaceState
|
||||
}
|
||||
})
|
||||
|
||||
if !self.didSetChatLocationInfoReady {
|
||||
self.didSetChatLocationInfoReady = true
|
||||
self._chatLocationInfoReady.set(.single(true))
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4286,6 +4286,7 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
}
|
||||
|
||||
var postEmptyMessages = false
|
||||
var isPostSuggestions = false
|
||||
if case let .customChatContents(customChatContents) = self.chatPresentationInterfaceState.subject {
|
||||
switch customChatContents.kind {
|
||||
case .hashTagSearch:
|
||||
@ -4294,8 +4295,11 @@ class ChatControllerNode: ASDisplayNode, ASScrollViewDelegate {
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
postEmptyMessages = true
|
||||
case .postSuggestions:
|
||||
isPostSuggestions = true
|
||||
}
|
||||
}
|
||||
let _ = isPostSuggestions
|
||||
|
||||
if !messages.isEmpty, let messageEffect {
|
||||
messages[0] = messages[0].withUpdatedAttributes { attributes in
|
||||
|
@ -385,109 +385,109 @@ extension ChatControllerImpl {
|
||||
}
|
||||
|
||||
let reactionsAttribute = mergedMessageReactions(attributes: message.attributes, isTags: false)
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? [])
|
||||
let _ = (ChatSendStarsScreen.initialData(context: self.context, peerId: message.id.peerId, messageId: message.id, topPeers: reactionsAttribute?.topPeers ?? [], completion: { [weak self] amount, privacy, isBecomingTop, transitionOut in
|
||||
guard let self, amount > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer {
|
||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var sourceItemNode: ChatMessageItemView?
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if itemNode.item?.message.id == message.id {
|
||||
sourceItemNode = itemNode
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == .stars {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
listAnimation: centerAnimation,
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionItem {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
self.view.window?.addSubview(standaloneReactionAnimation.view)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateOutToReaction(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
item: reactionItem,
|
||||
value: .stars,
|
||||
sourceView: transitionOut.sourceView,
|
||||
targetView: targetView,
|
||||
hideNode: false,
|
||||
forceSwitchToInlineImmediately: false,
|
||||
animateTargetContainer: nil,
|
||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
},
|
||||
onHit: { [weak self, weak itemNode] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isBecomingTop {
|
||||
self.chatDisplayNode.playConfettiAnimation()
|
||||
}
|
||||
|
||||
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars), self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
}
|
||||
},
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), privacy: privacy).startStandalone()
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount), privacy: privacy)
|
||||
})
|
||||
|> deliverOnMainQueue).start(next: { [weak self] initialData in
|
||||
guard let self, let initialData else {
|
||||
return
|
||||
}
|
||||
HapticFeedback().tap()
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData, completion: { [weak self] amount, privacy, isBecomingTop, transitionOut in
|
||||
guard let self, amount > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
if case let .known(reactionSettings) = reactionSettings, let starsAllowed = reactionSettings.starsAllowed, !starsAllowed {
|
||||
if let peer = self.presentationInterfaceState.renderedPeer?.chatMainPeer {
|
||||
self.present(standardTextAlertController(theme: AlertControllerTheme(presentationData: self.presentationData), title: nil, text: self.presentationData.strings.Chat_ToastStarsReactionsDisabled(peer.debugDisplayTitle).string, actions: [
|
||||
TextAlertAction(type: .genericAction, title: self.presentationData.strings.Common_OK, action: {})
|
||||
]), in: .window(.root))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var sourceItemNode: ChatMessageItemView?
|
||||
self.chatDisplayNode.historyNode.forEachItemNode { itemNode in
|
||||
if let itemNode = itemNode as? ChatMessageItemView {
|
||||
if itemNode.item?.message.id == message.id {
|
||||
sourceItemNode = itemNode
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let itemNode = sourceItemNode, let item = itemNode.item, let availableReactions = item.associatedData.availableReactions, let targetView = itemNode.targetReactionView(value: .stars) {
|
||||
var reactionItem: ReactionItem?
|
||||
|
||||
for reaction in availableReactions.reactions {
|
||||
guard let centerAnimation = reaction.centerAnimation else {
|
||||
continue
|
||||
}
|
||||
guard let aroundAnimation = reaction.aroundAnimation else {
|
||||
continue
|
||||
}
|
||||
if reaction.value == .stars {
|
||||
reactionItem = ReactionItem(
|
||||
reaction: ReactionItem.Reaction(rawValue: reaction.value),
|
||||
appearAnimation: reaction.appearAnimation,
|
||||
stillAnimation: reaction.selectAnimation,
|
||||
listAnimation: centerAnimation,
|
||||
largeListAnimation: reaction.activateAnimation,
|
||||
applicationAnimation: aroundAnimation,
|
||||
largeApplicationAnimation: reaction.effectAnimation,
|
||||
isCustom: false
|
||||
)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let reactionItem {
|
||||
let standaloneReactionAnimation = StandaloneReactionAnimation(genericReactionEffect: self.chatDisplayNode.historyNode.takeGenericReactionEffect())
|
||||
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
|
||||
self.view.window?.addSubview(standaloneReactionAnimation.view)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
standaloneReactionAnimation.animateOutToReaction(
|
||||
context: self.context,
|
||||
theme: self.presentationData.theme,
|
||||
item: reactionItem,
|
||||
value: .stars,
|
||||
sourceView: transitionOut.sourceView,
|
||||
targetView: targetView,
|
||||
hideNode: false,
|
||||
forceSwitchToInlineImmediately: false,
|
||||
animateTargetContainer: nil,
|
||||
addStandaloneReactionAnimation: { [weak self] standaloneReactionAnimation in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.chatDisplayNode.messageTransitionNode.addMessageStandaloneReactionAnimation(messageId: item.message.id, standaloneReactionAnimation: standaloneReactionAnimation)
|
||||
standaloneReactionAnimation.frame = self.chatDisplayNode.bounds
|
||||
self.chatDisplayNode.addSubnode(standaloneReactionAnimation)
|
||||
},
|
||||
onHit: { [weak self, weak itemNode] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if isBecomingTop {
|
||||
self.chatDisplayNode.playConfettiAnimation()
|
||||
}
|
||||
|
||||
if let itemNode, let targetView = itemNode.targetReactionView(value: .stars), self.context.sharedContext.energyUsageSettings.fullTranslucency {
|
||||
self.chatDisplayNode.wrappingNode.triggerRipple(at: targetView.convert(targetView.bounds.center, to: self.chatDisplayNode.view))
|
||||
}
|
||||
},
|
||||
completion: { [weak standaloneReactionAnimation] in
|
||||
standaloneReactionAnimation?.view.removeFromSuperview()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let _ = self.context.engine.messages.sendStarsReaction(id: message.id, count: Int(amount), privacy: privacy).startStandalone()
|
||||
self.displayOrUpdateSendStarsUndo(messageId: message.id, count: Int(amount), privacy: privacy)
|
||||
}))
|
||||
self.push(ChatSendStarsScreen(context: self.context, initialData: initialData))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ func inputContextQueriesForChatPresentationIntefaceState(_ chatPresentationInter
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
return []
|
||||
case .postSuggestions:
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
@ -241,6 +243,8 @@ func inputTextPanelStateForChatPresentationInterfaceState(_ chatPresentationInte
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
stickersEnabled = false
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2108,6 +2108,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState
|
||||
}
|
||||
case .businessLinkSetup:
|
||||
actions.removeAll()
|
||||
case .postSuggestions:
|
||||
//TODO:release
|
||||
actions.removeAll()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,13 @@ import ChatChannelSubscriberInputPanelNode
|
||||
import ChatMessageSelectionInputPanelNode
|
||||
|
||||
func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState: ChatPresentationInterfaceState, context: AccountContext, currentPanel: ChatInputPanelNode?, currentSecondaryPanel: ChatInputPanelNode?, textInputPanelNode: ChatTextInputPanelNode?, interfaceInteraction: ChatPanelInterfaceInteraction?) -> (primary: ChatInputPanelNode?, secondary: ChatInputPanelNode?) {
|
||||
if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||
var isPostSuggestions = false
|
||||
if case let .customChatContents(customChatContents) = chatPresentationInterfaceState.subject, case .postSuggestions = customChatContents.kind {
|
||||
isPostSuggestions = true
|
||||
}
|
||||
|
||||
if isPostSuggestions {
|
||||
} else if let renderedPeer = chatPresentationInterfaceState.renderedPeer, renderedPeer.peer?.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) != nil {
|
||||
return (nil, nil)
|
||||
}
|
||||
if chatPresentationInterfaceState.isNotAccessible {
|
||||
@ -132,7 +138,8 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
}
|
||||
}
|
||||
|
||||
if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
|
||||
if isPostSuggestions {
|
||||
} else if chatPresentationInterfaceState.peerIsBlocked, let peer = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramUser, peer.botInfo == nil {
|
||||
if let currentPanel = (currentPanel as? ChatUnblockInputPanelNode) ?? (currentSecondaryPanel as? ChatUnblockInputPanelNode) {
|
||||
currentPanel.interfaceInteraction = interfaceInteraction
|
||||
currentPanel.updateThemeAndStrings(theme: chatPresentationInterfaceState.theme, strings: chatPresentationInterfaceState.strings)
|
||||
@ -147,7 +154,9 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
|
||||
var displayInputTextPanel = false
|
||||
|
||||
if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
|
||||
if isPostSuggestions {
|
||||
displayInputTextPanel = true
|
||||
} else if let peer = chatPresentationInterfaceState.renderedPeer?.peer {
|
||||
if peer.id.isRepliesOrVerificationCodes {
|
||||
if let currentPanel = (currentPanel as? ChatChannelSubscriberInputPanelNode) ?? (currentSecondaryPanel as? ChatChannelSubscriberInputPanelNode) {
|
||||
return (currentPanel, nil)
|
||||
@ -423,6 +432,8 @@ func inputPanelForChatPresentationIntefaceState(_ chatPresentationInterfaceState
|
||||
displayInputTextPanel = false
|
||||
case .quickReplyMessageInput, .businessLinkSetup:
|
||||
displayInputTextPanel = true
|
||||
case .postSuggestions:
|
||||
displayInputTextPanel = true
|
||||
}
|
||||
|
||||
if let chatHistoryState = chatPresentationInterfaceState.chatHistoryState, case .loaded(_, true) = chatHistoryState {
|
||||
|
@ -59,7 +59,7 @@ func leftNavigationButtonForChatInterfaceState(_ presentationInterfaceState: Cha
|
||||
switch customChatContents.kind {
|
||||
case .hashTagSearch:
|
||||
break
|
||||
case .quickReplyMessageInput, .businessLinkSetup:
|
||||
case .quickReplyMessageInput, .businessLinkSetup, .postSuggestions:
|
||||
if let currentButton = currentButton, currentButton.action == .dismiss {
|
||||
return currentButton
|
||||
} else {
|
||||
@ -149,6 +149,8 @@ func rightNavigationButtonForChatInterfaceState(context: AccountContext, present
|
||||
buttonItem.accessibilityLabel = strings.Common_Done
|
||||
return ChatNavigationButton(action: .edit, buttonItem: buttonItem)
|
||||
}
|
||||
case .postSuggestions:
|
||||
return chatInfoNavigationButton
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,6 +66,8 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat
|
||||
panel.interfaceInteraction = interfaceInteraction
|
||||
return panel
|
||||
}
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
|
@ -128,6 +128,8 @@ final class ChatRestrictedInputPanelNode: ChatInputPanelNode {
|
||||
displayCount = customChatContents.messageLimit ?? 20
|
||||
case .businessLinkSetup:
|
||||
displayCount = 0
|
||||
case .postSuggestions:
|
||||
displayCount = 0
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: interfaceState.strings.Chat_QuickReplyMessageLimitReachedText(Int32(displayCount)), font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor)
|
||||
}
|
||||
|
@ -272,10 +272,9 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||
self.validLayout = size
|
||||
|
||||
var innerSize = size
|
||||
|
||||
var starsAmount: Int64?
|
||||
if let sendPaidMessageStars = interfaceState.sendPaidMessageStars, interfaceState.interfaceState.editMessage == nil {
|
||||
self.sendButton.imageNode.alpha = 0.0
|
||||
self.textNode.isHidden = false
|
||||
|
||||
var amount: Int64
|
||||
if let forwardedCount = interfaceState.interfaceState.forwardMessageIds?.count, forwardedCount > 0 {
|
||||
amount = sendPaidMessageStars.value * Int64(forwardedCount)
|
||||
@ -290,7 +289,19 @@ final class ChatTextInputActionButtonsNode: ASDisplayNode, ChatSendMessageAction
|
||||
amount = sendPaidMessageStars.value
|
||||
}
|
||||
}
|
||||
|
||||
starsAmount = amount
|
||||
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
|
||||
switch customChatContents.kind {
|
||||
case let .postSuggestions(postSuggestions):
|
||||
starsAmount = postSuggestions.value
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let amount = starsAmount {
|
||||
self.sendButton.imageNode.alpha = 0.0
|
||||
self.textNode.isHidden = false
|
||||
let text = "\(amount)"
|
||||
let font = Font.with(size: 17.0, design: .round, weight: .semibold, traits: .monospacedNumbers)
|
||||
let badgeString = NSMutableAttributedString(string: "⭐️ ", font: font, textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor)
|
||||
|
@ -1558,6 +1558,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
displayMediaButton = false
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@ -1944,6 +1946,10 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
}
|
||||
case .businessLinkSetup:
|
||||
placeholder = interfaceState.strings.Chat_Placeholder_BusinessLinkPreset
|
||||
case let .postSuggestions(postSuggestions):
|
||||
//TODO:localize
|
||||
placeholder = "Suggest for # \(postSuggestions)"
|
||||
placeholderHasStar = true
|
||||
}
|
||||
}
|
||||
|
||||
@ -1971,6 +1977,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
sendButtonHasApplyIcon = true
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2493,8 +2501,17 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
var actionButtonsSize = CGSize(width: 44.0, height: minimalHeight)
|
||||
if let presentationInterfaceState = self.presentationInterfaceState {
|
||||
var showTitle = false
|
||||
if let _ = presentationInterfaceState.sendPaidMessageStars, !self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
showTitle = true
|
||||
if !self.actionButtons.sendContainerNode.alpha.isZero {
|
||||
if let _ = presentationInterfaceState.sendPaidMessageStars {
|
||||
showTitle = true
|
||||
} else if case let .customChatContents(customChatContents) = interfaceState.subject {
|
||||
switch customChatContents.kind {
|
||||
case .postSuggestions:
|
||||
showTitle = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
actionButtonsSize = self.actionButtons.updateLayout(size: CGSize(width: 44.0, height: minimalHeight), isMediaInputExpanded: isMediaInputExpanded, showTitle: showTitle, currentMessageEffectId: presentationInterfaceState.interfaceState.sendMessageEffect, transition: transition, interfaceState: presentationInterfaceState)
|
||||
}
|
||||
@ -3768,6 +3785,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
keepSendButtonEnabled = true
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3888,6 +3907,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
hideMicButton = true
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3993,6 +4014,8 @@ class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDelegate, Ch
|
||||
break
|
||||
case .businessLinkSetup:
|
||||
sendButtonHasApplyIcon = true
|
||||
case .postSuggestions:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -83,7 +83,7 @@ import OldChannelsController
|
||||
import InviteLinksUI
|
||||
import GiftStoreScreen
|
||||
import SendInviteLinkScreen
|
||||
|
||||
import PostSuggestionsSettingsScreen
|
||||
|
||||
private final class AccountUserInterfaceInUseContext {
|
||||
let subscribers = Bag<(Bool) -> Void>()
|
||||
@ -3839,6 +3839,10 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
public func makeSendInviteLinkScreen(context: AccountContext, subject: SendInviteLinkScreenSubject, peers: [TelegramForbiddenInvitePeer], theme: PresentationTheme?) -> ViewController {
|
||||
return SendInviteLinkScreen(context: context, subject: subject, peers: peers, theme: theme)
|
||||
}
|
||||
|
||||
public func makePostSuggestionsSettingsScreen(context: AccountContext) -> ViewController {
|
||||
return PostSuggestionsSettingsScreen(context: context, completion: {})
|
||||
}
|
||||
}
|
||||
|
||||
private func peerInfoControllerImpl(context: AccountContext, updatedPresentationData: (PresentationData, Signal<PresentationData, NoError>)?, peer: Peer, mode: PeerInfoControllerMode, avatarInitiallyExpanded: Bool, isOpenedFromChat: Bool, requestsContext: PeerInvitationImportersContext? = nil) -> ViewController? {
|
||||
|
Loading…
x
Reference in New Issue
Block a user