Swiftgram/submodules/TelegramUI/Sources/ChatControllerContentData.swift
2025-05-27 18:41:24 +08:00

2309 lines
136 KiB
Swift

import Foundation
import TelegramPresentationData
import AccountContext
import Postbox
import ChatInterfaceState
import TelegramCore
import SwiftSignalKit
import ChatTitleView
import AvatarNode
import ChatPresentationInterfaceState
import PeerInfoScreen
import TelegramNotices
import ChatListUI
import EmojiStatusComponent
import TelegramUIPreferences
import TranslateUI
extension ChatControllerImpl {
final class ContentData {
final class Configuration: Equatable {
let subject: ChatControllerSubject?
let selectionState: ChatInterfaceSelectionState?
let reportReason: ChatPresentationInterfaceState.ReportReasonData?
init(
subject: ChatControllerSubject?,
selectionState: ChatInterfaceSelectionState?,
reportReason: ChatPresentationInterfaceState.ReportReasonData?
) {
self.subject = subject
self.selectionState = selectionState
self.reportReason = reportReason
}
static func ==(lhs: Configuration, rhs: Configuration) -> Bool {
if lhs.subject != rhs.subject {
return false
}
if lhs.selectionState != rhs.selectionState {
return false
}
if lhs.reportReason != rhs.reportReason {
return false
}
return true
}
}
enum InfoAvatar {
case peer(peer: EnginePeer, imageOverride: AvatarNodeImageOverride?, contextActionIsEnabled: Bool, accessibilityLabel: String?)
case emojiStatus(content: EmojiStatusComponent.Content, contextActionIsEnabled: Bool)
}
enum PerformDismissAction {
case upgraded(EnginePeer.Id)
case movedToForumTopics
case dismiss
}
struct NextChannelToRead: Equatable {
struct ThreadData: Equatable {
let id: Int64
let data: MessageHistoryThreadData
init(id: Int64, data: MessageHistoryThreadData) {
self.id = id
self.data = data
}
}
let peer: EnginePeer
let threadData: ThreadData?
let unreadCount: Int
let location: TelegramEngine.NextUnreadChannelLocation
init(peer: EnginePeer, threadData: ThreadData?, unreadCount: Int, location: TelegramEngine.NextUnreadChannelLocation) {
self.peer = peer
self.threadData = threadData
self.unreadCount = unreadCount
self.location = location
}
}
struct State {
var peerView: PeerView?
var threadInfo: EngineMessageHistoryThread.Info?
var infoAvatar: InfoAvatar?
var navigationUserInfo: PeerInfoNavigationSourceTag?
var chatTitleContent: ChatTitleContent?
var storyStats: PeerStoryStats?
var renderedPeer: RenderedPeer?
var hasScheduledMessages: Bool = false
var hasSearchTags: Bool = false
var hasSavedChats: Bool = false
var isPremiumRequiredForMessaging: Bool = false
var contactStatus: ChatContactStatus?
var adMessage: Message?
var offerNextChannelToRead: Bool = false
var nextChannelToRead: NextChannelToRead?
var nextChannelToReadDisplayName: Bool = false
var isNotAccessible: Bool = false
var hasBots: Bool = false
var hasBotCommands: Bool = false
var botMenuButton: BotMenuButton = .commands
var isArchived: Bool = false
var peerIsMuted: Bool = false
var peerDiscussionId: EnginePeer.Id?
var peerGeoLocation: PeerGeoLocation?
var explicitelyCanPinMessages: Bool = false
var autoremoveTimeout: Int32?
var currentSendAsPeerId: EnginePeer.Id?
var copyProtectionEnabled: Bool = false
var sendPaidMessageStars: StarsAmount?
var alwaysShowGiftButton: Bool = false
var disallowedGifts: TelegramDisallowedGifts?
var appliedBoosts: Int32?
var boostsToUnrestrict: Int32?
var hasBirthdayToday: Bool = false
var businessIntro: TelegramBusinessIntro?
var peerVerification: PeerVerification?
var starGiftsAvailable: Bool = false
var performDismissAction: PerformDismissAction?
var savedMessagesTopicPeer: EnginePeer?
var keyboardButtonsMessage: Message?
var pinnedMessageId: EngineMessage.Id?
var pinnedMessage: ChatPinnedMessage?
var peerIsBlocked: Bool = false
var callsAvailable: Bool = true
var callsPrivate: Bool = false
var activeGroupCallInfo: ChatActiveGroupCallInfo?
var slowmodeState: ChatSlowmodeState?
var suggestPremiumGift: Bool = false
var translationState: ChatPresentationTranslationState?
var voiceMessagesAvailable: Bool = true
var requestsState: PeerInvitationImportersState?
var dismissedInvitationRequests: [Int64]?
var customEmojiAvailable: Bool = false
var threadData: ChatPresentationInterfaceState.ThreadData?
var forumTopicData: ChatPresentationInterfaceState.ThreadData?
var isGeneralThreadClosed: Bool?
var premiumGiftOptions: [CachedPremiumGiftOption] = []
}
private let presentationData: PresentationData
private var peerDisposable: Disposable?
private var titleDisposable: Disposable?
private var preloadSavedMessagesChatsDisposable: Disposable?
private var preloadHistoryPeerId: PeerId?
private let preloadHistoryPeerIdDisposable = MetaDisposable()
private var preloadNextChatPeerId: PeerId?
private let preloadNextChatPeerIdDisposable = MetaDisposable()
private var nextChannelToReadDisposable: Disposable?
private let chatAdditionalDataDisposable = MetaDisposable()
private var premiumOrStarsRequiredDisposable: Disposable?
private var buttonKeyboardMessageDisposable: Disposable?
private var cachedDataDisposable: Disposable?
private var premiumGiftSuggestionDisposable: Disposable?
private var translationStateDisposable: Disposable?
private let isPeerInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isChatLocationInfoReady = ValuePromise<Bool>(false, ignoreRepeated: true)
private let isCachedDataReady = ValuePromise<Bool>(false, ignoreRepeated: true)
let chatLocation: ChatLocation
let chatLocationInfoData: ChatLocationInfoData
private(set) var state: State = State()
var initialInterfaceState: (interfaceState: ChatInterfaceState, editMessage: Message?)?
var initialNavigationBadge: String?
var overlayTitle: String? {
var title: String?
if let threadInfo = self.state.threadInfo {
title = threadInfo.title
} else if let peerView = self.state.peerView {
if let peer = peerViewMainPeer(peerView) {
title = EnginePeer(peer).displayTitle(strings: self.presentationData.strings, displayOrder: self.presentationData.nameDisplayOrder)
}
}
return title
}
let isReady = Promise<Bool>()
var onUpdated: ((State) -> Void)?
let scrolledToMessageId = ValuePromise<ScrolledToMessageId?>(nil, ignoreRepeated: true)
var scrolledToMessageIdValue: ScrolledToMessageId? = nil {
didSet {
self.scrolledToMessageId.set(self.scrolledToMessageIdValue)
}
}
var historyNavigationStack = ChatHistoryNavigationStack()
let chatThemeEmoticonPromise = Promise<String?>()
let chatWallpaperPromise = Promise<TelegramWallpaper?>()
private(set) var inviteRequestsContext: PeerInvitationImportersContext?
private var inviteRequestsDisposable: Disposable?
init(
context: AccountContext,
chatLocation: ChatLocation,
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>,
initialSubject: ChatControllerSubject?,
mode: ChatControllerPresentationMode,
configuration: Signal<Configuration, NoError>,
adMessagesContext: AdMessagesHistoryContext?,
currentChatListFilter: Int32?,
customChatNavigationStack: [EnginePeer.Id]?,
presentationData: PresentationData,
historyNode: ChatHistoryListNodeImpl,
inviteRequestsContext: PeerInvitationImportersContext?
) {
self.chatLocation = chatLocation
self.presentationData = presentationData
self.inviteRequestsContext = inviteRequestsContext
let strings = self.presentationData.strings
let chatLocationPeerId: PeerId? = chatLocation.peerId
let peerId = chatLocationPeerId
switch chatLocation {
case .peer:
self.chatLocationInfoData = .peer(Promise())
case let .replyThread(replyThreadMessage):
let promise = Promise<Message?>()
if let effectiveMessageId = replyThreadMessage.effectiveMessageId {
promise.set(context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.Message(id: effectiveMessageId))
|> map { message -> Message? in
guard let message = message else {
return nil
}
return message._asMessage()
})
} else {
promise.set(.single(nil))
}
self.chatLocationInfoData = .replyThread(promise)
case .customChatContents:
self.chatLocationInfoData = .customChatContents
}
if let peerId = chatLocation.peerId, peerId != context.account.peerId {
switch initialSubject {
case .pinnedMessages, .scheduledMessages, .messageOptions:
break
default:
self.state.navigationUserInfo = PeerInfoNavigationSourceTag(peerId: peerId, threadId: chatLocation.threadId)
}
}
let managingBot: Signal<ChatManagingBot?, NoError>
if let peerId = chatLocation.peerId, peerId.namespace == Namespaces.Peer.CloudUser {
managingBot = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.ChatManagingBot(id: peerId)
)
|> mapToSignal { result -> Signal<ChatManagingBot?, NoError> in
guard let result else {
return .single(nil)
}
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: result.id)
)
|> map { botPeer -> ChatManagingBot? in
guard let botPeer else {
return nil
}
return ChatManagingBot(bot: botPeer, isPaused: result.isPaused, canReply: result.canReply, settingsUrl: result.manageUrl)
}
}
|> distinctUntilChanged
} else {
managingBot = .single(nil)
}
if case let .peer(peerView) = self.chatLocationInfoData, let peerId = peerId {
peerView.set(context.account.viewTracker.peerView(peerId))
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
if peerId.namespace == Namespaces.Peer.CloudChannel {
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView.get()
|> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info {
return nil
} else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
return true
} else {
return false
}
} else {
return false
}
}
|> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge {
if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
}
} else {
return .single((nil, nil))
}
}
onlineMemberCount = recentOnlineSignal
}
var isScheduledOrPinnedMessages = false
switch initialSubject {
case .scheduledMessages, .pinnedMessages, .messageOptions:
isScheduledOrPinnedMessages = true
default:
break
}
if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat {
hasScheduledMessages = peerView.get()
|> take(1)
|> mapToSignal { view -> Signal<Bool, NoError> in
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
return .single(false)
} else {
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|> map { view, _, _ in
return !view.entries.isEmpty
}
}
}
}
var displayedCountSignal: Signal<Int?, NoError> = .single(nil)
var subtitleTextSignal: Signal<String?, NoError> = .single(nil)
if case .pinnedMessages = initialSubject {
displayedCountSignal = ChatControllerImpl.topPinnedMessageSignal(context: context, chatLocation: chatLocation, referenceMessage: nil)
|> map { message -> Int? in
return message?.totalCount
}
|> distinctUntilChanged
} else if case let .messageOptions(peerIds, messageIds, info) = initialSubject {
displayedCountSignal = configuration
|> map { configuration -> Int? in
if let selectionState = configuration.selectionState {
return selectionState.selectedIds.count
} else {
return messageIds.count
}
}
|> distinctUntilChanged
let peers = context.account.postbox.multiplePeersView(peerIds)
|> take(1)
switch info {
case let .forward(forward):
subtitleTextSignal = combineLatest(peers, forward.options, displayedCountSignal)
|> map { peersView, options, count in
let peers = peersView.peers.values
if !peers.isEmpty {
if peers.count == 1, let peer = peers.first {
if let peer = peer as? TelegramUser {
let displayName = EnginePeer(peer).compactDisplayTitle
if count == 1 {
if options.hideNames {
return strings.Conversation_ForwardOptions_UserMessageForwardHidden(displayName).string
} else {
return strings.Conversation_ForwardOptions_UserMessageForwardVisible(displayName).string
}
} else {
if options.hideNames {
return strings.Conversation_ForwardOptions_UserMessagesForwardHidden(displayName).string
} else {
return strings.Conversation_ForwardOptions_UserMessagesForwardVisible(displayName).string
}
}
} else if let peer = peer as? TelegramChannel, case .broadcast = peer.info {
if count == 1 {
if options.hideNames {
return strings.Conversation_ForwardOptions_ChannelMessageForwardHidden
} else {
return strings.Conversation_ForwardOptions_ChannelMessageForwardVisible
}
} else {
if options.hideNames {
return strings.Conversation_ForwardOptions_ChannelMessagesForwardHidden
} else {
return strings.Conversation_ForwardOptions_ChannelMessagesForwardVisible
}
}
} else {
if count == 1 {
if options.hideNames {
return strings.Conversation_ForwardOptions_GroupMessageForwardHidden
} else {
return strings.Conversation_ForwardOptions_GroupMessageForwardVisible
}
} else {
if options.hideNames {
return strings.Conversation_ForwardOptions_GroupMessagesForwardHidden
} else {
return strings.Conversation_ForwardOptions_GroupMessagesForwardVisible
}
}
}
} else {
if count == 1 {
if options.hideNames {
return strings.Conversation_ForwardOptions_RecipientsMessageForwardHidden
} else {
return strings.Conversation_ForwardOptions_RecipientsMessageForwardVisible
}
} else {
if options.hideNames {
return strings.Conversation_ForwardOptions_RecipientsMessagesForwardHidden
} else {
return strings.Conversation_ForwardOptions_RecipientsMessagesForwardVisible
}
}
}
} else {
return nil
}
}
case let .reply(reply):
subtitleTextSignal = reply.selectionState.get()
|> map { selectionState -> String? in
if !selectionState.canQuote {
return nil
}
return strings.Chat_SubtitleQuoteSelectionTip
}
case let .link(link):
subtitleTextSignal = link.options
|> map { options -> String? in
if options.hasAlternativeLinks {
return strings.Chat_SubtitleLinkListTip
} else {
return nil
}
}
|> distinctUntilChanged
}
}
let hasPeerInfo: Signal<Bool, NoError>
if peerId == context.account.peerId {
hasPeerInfo = .single(true)
|> then(
hasAvailablePeerInfoMediaPanes(context: context, peerId: peerId)
)
} else {
hasPeerInfo = .single(true)
}
enum MessageOptionsTitleInfo {
case reply(hasQuote: Bool)
}
let messageOptionsTitleInfo: Signal<MessageOptionsTitleInfo?, NoError>
if case let .messageOptions(_, _, info) = initialSubject {
switch info {
case .forward, .link:
messageOptionsTitleInfo = .single(nil)
case let .reply(reply):
messageOptionsTitleInfo = reply.selectionState.get()
|> map { selectionState -> Bool in
return selectionState.quote != nil
}
|> distinctUntilChanged
|> map { hasQuote -> MessageOptionsTitleInfo in
return .reply(hasQuote: hasQuote)
}
}
} else {
messageOptionsTitleInfo = .single(nil)
}
self.titleDisposable = (combineLatest(
queue: Queue.mainQueue(),
peerView.get(),
onlineMemberCount,
displayedCountSignal,
subtitleTextSignal,
configuration,
hasPeerInfo,
messageOptionsTitleInfo
)
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, onlineMemberCount, displayedCount, subtitleText, configuration, hasPeerInfo, messageOptionsTitleInfo in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
var isScheduledMessages = false
if case .scheduledMessages = configuration.subject {
isScheduledMessages = true
}
if case let .messageOptions(_, _, info) = configuration.subject {
if case .reply = info {
let titleContent: ChatTitleContent
if case let .reply(hasQuote) = messageOptionsTitleInfo, hasQuote {
titleContent = .custom(strings.Chat_TitleQuoteSelection, subtitleText, false)
} else {
titleContent = .custom(strings.Chat_TitleReply, subtitleText, false)
}
strongSelf.state.chatTitleContent = titleContent
} else if case .link = info {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitleLinkOptions, subtitleText, false)
} else if displayedCount == 1 {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitleSingle, subtitleText, false)
} else {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_ForwardOptions_ForwardTitle(Int32(displayedCount ?? 1)), subtitleText, false)
}
} else if let selectionState = configuration.selectionState {
if selectionState.selectedIds.count > 0 {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectedMessages(Int32(selectionState.selectedIds.count)), nil, false)
} else {
if let reportReason = configuration.reportReason {
strongSelf.state.chatTitleContent = .custom(reportReason.title, strings.Conversation_SelectMessages, false)
} else {
strongSelf.state.chatTitleContent = .custom(strings.Conversation_SelectMessages, nil, false)
}
}
} else if let peer = peerViewMainPeer(peerView) {
if case .pinnedMessages = configuration.subject {
strongSelf.state.chatTitleContent = .custom(strings.Chat_TitlePinnedMessages(Int32(displayedCount ?? 1)), nil, false)
} else if let channel = peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainPeer = peerView.peers[linkedMonoforumId] {
strongSelf.state.chatTitleContent = .custom(mainPeer.debugDisplayTitle, nil, false)
} else {
strongSelf.state.chatTitleContent = .custom(channel.debugDisplayTitle, nil, false)
}
} else {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: nil, onlineMemberCount: onlineMemberCount, isScheduledMessages: isScheduledMessages, isMuted: nil, customMessageCount: nil, isEnabled: hasPeerInfo)
let imageOverride: AvatarNodeImageOverride?
if context.account.peerId == peer.id {
imageOverride = .savedMessagesIcon
} else if peer.id.isReplies {
imageOverride = .repliesIcon
} else if peer.id.isAnonymousSavedMessages {
imageOverride = .anonymousSavedMessagesIcon(isColored: true)
} else if peer.isDeleted {
imageOverride = .deletedIcon
} else {
imageOverride = nil
}
let infoContextActionIsEnabled: Bool
if case .standard(.previewing) = mode {
infoContextActionIsEnabled = false
} else {
infoContextActionIsEnabled = peer.restrictionText(platform: "ios", contentSettings: context.currentContentSettings.with { $0 }) == nil
}
strongSelf.state.infoAvatar = .peer(
peer: EnginePeer(peer),
imageOverride: imageOverride,
contextActionIsEnabled: infoContextActionIsEnabled,
accessibilityLabel: strings.Conversation_ContextMenuOpenProfile
)
strongSelf.state.storyStats = peerView.storyStats
}
}
strongSelf.isPeerInfoReady.set(true)
strongSelf.onUpdated?(previousState)
})
let threadInfo: Signal<EngineMessageHistoryThread.Info?, NoError>
if let threadId = chatLocation.threadId {
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId)
threadInfo = context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> EngineMessageHistoryThread.Info? in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return nil
}
guard let data = view.info?.data.get(MessageHistoryThreadData.self) else {
return nil
}
return data.info
}
|> distinctUntilChanged
} else {
threadInfo = .single(nil)
}
let hasSearchTags: Signal<Bool, NoError>
if let peerId = chatLocation.peerId, peerId == context.account.peerId {
hasSearchTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId)
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
} else {
hasSearchTags = .single(false)
}
let hasSavedChats: Signal<Bool, NoError>
if case .peer(context.account.peerId) = chatLocation {
hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
} else {
hasSavedChats = .single(false)
}
let isPremiumRequiredForMessaging: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
|> distinctUntilChanged
} else {
isPremiumRequiredForMessaging = .single(false)
}
let adMessage: Signal<Message?, NoError>
if let adMessagesContext {
adMessage = adMessagesContext.state |> map { $0.messages.first }
} else {
adMessage = .single(nil)
}
let displayedPeerVerification: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
displayedPeerVerification = ApplicationSpecificNotice.displayedPeerVerification(accountManager: context.sharedContext.accountManager, peerId: peerId)
|> take(1)
} else {
displayedPeerVerification = .single(false)
}
let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy())
self.peerDisposable = combineLatest(
queue: Queue.mainQueue(),
peerView.get(),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
onlineMemberCount,
hasScheduledMessages,
displayedCountSignal,
threadInfo,
hasSearchTags,
hasSavedChats,
isPremiumRequiredForMessaging,
managingBot,
adMessage,
displayedPeerVerification,
globalPrivacySettings
).startStrict(next: { [weak self] peerView, globalNotificationSettings, onlineMemberCount, hasScheduledMessages, pinnedCount, threadInfo, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, adMessage, displayedPeerVerification, globalPrivacySettings in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
if strongSelf.state.peerView === peerView
&& strongSelf.state.hasScheduledMessages == hasScheduledMessages
&& strongSelf.state.threadInfo == threadInfo
&& strongSelf.state.hasSearchTags == hasSearchTags
&& strongSelf.state.hasSavedChats == hasSavedChats
&& strongSelf.state.isPremiumRequiredForMessaging == isPremiumRequiredForMessaging
&& managingBot == strongSelf.state.contactStatus?.managingBot
&& adMessage?.id == strongSelf.state.adMessage?.id {
return
}
strongSelf.state.hasScheduledMessages = hasScheduledMessages
var upgradedToPeerId: PeerId?
var movedToForumTopics = false
if let previous = strongSelf.state.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.migrationReference == nil, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, let migrationReference = updatedGroup.migrationReference {
upgradedToPeerId = migrationReference.peerId
}
if let previous = strongSelf.state.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, !channel.isForumOrMonoForum, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.isForumOrMonoForum {
movedToForumTopics = true
}
var shouldDismiss = false
if let previous = strongSelf.state.peerView, let group = previous.peers[previous.peerId] as? TelegramGroup, group.membership != .Removed, let updatedGroup = peerView.peers[peerView.peerId] as? TelegramGroup, updatedGroup.membership == .Removed {
shouldDismiss = true
} else if let previous = strongSelf.state.peerView, let channel = previous.peers[previous.peerId] as? TelegramChannel, channel.participationStatus != .kicked, let updatedChannel = peerView.peers[peerView.peerId] as? TelegramChannel, updatedChannel.participationStatus == .kicked {
shouldDismiss = true
} else if let previous = strongSelf.state.peerView, let secretChat = previous.peers[previous.peerId] as? TelegramSecretChat, case .active = secretChat.embeddedState, let updatedSecretChat = peerView.peers[peerView.peerId] as? TelegramSecretChat, case .terminated = updatedSecretChat.embeddedState {
shouldDismiss = true
}
var wasGroupChannel: Bool?
if let previousPeerView = strongSelf.state.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info {
if case .group = info {
wasGroupChannel = true
} else {
wasGroupChannel = false
}
}
var isGroupChannel: Bool?
if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info {
if case .group = info {
isGroupChannel = true
} else {
isGroupChannel = false
}
}
let firstTime = strongSelf.state.peerView == nil
strongSelf.state.peerView = peerView
strongSelf.state.threadInfo = threadInfo
if wasGroupChannel != isGroupChannel {
if let isGroupChannel = isGroupChannel, isGroupChannel {
let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
let disposable = DisposableSet()
disposable.add(recentDisposable)
disposable.add(adminsDisposable)
strongSelf.chatAdditionalDataDisposable.set(disposable)
} else {
strongSelf.chatAdditionalDataDisposable.set(nil)
}
}
var peerIsMuted = false
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
peerIsMuted = true
} else if case .default = notificationSettings.muteState {
if let peer = peerView.peers[peerView.peerId] {
if peer is TelegramUser {
peerIsMuted = !globalNotificationSettings.privateChats.enabled
} else if peer is TelegramGroup {
peerIsMuted = !globalNotificationSettings.groupChats.enabled
} else if let channel = peer as? TelegramChannel {
switch channel.info {
case .group:
peerIsMuted = !globalNotificationSettings.groupChats.enabled
case .broadcast:
peerIsMuted = !globalNotificationSettings.channels.enabled
}
}
}
}
}
var starGiftsAvailable = false
var peerDiscussionId: PeerId?
var peerGeoLocation: PeerGeoLocation?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
if case .broadcast = peer.info {
starGiftsAvailable = cachedData.flags.contains(.starGiftsAvailable)
} else {
peerGeoLocation = cachedData.peerGeoLocation
}
if case let .known(value) = cachedData.linkedDiscussionPeerId {
peerDiscussionId = value
}
}
var renderedPeer: RenderedPeer?
var contactStatus: ChatContactStatus?
var businessIntro: TelegramBusinessIntro?
var sendPaidMessageStars: StarsAmount?
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
businessIntro = value
}
if case let .peer(peerId) = chatLocation, peerId.namespace == Namespaces.Peer.SecretChat {
} else {
sendPaidMessageStars = cachedData.sendPaidMessageStars
if cachedData.disallowedGifts != .All {
alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton)
}
disallowedGifts = cachedData.disallowedGifts
}
} else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy {
if let peer = peerView.peers[invitedByPeerId] {
invitedBy = peer
}
}
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedChannelData {
var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy {
if let peer = peerView.peers[invitedByPeerId] {
invitedBy = peer
}
}
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel {
if channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
} else {
sendPaidMessageStars = channel.sendPaidMessageStars
}
} else {
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
} else {
sendPaidMessageStars = channel.sendPaidMessageStars
}
}
}
}
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)
}
var isNotAccessible: Bool = false
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
isNotAccessible = cachedChannelData.isNotAccessible
}
if firstTime && isNotAccessible {
context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId)
}
var hasBots: Bool = false
var hasBotCommands: Bool = false
var botMenuButton: BotMenuButton = .commands
var currentSendAsPeerId: PeerId?
var autoremoveTimeout: Int32?
var copyProtectionEnabled: Bool = false
var hasBirthdayToday = false
var peerVerification: PeerVerification?
if let peer = peerView.peers[peerView.peerId] {
if !displayedPeerVerification {
if let cachedUserData = peerView.cachedData as? CachedUserData {
peerVerification = cachedUserData.verification
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
peerVerification = cachedChannelData.verification
}
}
copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
}
let botCommands = cachedGroupData.botInfos.reduce(into: [], { result, info in
result.append(contentsOf: info.botInfo.commands)
})
if !botCommands.isEmpty {
hasBotCommands = true
}
if case let .known(value) = cachedGroupData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData {
if let channel = peer as? TelegramChannel, channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
currentSendAsPeerId = channel.linkedMonoforumId
} else {
currentSendAsPeerId = nil
}
} else {
currentSendAsPeerId = cachedChannelData.sendAsPeerId
if let channel = peer as? TelegramChannel, case .group = channel.info {
if !cachedChannelData.botInfos.isEmpty {
hasBots = true
}
let botCommands = cachedChannelData.botInfos.reduce(into: [], { result, info in
result.append(contentsOf: info.botInfo.commands)
})
if !botCommands.isEmpty {
hasBotCommands = true
}
}
}
if case let .known(value) = cachedChannelData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
} else if let cachedUserData = peerView.cachedData as? CachedUserData {
botMenuButton = cachedUserData.botInfo?.menuButton ?? .commands
if case let .known(value) = cachedUserData.autoremoveTimeout {
autoremoveTimeout = value?.effectiveValue
}
if let botInfo = cachedUserData.botInfo, !botInfo.commands.isEmpty {
hasBotCommands = true
}
if let birthday = cachedUserData.birthday {
let today = Calendar.current.dateComponents(Set([.day, .month]), from: Date())
if today.day == Int(birthday.day) && today.month == Int(birthday.month) {
hasBirthdayToday = true
}
}
}
}
let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive
var explicitelyCanPinMessages: Bool = false
if let cachedUserData = peerView.cachedData as? CachedUserData {
explicitelyCanPinMessages = cachedUserData.canPinMessages
} else if peerView.peerId == context.account.peerId {
explicitelyCanPinMessages = true
}
if strongSelf.preloadHistoryPeerId != peerDiscussionId {
strongSelf.preloadHistoryPeerId = peerDiscussionId
if let peerDiscussionId = peerDiscussionId, let channel = peerView.peers[peerView.peerId] as? TelegramChannel, case .broadcast = channel.info {
let combinedDisposable = DisposableSet()
strongSelf.preloadHistoryPeerIdDisposable.set(combinedDisposable)
combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: peerDiscussionId).startStrict())
combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
} else {
strongSelf.preloadHistoryPeerIdDisposable.set(nil)
}
}
var appliedBoosts: Int32?
var boostsToUnrestrict: Int32?
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
appliedBoosts = cachedChannelData.appliedBoosts
boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
}
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId {
strongSelf.premiumOrStarsRequiredDisposable = ((context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone()
}
var adMessage = adMessage
if let peer = peerView.peers[peerView.peerId] as? TelegramUser, peer.botInfo != nil {
} else {
adMessage = nil
}
strongSelf.state.isNotAccessible = isNotAccessible
strongSelf.state.contactStatus = contactStatus
strongSelf.state.hasBots = hasBots
strongSelf.state.hasBotCommands = hasBotCommands
strongSelf.state.botMenuButton = botMenuButton
strongSelf.state.isArchived = isArchived
strongSelf.state.peerIsMuted = peerIsMuted
strongSelf.state.peerDiscussionId = peerDiscussionId
strongSelf.state.peerGeoLocation = peerGeoLocation
strongSelf.state.explicitelyCanPinMessages = explicitelyCanPinMessages
strongSelf.state.hasScheduledMessages = hasScheduledMessages
strongSelf.state.autoremoveTimeout = autoremoveTimeout
strongSelf.state.currentSendAsPeerId = currentSendAsPeerId
strongSelf.state.copyProtectionEnabled = copyProtectionEnabled
strongSelf.state.hasSearchTags = hasSearchTags
strongSelf.state.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging
strongSelf.state.sendPaidMessageStars = sendPaidMessageStars
strongSelf.state.alwaysShowGiftButton = alwaysShowGiftButton
strongSelf.state.disallowedGifts = disallowedGifts
strongSelf.state.hasSavedChats = hasSavedChats
strongSelf.state.appliedBoosts = appliedBoosts
strongSelf.state.boostsToUnrestrict = boostsToUnrestrict
strongSelf.state.hasBirthdayToday = hasBirthdayToday
strongSelf.state.businessIntro = businessIntro
strongSelf.state.adMessage = adMessage
strongSelf.state.peerVerification = peerVerification
strongSelf.state.starGiftsAvailable = starGiftsAvailable
strongSelf.state.renderedPeer = renderedPeer
strongSelf.state.adMessage = adMessage
if case .standard(.default) = mode, let channel = renderedPeer?.chatMainPeer as? TelegramChannel, case .broadcast = channel.info {
var isRegularChat = false
if let subject = initialSubject {
if case .message = subject {
isRegularChat = true
}
} else {
isRegularChat = true
}
if strongSelf.nextChannelToReadDisposable == nil, let peerId = chatLocation.peerId, let customChatNavigationStack {
if let index = customChatNavigationStack.firstIndex(of: peerId), index != customChatNavigationStack.count - 1 {
let nextPeerId = customChatNavigationStack[index + 1]
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: nextPeerId)
),
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager)
)
|> then(.complete() |> delay(1.0, queue: .mainQueue()))
|> restart).startStrict(next: { [weak strongSelf] nextPeer, nextChatSuggestionTip in
guard let strongSelf else {
return
}
let previousState = strongSelf.state
var isUpdated = false
if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in
return NextChannelToRead(peer: nextPeer, threadData: nil, unreadCount: 0, location: .same)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
}
let nextPeerId = nextPeer?.id
if strongSelf.preloadNextChatPeerId != nextPeerId {
strongSelf.preloadNextChatPeerId = nextPeerId
if let nextPeerId = nextPeerId {
let combinedDisposable = DisposableSet()
strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable)
combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict())
combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId))
} else {
strongSelf.preloadNextChatPeerIdDisposable.set(nil)
}
}
if isUpdated {
strongSelf.onUpdated?(previousState)
}
})
}
} else if isRegularChat, strongSelf.nextChannelToReadDisposable == nil {
//TODO:loc optimize
let accountPeerId = context.account.peerId
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
context.engine.peers.getNextUnreadChannel(peerId: channel.id, chatListFilterId: currentChatListFilter, getFilterPredicate: { data in
return chatListFilterPredicate(filter: data, accountPeerId: accountPeerId)
}),
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager)
)
|> then(.complete() |> delay(1.0, queue: .mainQueue()))
|> restart).startStrict(next: { [weak strongSelf] nextPeer, nextChatSuggestionTip in
guard let strongSelf else {
return
}
let previousState = strongSelf.state
var isUpdated = false
if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextPeer.flatMap { nextPeer -> NextChannelToRead in
return NextChannelToRead(peer: nextPeer.peer, threadData: nil, unreadCount: nextPeer.unreadCount, location: nextPeer.location)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
}
let nextPeerId = nextPeer?.peer.id
if strongSelf.preloadNextChatPeerId != nextPeerId {
strongSelf.preloadNextChatPeerId = nextPeerId
if let nextPeerId = nextPeerId {
let combinedDisposable = DisposableSet()
strongSelf.preloadNextChatPeerIdDisposable.set(combinedDisposable)
combinedDisposable.add(context.account.viewTracker.polledChannel(peerId: nextPeerId).startStrict())
combinedDisposable.add(context.account.addAdditionalPreloadHistoryPeerId(peerId: nextPeerId))
} else {
strongSelf.preloadNextChatPeerIdDisposable.set(nil)
}
}
if isUpdated {
strongSelf.onUpdated?(previousState)
}
})
}
}
if let upgradedToPeerId {
strongSelf.state.performDismissAction = .upgraded(upgradedToPeerId)
} else if movedToForumTopics {
strongSelf.state.performDismissAction = .movedToForumTopics
} else if shouldDismiss {
strongSelf.state.performDismissAction = .dismiss
}
strongSelf.isChatLocationInfoReady.set(true)
strongSelf.onUpdated?(previousState)
})
if peerId == context.account.peerId {
self.preloadSavedMessagesChatsDisposable?.dispose()
self.preloadSavedMessagesChatsDisposable = context.engine.messages.savedMessagesPeerListHead().start()
}
} else if case let .replyThread(messagePromise) = self.chatLocationInfoData, let peerId = peerId {
self.isPeerInfoReady.set(true)
let replyThreadType: ChatTitleContent.ReplyThreadType
var replyThreadId: Int64?
switch chatLocation {
case .peer:
replyThreadType = .replies
case let .replyThread(replyThreadMessage):
if replyThreadMessage.peerId == context.account.peerId {
replyThreadId = replyThreadMessage.threadId
replyThreadType = .replies
} else {
replyThreadId = replyThreadMessage.threadId
if replyThreadMessage.isChannelPost {
replyThreadType = .comments
} else {
replyThreadType = .replies
}
}
case .customChatContents:
replyThreadType = .replies
}
let peerView = context.account.viewTracker.peerView(peerId)
let messageAndTopic = messagePromise.get()
|> mapToSignal { message -> Signal<(message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int), NoError> in
guard let replyThreadId = replyThreadId else {
return .single((message, nil, 0))
}
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: replyThreadId)
let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Cloud, customTag: nil)
let localCountViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: replyThreadId, namespace: Namespaces.Message.Local, customTag: nil)
return context.account.postbox.combinedView(keys: [viewKey, countViewKey, localCountViewKey])
|> map { views -> (message: Message?, threadData: MessageHistoryThreadData?, messageCount: Int) in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return (message, nil, 0)
}
var messageCount = 0
if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
if replyThreadId == 1 {
messageCount += Int(count)
} else {
messageCount += max(Int(count) - 1, 0)
}
}
if let summaryView = views.views[localCountViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
messageCount += Int(count)
}
return (message, view.info?.data.get(MessageHistoryThreadData.self), messageCount)
}
}
let savedMessagesPeerId: PeerId?
if case let .replyThread(replyThreadMessage) = chatLocation, (replyThreadMessage.peerId == context.account.peerId || replyThreadMessage.isMonoforumPost) {
savedMessagesPeerId = PeerId(replyThreadMessage.threadId)
} else {
savedMessagesPeerId = nil
}
let savedMessagesPeer: Signal<(peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)?, NoError>
if let savedMessagesPeerId {
let threadPeerId = savedMessagesPeerId
let basicPeerKey: PostboxViewKey = .peer(peerId: threadPeerId, components: [])
let countViewKey: PostboxViewKey = .historyTagSummaryView(tag: MessageTags(), peerId: peerId, threadId: savedMessagesPeerId.toInt64(), namespace: Namespaces.Message.Cloud, customTag: nil)
savedMessagesPeer = context.account.postbox.combinedView(keys: [basicPeerKey, countViewKey])
|> map { views -> (peer: EnginePeer?, messageCount: Int, presence: EnginePeer.Presence?)? in
var peer: EnginePeer?
var presence: EnginePeer.Presence?
if let peerView = views.views[basicPeerKey] as? PeerView {
peer = peerViewMainPeer(peerView).flatMap(EnginePeer.init)
presence = peerView.peerPresences[threadPeerId].flatMap(EnginePeer.Presence.init)
}
var messageCount = 0
if let summaryView = views.views[countViewKey] as? MessageHistoryTagSummaryView, let count = summaryView.count {
messageCount += Int(count)
}
return (peer, messageCount, presence)
}
|> distinctUntilChanged(isEqual: { lhs, rhs in
if lhs?.peer != rhs?.peer {
return false
}
if lhs?.messageCount != rhs?.messageCount {
return false
}
if lhs?.presence != rhs?.presence {
return false
}
return true
})
} else {
savedMessagesPeer = .single(nil)
}
var isScheduledOrPinnedMessages = false
switch initialSubject {
case .scheduledMessages, .pinnedMessages, .messageOptions:
isScheduledOrPinnedMessages = true
default:
break
}
var hasScheduledMessages: Signal<Bool, NoError> = .single(false)
if chatLocation.peerId != nil, !isScheduledOrPinnedMessages, peerId.namespace != Namespaces.Peer.SecretChat {
let chatLocationContextHolder = chatLocationContextHolder
hasScheduledMessages = peerView
|> take(1)
|> mapToSignal { view -> Signal<Bool, NoError> in
if let peer = peerViewMainPeer(view) as? TelegramChannel, !peer.hasPermission(.sendSomething) {
return .single(false)
} else {
if case let .replyThread(message) = chatLocation, message.peerId == context.account.peerId {
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: .peer(id: context.account.peerId), contextHolder: Atomic(value: nil)))
|> map { view, _, _ in
return !view.entries.isEmpty
}
|> distinctUntilChanged
} else {
return context.account.viewTracker.scheduledMessagesViewForLocation(context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder))
|> map { view, _, _ in
return !view.entries.isEmpty
}
|> distinctUntilChanged
}
}
}
}
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
if peerId.namespace == Namespaces.Peer.CloudChannel {
let recentOnlineSignal: Signal<(total: Int32?, recent: Int32?), NoError> = peerView
|> map { view -> Bool? in
if let cachedData = view.cachedData as? CachedChannelData, let peer = peerViewMainPeer(view) as? TelegramChannel {
if case .broadcast = peer.info {
return nil
} else if let memberCount = cachedData.participantsSummary.memberCount, memberCount > 50 {
return true
} else {
return false
}
} else {
return false
}
}
|> distinctUntilChanged
|> mapToSignal { isLarge -> Signal<(total: Int32?, recent: Int32?), NoError> in
if let isLarge = isLarge {
if isLarge {
return context.peerChannelMemberCategoriesContextsManager.recentOnline(account: context.account, accountPeerId: context.account.peerId, peerId: peerId)
|> map { value -> (total: Int32?, recent: Int32?) in
return (nil, value)
}
} else {
return context.peerChannelMemberCategoriesContextsManager.recentOnlineSmall(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerId)
|> map { value -> (total: Int32?, recent: Int32?) in
return (value.total, value.recent)
}
}
} else {
return .single((nil, nil))
}
}
onlineMemberCount = recentOnlineSignal
}
let hasSearchTags: Signal<Bool, NoError>
if let peerId = chatLocation.peerId, peerId == context.account.peerId {
hasSearchTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: chatLocation.threadId)
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
} else {
hasSearchTags = .single(false)
}
let hasSavedChats: Signal<Bool, NoError>
if case .peer(context.account.peerId) = chatLocation {
hasSavedChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
} else {
hasSavedChats = .single(false)
}
let isPremiumRequiredForMessaging: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
isPremiumRequiredForMessaging = context.engine.peers.subscribeIsPremiumRequiredForMessaging(id: peerId)
|> distinctUntilChanged
} else {
isPremiumRequiredForMessaging = .single(false)
}
let globalPrivacySettings = context.engine.data.get(TelegramEngine.EngineData.Item.Configuration.GlobalPrivacy())
self.peerDisposable = (combineLatest(queue: Queue.mainQueue(),
peerView,
messageAndTopic,
savedMessagesPeer,
onlineMemberCount,
hasScheduledMessages,
hasSearchTags,
hasSavedChats,
isPremiumRequiredForMessaging,
managingBot,
globalPrivacySettings
)
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView, messageAndTopic, savedMessagesPeer, onlineMemberCount, hasScheduledMessages, hasSearchTags, hasSavedChats, isPremiumRequiredForMessaging, managingBot, globalPrivacySettings in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
strongSelf.state.hasScheduledMessages = hasScheduledMessages
var renderedPeer: RenderedPeer?
var contactStatus: ChatContactStatus?
var copyProtectionEnabled = false
var businessIntro: TelegramBusinessIntro?
var sendPaidMessageStars: StarsAmount?
var alwaysShowGiftButton = false
var disallowedGifts: TelegramDisallowedGifts?
if let peer = peerView.peers[peerView.peerId] {
copyProtectionEnabled = peer.isCopyProtectionEnabled
if let cachedData = peerView.cachedData as? CachedUserData {
contactStatus = ChatContactStatus(canAddContact: !peerView.peerIsContact, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: nil, managingBot: managingBot)
if case let .known(value) = cachedData.businessIntro {
businessIntro = value
}
if cachedData.disallowedGifts != .All {
alwaysShowGiftButton = globalPrivacySettings.displayGiftButton || cachedData.flags.contains(.displayGiftButton)
}
disallowedGifts = cachedData.disallowedGifts
} else if let cachedData = peerView.cachedData as? CachedGroupData {
var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy {
if let peer = peerView.peers[invitedByPeerId] {
invitedBy = peer
}
}
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
} else if let cachedData = peerView.cachedData as? CachedChannelData {
var invitedBy: Peer?
if let invitedByPeerId = cachedData.invitedBy {
if let peer = peerView.peers[invitedByPeerId] {
invitedBy = peer
}
}
contactStatus = ChatContactStatus(canAddContact: false, peerStatusSettings: cachedData.peerStatusSettings, invitedBy: invitedBy, managingBot: managingBot)
if let channel = peerView.peers[peerView.peerId] as? TelegramChannel {
if channel.isMonoForum {
if let linkedMonoforumId = channel.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
} else {
sendPaidMessageStars = channel.sendPaidMessageStars
}
} else {
if channel.flags.contains(.isCreator) || channel.adminRights != nil {
} else {
sendPaidMessageStars = channel.sendPaidMessageStars
}
}
}
}
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)
}
if let savedMessagesPeerId {
var peerPresences: [PeerId: PeerPresence] = [:]
if let presence = savedMessagesPeer?.presence {
peerPresences[savedMessagesPeerId] = presence._asPresence()
}
let mappedPeerData = ChatTitleContent.PeerData(
peerId: savedMessagesPeerId,
peer: savedMessagesPeer?.peer?._asPeer(),
isContact: true,
isSavedMessages: true,
notificationSettings: nil,
peerPresences: peerPresences,
cachedData: nil
)
var customMessageCount: Int?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, peer.isMonoForum {
} else {
customMessageCount = savedMessagesPeer?.messageCount ?? 0
}
strongSelf.state.chatTitleContent = .peer(peerView: mappedPeerData, customTitle: nil, onlineMemberCount: (nil, nil), isScheduledMessages: false, isMuted: false, customMessageCount: customMessageCount, isEnabled: true)
strongSelf.state.peerView = peerView
let imageOverride: AvatarNodeImageOverride?
if context.account.peerId == savedMessagesPeerId {
imageOverride = .myNotesIcon
} else if let peer = savedMessagesPeer?.peer, peer.id.isReplies {
imageOverride = .repliesIcon
} else if let peer = savedMessagesPeer?.peer, peer.id.isAnonymousSavedMessages {
imageOverride = .anonymousSavedMessagesIcon(isColored: true)
} else if let peer = savedMessagesPeer?.peer, peer.isDeleted {
imageOverride = .deletedIcon
} else {
imageOverride = nil
}
if let peer = savedMessagesPeer?.peer {
var infoContextActionIsEnabled = false
if case .standard(.previewing) = mode {
infoContextActionIsEnabled = false
} else {
infoContextActionIsEnabled = true
}
strongSelf.state.infoAvatar = .peer(
peer: peer,
imageOverride: imageOverride,
contextActionIsEnabled: infoContextActionIsEnabled,
accessibilityLabel: strings.Conversation_ContextMenuOpenProfile
)
}
var currentSendAsPeerId: PeerId?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
if peer.isMonoForum {
if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
currentSendAsPeerId = peer.linkedMonoforumId
} else {
currentSendAsPeerId = nil
}
} else {
currentSendAsPeerId = cachedData.sendAsPeerId
}
}
strongSelf.state.renderedPeer = renderedPeer
strongSelf.state.savedMessagesTopicPeer = savedMessagesPeer?.peer
strongSelf.state.hasSearchTags = hasSearchTags
strongSelf.state.hasSavedChats = hasSavedChats
strongSelf.state.hasScheduledMessages = hasScheduledMessages
strongSelf.state.currentSendAsPeerId = currentSendAsPeerId
} else {
let message = messageAndTopic.message
var count = 0
if let message = message {
for attribute in message.attributes {
if let attribute = attribute as? ReplyThreadMessageAttribute {
count = Int(attribute.count)
break
}
}
}
var peerIsMuted = false
if let threadData = messageAndTopic.threadData {
if case let .muted(until) = threadData.notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
peerIsMuted = true
}
} else if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
if case let .muted(until) = notificationSettings.muteState, until >= Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) {
peerIsMuted = true
}
}
if let threadInfo = messageAndTopic.threadData?.info {
strongSelf.state.chatTitleContent = .peer(peerView: ChatTitleContent.PeerData(peerView: peerView), customTitle: threadInfo.title, onlineMemberCount: onlineMemberCount, isScheduledMessages: false, isMuted: peerIsMuted, customMessageCount: messageAndTopic.messageCount == 0 ? nil : messageAndTopic.messageCount, isEnabled: true)
let avatarContent: EmojiStatusComponent.Content
if chatLocation.threadId == 1 {
avatarContent = .image(image: PresentationResourcesChat.chatGeneralThreadIcon(strongSelf.presentationData.theme), tintColor: nil)
} else if let fileId = threadInfo.icon {
avatarContent = .animation(content: .customEmoji(fileId: fileId), size: CGSize(width: 48.0, height: 48.0), placeholderColor: strongSelf.presentationData.theme.list.mediaPlaceholderColor, themeColor: strongSelf.presentationData.theme.list.itemAccentColor, loopMode: .count(1))
} else {
avatarContent = .topic(title: String(threadInfo.title.prefix(1)), color: threadInfo.iconColor, size: CGSize(width: 32.0, height: 32.0))
}
var infoContextActionIsEnabled = false
if case .standard(.previewing) = mode {
infoContextActionIsEnabled = false
} else {
infoContextActionIsEnabled = true
}
strongSelf.state.infoAvatar = .emojiStatus(content: avatarContent, contextActionIsEnabled: infoContextActionIsEnabled)
} else {
strongSelf.state.chatTitleContent = .replyThread(type: replyThreadType, count: count)
}
var wasGroupChannel: Bool?
if let previousPeerView = strongSelf.state.peerView, let info = (previousPeerView.peers[previousPeerView.peerId] as? TelegramChannel)?.info {
if case .group = info {
wasGroupChannel = true
} else {
wasGroupChannel = false
}
}
var isGroupChannel: Bool?
if let info = (peerView.peers[peerView.peerId] as? TelegramChannel)?.info {
if case .group = info {
isGroupChannel = true
} else {
isGroupChannel = false
}
}
let firstTime = strongSelf.state.peerView == nil
if wasGroupChannel != isGroupChannel {
if let isGroupChannel = isGroupChannel, isGroupChannel {
let (recentDisposable, _) = context.peerChannelMemberCategoriesContextsManager.recent(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
let (adminsDisposable, _) = context.peerChannelMemberCategoriesContextsManager.admins(engine: context.engine, postbox: context.account.postbox, network: context.account.network, accountPeerId: context.account.peerId, peerId: peerView.peerId, updated: { _ in })
let disposable = DisposableSet()
disposable.add(recentDisposable)
disposable.add(adminsDisposable)
strongSelf.chatAdditionalDataDisposable.set(disposable)
} else {
strongSelf.chatAdditionalDataDisposable.set(nil)
}
}
strongSelf.state.peerView = peerView
strongSelf.state.threadInfo = messageAndTopic.threadData?.info
var peerDiscussionId: PeerId?
var peerGeoLocation: PeerGeoLocation?
var currentSendAsPeerId: PeerId?
if let peer = peerView.peers[peerView.peerId] as? TelegramChannel, let cachedData = peerView.cachedData as? CachedChannelData {
if peer.isMonoForum {
if let linkedMonoforumId = peer.linkedMonoforumId, let mainChannel = peerView.peers[linkedMonoforumId] as? TelegramChannel, mainChannel.hasPermission(.sendSomething) {
currentSendAsPeerId = peer.linkedMonoforumId
} else {
currentSendAsPeerId = nil
}
} else {
currentSendAsPeerId = cachedData.sendAsPeerId
if case .group = peer.info {
peerGeoLocation = cachedData.peerGeoLocation
}
if case let .known(value) = cachedData.linkedDiscussionPeerId {
peerDiscussionId = value
}
}
}
var isNotAccessible: Bool = false
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
isNotAccessible = cachedChannelData.isNotAccessible
}
if firstTime && isNotAccessible {
context.account.viewTracker.forceUpdateCachedPeerData(peerId: peerView.peerId)
}
var hasBots: Bool = false
if let peer = peerView.peers[peerView.peerId] {
if let cachedGroupData = peerView.cachedData as? CachedGroupData {
if !cachedGroupData.botInfos.isEmpty {
hasBots = true
}
} else if let cachedChannelData = peerView.cachedData as? CachedChannelData, let channel = peer as? TelegramChannel, case .group = channel.info {
if !cachedChannelData.botInfos.isEmpty {
hasBots = true
}
}
}
let isArchived: Bool = peerView.groupId == Namespaces.PeerGroup.archive
var explicitelyCanPinMessages: Bool = false
if let cachedUserData = peerView.cachedData as? CachedUserData {
explicitelyCanPinMessages = cachedUserData.canPinMessages
} else if peerView.peerId == context.account.peerId {
explicitelyCanPinMessages = true
}
if strongSelf.preloadHistoryPeerId != peerDiscussionId {
strongSelf.preloadHistoryPeerId = peerDiscussionId
if let peerDiscussionId = peerDiscussionId {
strongSelf.preloadHistoryPeerIdDisposable.set(context.account.addAdditionalPreloadHistoryPeerId(peerId: peerDiscussionId))
} else {
strongSelf.preloadHistoryPeerIdDisposable.set(nil)
}
}
var appliedBoosts: Int32?
var boostsToUnrestrict: Int32?
if let cachedChannelData = peerView.cachedData as? CachedChannelData {
appliedBoosts = cachedChannelData.appliedBoosts
boostsToUnrestrict = cachedChannelData.boostsToUnrestrict
}
if strongSelf.premiumOrStarsRequiredDisposable == nil, sendPaidMessageStars != nil, let peerId = chatLocation.peerId {
strongSelf.premiumOrStarsRequiredDisposable = ((context.engine.peers.isPremiumRequiredToContact([peerId]) |> then(.complete() |> suspendAwareDelay(60.0, queue: Queue.concurrentDefaultQueue()))) |> restart).startStandalone()
}
strongSelf.state.renderedPeer = renderedPeer
strongSelf.state.isNotAccessible = isNotAccessible
strongSelf.state.contactStatus = contactStatus
strongSelf.state.hasBots = hasBots
strongSelf.state.isArchived = isArchived
strongSelf.state.peerIsMuted = peerIsMuted
strongSelf.state.peerDiscussionId = peerDiscussionId
strongSelf.state.peerGeoLocation = peerGeoLocation
strongSelf.state.explicitelyCanPinMessages = explicitelyCanPinMessages
strongSelf.state.hasScheduledMessages = hasScheduledMessages
strongSelf.state.currentSendAsPeerId = currentSendAsPeerId
strongSelf.state.copyProtectionEnabled = copyProtectionEnabled
strongSelf.state.hasSearchTags = hasSearchTags
strongSelf.state.isPremiumRequiredForMessaging = isPremiumRequiredForMessaging
strongSelf.state.hasSavedChats = hasSavedChats
strongSelf.state.appliedBoosts = appliedBoosts
strongSelf.state.boostsToUnrestrict = boostsToUnrestrict
strongSelf.state.businessIntro = businessIntro
strongSelf.state.sendPaidMessageStars = sendPaidMessageStars
strongSelf.state.alwaysShowGiftButton = alwaysShowGiftButton
strongSelf.state.disallowedGifts = disallowedGifts
if let replyThreadId, let channel = renderedPeer?.peer as? TelegramChannel, channel.isForumOrMonoForum, strongSelf.nextChannelToReadDisposable == nil {
strongSelf.nextChannelToReadDisposable = (combineLatest(queue: .mainQueue(),
context.engine.peers.getNextUnreadForumTopic(peerId: channel.id, topicId: Int32(clamping: replyThreadId)),
ApplicationSpecificNotice.getNextChatSuggestionTip(accountManager: context.sharedContext.accountManager)
)
|> then(.complete() |> delay(1.0, queue: .mainQueue()))
|> restart).startStrict(next: { nextThreadData, nextChatSuggestionTip in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
var isUpdated = false
if !strongSelf.state.offerNextChannelToRead {
strongSelf.state.offerNextChannelToRead = true
isUpdated = true
}
let nextChannelToRead = nextThreadData.flatMap { nextThreadData -> NextChannelToRead in
return NextChannelToRead(peer: EnginePeer(channel), threadData: NextChannelToRead.ThreadData(id: nextThreadData.id, data: nextThreadData.data), unreadCount: Int(nextThreadData.data.incomingUnreadCount), location: .same)
}
if strongSelf.state.nextChannelToRead != nextChannelToRead {
strongSelf.state.nextChannelToRead = nextChannelToRead
isUpdated = true
}
if strongSelf.state.nextChannelToReadDisplayName != (nextChatSuggestionTip >= 3) {
strongSelf.state.nextChannelToReadDisplayName = nextChatSuggestionTip >= 3
isUpdated = true
}
if isUpdated {
strongSelf.onUpdated?(previousState)
}
})
}
}
strongSelf.isChatLocationInfoReady.set(true)
strongSelf.onUpdated?(previousState)
})
} else if case .customChatContents = self.chatLocationInfoData {
self.titleDisposable?.dispose()
self.titleDisposable = nil
self.isPeerInfoReady.set(true)
let peerView: Signal<PeerView?, NoError> = .single(nil)
if case let .customChatContents(customChatContents) = initialSubject {
switch customChatContents.kind {
case .hashTagSearch:
break
case let .quickReplyMessageInput(shortcut, shortcutType):
switch shortcutType {
case .generic:
self.state.chatTitleContent = .custom("\(shortcut)", nil, false)
case .greeting:
self.state.chatTitleContent = .custom(strings.QuickReply_TitleGreetingMessage, nil, false)
case .away:
self.state.chatTitleContent = .custom(strings.QuickReply_TitleAwayMessage, nil, false)
}
case let .businessLinkSetup(link):
let linkUrl: String
if link.url.hasPrefix("https://") {
linkUrl = String(link.url[link.url.index(link.url.startIndex, offsetBy: "https://".count)...])
} else {
linkUrl = link.url
}
self.state.chatTitleContent = .custom(link.title ?? strings.Business_Links_EditLinkTitle, linkUrl, false)
}
} else {
self.state.chatTitleContent = .custom(" ", nil, false)
}
self.peerDisposable = (peerView
|> deliverOnMainQueue).startStrict(next: { [weak self] peerView in
guard let self else {
return
}
let previousState = self.state
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.state.infoAvatar = .peer(
peer: EnginePeer(peer),
imageOverride: nil,
contextActionIsEnabled: false,
accessibilityLabel: nil
)
} else {
self.state.infoAvatar = nil
}
self.state.peerView = peerView
self.state.renderedPeer = renderedPeer
self.isChatLocationInfoReady.set(true)
self.onUpdated?(previousState)
})
}
let initialData = historyNode.initialData
|> take(1)
|> deliverOnMainQueue
|> beforeNext { [weak self] combinedInitialData in
guard let strongSelf = self, let combinedInitialData else {
return
}
let previousState = strongSelf.state
if let opaqueState = (combinedInitialData.initialData?.storedInterfaceState).flatMap(_internal_decodeStoredChatInterfaceState) {
var interfaceState = ChatInterfaceState.parse(opaqueState)
var pinnedMessageId: MessageId?
var peerIsBlocked: Bool = false
var callsAvailable: Bool = true
var callsPrivate: Bool = false
var activeGroupCallInfo: ChatActiveGroupCallInfo?
var slowmodeState: ChatSlowmodeState?
if let cachedData = combinedInitialData.cachedData as? CachedChannelData {
pinnedMessageId = cachedData.pinnedMessageId
var canBypassRestrictions = false
if let boostsToUnrestrict = cachedData.boostsToUnrestrict, let appliedBoosts = cachedData.appliedBoosts, appliedBoosts >= boostsToUnrestrict {
canBypassRestrictions = true
}
if !canBypassRestrictions, let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout {
if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) {
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp))
}
}
if let activeCall = cachedData.activeCall {
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
}
} else if let cachedData = combinedInitialData.cachedData as? CachedUserData {
peerIsBlocked = cachedData.isBlocked
callsAvailable = cachedData.voiceCallsAvailable
callsPrivate = cachedData.callsPrivate
pinnedMessageId = cachedData.pinnedMessageId
} else if let cachedData = combinedInitialData.cachedData as? CachedGroupData {
pinnedMessageId = cachedData.pinnedMessageId
if let activeCall = cachedData.activeCall {
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
}
} else if let _ = combinedInitialData.cachedData as? CachedSecretChatData {
}
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel {
if channel.hasBannedPermission(.banSendVoice) != nil && channel.hasBannedPermission(.banSendInstantVideos) != nil {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
} else if channel.hasBannedPermission(.banSendVoice) != nil {
if channel.hasBannedPermission(.banSendInstantVideos) == nil {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
}
} else if channel.hasBannedPermission(.banSendInstantVideos) != nil {
if channel.hasBannedPermission(.banSendVoice) == nil {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
}
}
} else if let group = combinedInitialData.initialData?.peer as? TelegramGroup {
if group.hasBannedPermission(.banSendVoice) && group.hasBannedPermission(.banSendInstantVideos) {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
} else if group.hasBannedPermission(.banSendVoice) {
if !group.hasBannedPermission(.banSendInstantVideos) {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.video)
}
} else if group.hasBannedPermission(.banSendInstantVideos) {
if !group.hasBannedPermission(.banSendVoice) {
interfaceState = interfaceState.withUpdatedMediaRecordingMode(.audio)
}
}
}
if case let .replyThread(replyThreadMessageId) = chatLocation {
if let channel = combinedInitialData.initialData?.peer as? TelegramChannel, channel.isForumOrMonoForum {
pinnedMessageId = nil
} else {
pinnedMessageId = replyThreadMessageId.effectiveTopId
}
}
var pinnedMessage: ChatPinnedMessage?
if let pinnedMessageId = pinnedMessageId {
if let cachedDataMessages = combinedInitialData.cachedDataMessages {
if let message = cachedDataMessages[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
}
}
}
var buttonKeyboardMessage = combinedInitialData.buttonKeyboardMessage
if let buttonKeyboardMessageValue = buttonKeyboardMessage, buttonKeyboardMessageValue.isRestricted(platform: "ios", contentSettings: context.currentContentSettings.with({ $0 })) {
buttonKeyboardMessage = nil
}
strongSelf.state.pinnedMessageId = pinnedMessageId
strongSelf.state.pinnedMessage = pinnedMessage
strongSelf.state.keyboardButtonsMessage = buttonKeyboardMessage
strongSelf.state.peerIsBlocked = peerIsBlocked
strongSelf.state.callsAvailable = callsAvailable
strongSelf.state.callsPrivate = callsPrivate
strongSelf.state.activeGroupCallInfo = activeGroupCallInfo
strongSelf.state.slowmodeState = slowmodeState
var initialEditMessage: Message?
if let editMessage = interfaceState.editMessage, let message = combinedInitialData.initialData?.associatedMessages[editMessage.messageId] {
initialEditMessage = message
}
strongSelf.initialInterfaceState = (interfaceState, initialEditMessage)
}
if let readStateData = combinedInitialData.readStateData {
if case let .peer(peerId) = chatLocation, let peerReadStateData = readStateData[peerId], let notificationSettings = peerReadStateData.notificationSettings {
let inAppSettings = context.sharedContext.currentInAppNotificationSettings.with { $0 }
let (count, _) = renderedTotalUnreadCount(inAppSettings: inAppSettings, totalUnreadState: peerReadStateData.totalState ?? ChatListTotalUnreadState(absoluteCounters: [:], filteredCounters: [:]))
var globalRemainingUnreadChatCount = count
if !notificationSettings.isRemovedFromTotalUnreadCount(default: false) && peerReadStateData.unreadCount > 0 {
if case .messages = inAppSettings.totalUnreadCountDisplayCategory {
globalRemainingUnreadChatCount -= peerReadStateData.unreadCount
} else {
globalRemainingUnreadChatCount -= 1
}
}
if globalRemainingUnreadChatCount > 0 {
strongSelf.initialNavigationBadge = "\(globalRemainingUnreadChatCount)"
}
}
}
strongSelf.onUpdated?(previousState)
}
self.isReady.set(combineLatest(queue: .mainQueue(), [
self.isPeerInfoReady.get(),
self.isChatLocationInfoReady.get(),
self.isCachedDataReady.get(),
historyNode.isReady,
initialData |> map { _ -> Bool in true }
])
|> map { values in
return !values.contains(where: { !$0 })
}
|> filter { $0 }
|> take(1)
|> distinctUntilChanged)
self.buttonKeyboardMessageDisposable?.dispose()
self.buttonKeyboardMessageDisposable = historyNode.buttonKeyboardMessage.startStrict(next: { [weak self] message in
guard let strongSelf = self else {
return
}
var buttonKeyboardMessageUpdated = false
if let currentButtonKeyboardMessage = strongSelf.state.keyboardButtonsMessage, let message = message {
if currentButtonKeyboardMessage.id != message.id || currentButtonKeyboardMessage.stableVersion != message.stableVersion {
buttonKeyboardMessageUpdated = true
}
} else if (strongSelf.state.keyboardButtonsMessage != nil) != (message != nil) {
buttonKeyboardMessageUpdated = true
}
if buttonKeyboardMessageUpdated {
let previousState = strongSelf.state
strongSelf.state.keyboardButtonsMessage = message
strongSelf.onUpdated?(previousState)
}
})
if let peerId = chatLocation.peerId {
let customEmojiAvailable: Signal<Bool, NoError> = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.SecretChatLayer(id: peerId)
)
|> map { layer -> Bool in
guard let layer = layer else {
return true
}
return layer >= 144
}
|> distinctUntilChanged
let isForum = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> map { peer -> Bool in
if case let .channel(channel) = peer {
return channel.isForumOrMonoForum
} else {
return false
}
}
|> distinctUntilChanged
let threadData: Signal<ChatPresentationInterfaceState.ThreadData?, NoError>
let forumTopicData: Signal<ChatPresentationInterfaceState.ThreadData?, NoError>
if let threadId = chatLocation.threadId {
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId)
threadData = context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> ChatPresentationInterfaceState.ThreadData? in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return nil
}
guard let data = view.info?.data.get(MessageHistoryThreadData.self) else {
return nil
}
return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed)
}
|> distinctUntilChanged
forumTopicData = .single(nil)
} else {
forumTopicData = isForum
|> mapToSignal { isForum -> Signal<ChatPresentationInterfaceState.ThreadData?, NoError> in
if isForum {
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: 1)
return context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> ChatPresentationInterfaceState.ThreadData? in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return nil
}
guard let data = view.info?.data.get(MessageHistoryThreadData.self) else {
return nil
}
return ChatPresentationInterfaceState.ThreadData(title: data.info.title, icon: data.info.icon, iconColor: data.info.iconColor, isOwnedByMe: data.isOwnedByMe, isClosed: data.isClosed)
}
|> distinctUntilChanged
} else {
return .single(nil)
}
}
threadData = .single(nil)
}
if case .standard(.previewing) = mode {
} else if peerId.namespace != Namespaces.Peer.SecretChat && peerId != context.account.peerId && initialSubject != .scheduledMessages {
self.premiumGiftSuggestionDisposable?.dispose()
self.premiumGiftSuggestionDisposable = (ApplicationSpecificNotice.dismissedPremiumGiftSuggestion(accountManager: context.sharedContext.accountManager, peerId: peerId)
|> deliverOnMainQueue).startStrict(next: { [weak self] timestamp in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
let currentTime = Int32(Date().timeIntervalSince1970)
var suggest = true
if let timestamp, currentTime < timestamp + 60 * 60 * 24 {
suggest = false
}
strongSelf.state.suggestPremiumGift = suggest
strongSelf.onUpdated?(previousState)
})
var baseLanguageCode = self.presentationData.strings.baseLanguageCode
if baseLanguageCode.contains("-") {
baseLanguageCode = baseLanguageCode.components(separatedBy: "-").first ?? baseLanguageCode
}
let isPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
} |> distinctUntilChanged
let isHidden = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.TranslationHidden(id: peerId))
|> distinctUntilChanged
let hasAutoTranslate = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.AutoTranslateEnabled(id: peerId))
|> distinctUntilChanged
self.translationStateDisposable?.dispose()
self.translationStateDisposable = (combineLatest(
queue: .concurrentDefaultQueue(),
isPremium,
isHidden,
hasAutoTranslate,
ApplicationSpecificNotice.translationSuggestion(accountManager: context.sharedContext.accountManager)
) |> mapToSignal { isPremium, isHidden, hasAutoTranslate, counterAndTimestamp -> Signal<ChatPresentationTranslationState?, NoError> in
var maybeSuggestPremium = false
if counterAndTimestamp.0 >= 3 {
maybeSuggestPremium = true
}
if (isPremium || maybeSuggestPremium || hasAutoTranslate) && !isHidden {
return chatTranslationState(context: context, peerId: peerId, threadId: chatLocation.threadId)
|> map { translationState -> ChatPresentationTranslationState? in
if let translationState, !translationState.fromLang.isEmpty && (translationState.fromLang != baseLanguageCode || translationState.isEnabled) {
return ChatPresentationTranslationState(isEnabled: translationState.isEnabled, fromLang: translationState.fromLang, toLang: translationState.toLang ?? baseLanguageCode)
} else {
return nil
}
}
|> distinctUntilChanged
} else {
return .single(nil)
}
}
|> deliverOnMainQueue).startStrict(next: { [weak self] chatTranslationState in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
strongSelf.state.translationState = chatTranslationState
strongSelf.onUpdated?(previousState)
})
}
let premiumGiftOptions: Signal<[CachedPremiumGiftOption], NoError> = .single([])
|> then(
context.engine.payments.premiumGiftCodeOptions(peerId: peerId, onlyCached: true)
|> map { options in
return options.filter { $0.users == 1 }.map { CachedPremiumGiftOption(months: $0.months, currency: $0.currency, amount: $0.amount, botUrl: "", storeProductId: $0.storeProductId) }
}
)
let isTopReplyThreadMessageShown: Signal<Bool, NoError> = historyNode.isTopReplyThreadMessageShown.get()
|> distinctUntilChanged
let hasPendingMessages: Signal<Bool, NoError>
let chatLocationPeerId = chatLocation.peerId
if let chatLocationPeerId = chatLocationPeerId {
hasPendingMessages = context.account.pendingMessageManager.hasPendingMessages
|> mapToSignal { peerIds -> Signal<Bool, NoError> in
let value = peerIds.contains(chatLocationPeerId)
if value {
return .single(true)
} else {
return .single(false)
}
}
|> distinctUntilChanged
} else {
hasPendingMessages = .single(false)
}
let topPinnedMessage: Signal<ChatPinnedMessage?, NoError>
if let subject = initialSubject {
switch subject {
case .messageOptions, .pinnedMessages, .scheduledMessages:
topPinnedMessage = .single(nil)
default:
topPinnedMessage = ChatControllerImpl.topPinnedScrollMessage(context: context, chatLocation: chatLocation, historyNode: historyNode, scrolledToMessageId: self.scrolledToMessageId.get())
}
} else {
topPinnedMessage = ChatControllerImpl.topPinnedScrollMessage(context: context, chatLocation: chatLocation, historyNode: historyNode, scrolledToMessageId: self.scrolledToMessageId.get())
}
self.cachedDataDisposable?.dispose()
self.cachedDataDisposable = combineLatest(queue: .mainQueue(), historyNode.cachedPeerDataAndMessages,
hasPendingMessages,
isTopReplyThreadMessageShown,
topPinnedMessage,
customEmojiAvailable,
isForum,
threadData,
forumTopicData,
premiumGiftOptions
).startStrict(next: { [weak self] cachedDataAndMessages, hasPendingMessages, isTopReplyThreadMessageShown, topPinnedMessage, customEmojiAvailable, isForum, threadData, forumTopicData, premiumGiftOptions in
guard let strongSelf = self else {
return
}
let previousState = strongSelf.state
let (cachedData, messages) = cachedDataAndMessages
if cachedData != nil {
var themeEmoticon: String? = nil
var chatWallpaper: TelegramWallpaper?
if let cachedData = cachedData as? CachedUserData {
themeEmoticon = cachedData.themeEmoticon
chatWallpaper = cachedData.wallpaper
} else if let cachedData = cachedData as? CachedGroupData {
themeEmoticon = cachedData.themeEmoticon
} else if let cachedData = cachedData as? CachedChannelData {
themeEmoticon = cachedData.themeEmoticon
chatWallpaper = cachedData.wallpaper
}
strongSelf.chatThemeEmoticonPromise.set(.single(themeEmoticon))
strongSelf.chatWallpaperPromise.set(.single(chatWallpaper))
}
var pinnedMessageId: MessageId?
var peerIsBlocked: Bool = false
var callsAvailable: Bool = false
var callsPrivate: Bool = false
var voiceMessagesAvailable: Bool = true
var slowmodeState: ChatSlowmodeState?
var activeGroupCallInfo: ChatActiveGroupCallInfo?
var inviteRequestsPending: Int32?
if let cachedData = cachedData as? CachedChannelData {
pinnedMessageId = cachedData.pinnedMessageId
if !canBypassRestrictions(boostsToUnrestrict: strongSelf.state.boostsToUnrestrict, appliedBoosts: strongSelf.state.appliedBoosts) {
if let channel = strongSelf.state.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout {
if hasPendingMessages {
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .pendingMessages)
} else if let slowmodeUntilTimestamp = calculateSlowmodeActiveUntilTimestamp(account: context.account, untilTimestamp: cachedData.slowModeValidUntilTimestamp) {
slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp))
}
}
}
if let activeCall = cachedData.activeCall {
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
}
inviteRequestsPending = cachedData.inviteRequestsPending
} else if let cachedData = cachedData as? CachedUserData {
peerIsBlocked = cachedData.isBlocked
callsAvailable = cachedData.voiceCallsAvailable
callsPrivate = cachedData.callsPrivate
pinnedMessageId = cachedData.pinnedMessageId
voiceMessagesAvailable = cachedData.voiceMessagesAvailable
} else if let cachedData = cachedData as? CachedGroupData {
pinnedMessageId = cachedData.pinnedMessageId
if let activeCall = cachedData.activeCall {
activeGroupCallInfo = ChatActiveGroupCallInfo(activeCall: activeCall)
}
inviteRequestsPending = cachedData.inviteRequestsPending
} else if let _ = cachedData as? CachedSecretChatData {
}
var pinnedMessage: ChatPinnedMessage?
switch chatLocation {
case let .replyThread(replyThreadMessage):
if isForum {
pinnedMessageId = topPinnedMessage?.message.id
pinnedMessage = topPinnedMessage
} else {
if isTopReplyThreadMessageShown {
pinnedMessageId = nil
} else {
pinnedMessageId = replyThreadMessage.effectiveTopId
}
if let pinnedMessageId = pinnedMessageId {
if let message = messages?[pinnedMessageId] {
pinnedMessage = ChatPinnedMessage(message: message, index: 0, totalCount: 1, topMessageId: message.id)
}
}
}
case .peer:
pinnedMessageId = topPinnedMessage?.message.id
pinnedMessage = topPinnedMessage
case .customChatContents:
pinnedMessageId = nil
pinnedMessage = nil
}
var pinnedMessageUpdated = false
if let current = strongSelf.state.pinnedMessage, let updated = pinnedMessage {
if current != updated {
pinnedMessageUpdated = true
}
} else if (strongSelf.state.pinnedMessage != nil) != (pinnedMessage != nil) {
pinnedMessageUpdated = true
}
let callsDataUpdated = strongSelf.state.callsAvailable != callsAvailable || strongSelf.state.callsPrivate != callsPrivate
let voiceMessagesAvailableUpdated = strongSelf.state.voiceMessagesAvailable != voiceMessagesAvailable
var canManageInvitations = false
if let channel = strongSelf.state.renderedPeer?.peer as? TelegramChannel, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
canManageInvitations = true
} else if let group = strongSelf.state.renderedPeer?.peer as? TelegramGroup {
if case .creator = group.role {
canManageInvitations = true
} else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) {
canManageInvitations = true
}
}
if canManageInvitations, let inviteRequestsPending = inviteRequestsPending, inviteRequestsPending >= 0 {
if strongSelf.inviteRequestsContext == nil {
let inviteRequestsContext = context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
strongSelf.inviteRequestsContext = inviteRequestsContext
} else if let inviteRequestsContext = strongSelf.inviteRequestsContext {
let _ = (inviteRequestsContext.state
|> take(1)
|> deliverOnMainQueue).startStandalone(next: { [weak inviteRequestsContext] state in
if state.count != inviteRequestsPending {
inviteRequestsContext?.loadMore()
}
})
}
if chatLocation.threadId == nil {
if strongSelf.inviteRequestsDisposable == nil, let inviteRequestsContext = strongSelf.inviteRequestsContext {
strongSelf.inviteRequestsDisposable = combineLatest(queue: Queue.mainQueue(), inviteRequestsContext.state, ApplicationSpecificNotice.dismissedInvitationRequests(accountManager: context.sharedContext.accountManager, peerId: peerId)).startStrict(next: { [weak strongSelf] requestsState, dismissedInvitationRequests in
guard let strongSelf else {
return
}
let previousState = strongSelf.state
strongSelf.state.requestsState = requestsState
strongSelf.state.dismissedInvitationRequests = dismissedInvitationRequests
strongSelf.onUpdated?(previousState)
})
}
} else {
strongSelf.state.requestsState = nil
strongSelf.state.dismissedInvitationRequests = []
}
} else {
strongSelf.inviteRequestsContext = nil
strongSelf.state.requestsState = nil
strongSelf.state.dismissedInvitationRequests = []
}
var isUpdated = false
if strongSelf.state.pinnedMessageId != pinnedMessageId || strongSelf.state.pinnedMessage != pinnedMessage || strongSelf.state.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || voiceMessagesAvailableUpdated || strongSelf.state.slowmodeState != slowmodeState || strongSelf.state.activeGroupCallInfo != activeGroupCallInfo || customEmojiAvailable != strongSelf.state.customEmojiAvailable || threadData != strongSelf.state.threadData || forumTopicData != strongSelf.state.forumTopicData || premiumGiftOptions != strongSelf.state.premiumGiftOptions {
isUpdated = true
strongSelf.state.pinnedMessage = pinnedMessage
strongSelf.state.pinnedMessageId = pinnedMessageId
strongSelf.state.activeGroupCallInfo = activeGroupCallInfo
strongSelf.state.peerIsBlocked = peerIsBlocked
strongSelf.state.callsAvailable = callsAvailable
strongSelf.state.callsPrivate = callsPrivate
strongSelf.state.voiceMessagesAvailable = voiceMessagesAvailable
strongSelf.state.customEmojiAvailable = customEmojiAvailable
strongSelf.state.threadData = threadData
strongSelf.state.forumTopicData = forumTopicData
strongSelf.state.isGeneralThreadClosed = forumTopicData?.isClosed
strongSelf.state.premiumGiftOptions = premiumGiftOptions
strongSelf.state.slowmodeState = slowmodeState
}
strongSelf.isCachedDataReady.set(true)
if isUpdated {
strongSelf.onUpdated?(previousState)
}
})
} else {
self.isCachedDataReady.set(true)
}
}
deinit {
self.peerDisposable?.dispose()
self.titleDisposable?.dispose()
self.preloadSavedMessagesChatsDisposable?.dispose()
self.preloadHistoryPeerIdDisposable.dispose()
self.preloadNextChatPeerIdDisposable.dispose()
self.nextChannelToReadDisposable?.dispose()
self.chatAdditionalDataDisposable.dispose()
self.premiumOrStarsRequiredDisposable?.dispose()
self.buttonKeyboardMessageDisposable?.dispose()
self.cachedDataDisposable?.dispose()
self.premiumGiftSuggestionDisposable?.dispose()
self.translationStateDisposable?.dispose()
self.inviteRequestsDisposable?.dispose()
}
}
}