Telegram-iOS/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoData.swift
isaac d1aa0db537 Postbox -> TelegramEngine waves 46-93 (squashed)
Squash of 63 commits spanning waves 46-93 (plus interspersed docs commits)
of the gradual Postbox->TelegramEngine consumer-side migration.

Scope: 139 files changed, 2123 insertions(+), 452 deletions(-).

## Themes by wave-block

**Waves 46-58 — Peer field migrations + facade additions**
Foundational EnginePeer convenience init additions (PeerReference, RenderedPeer,
SelectivePrivacyPeer). Multiple `peer: Peer` field migrations across PeerInfo,
ChatList, and SettingsUI components.

**Waves 59-73 — peer field cascade + EnginePeer wrap drops**
Series of single- to two-file peer-field migrations; consumer-side wrap
removal (`EnginePeer(peer)` -> direct EnginePeer use); `as? TelegramUser`
cast conversion to `case let .user(...)` enum match. Wave 64: RenderedPeer
convenience init. Wave 68: SelectivePrivacyPeer convenience init.

**Waves 74-83 — controller-Node bridge cleanup + small migrations**
Wave-71 shadow-pattern cleanup at controller->Node bridges. Migrations of
ChatRecentActionsController.peer (74), PeerInfoMember (75), MentionChatInputPanelItem
(76), PassportUI SecureIdAuthController (77), AccountWithInfo + ShareController
(78), peerInputActivitiesPromise (79), InactiveChannel (80), BlockedPeers (81),
openHashtag resolveSignal (82), NotificationExceptionsList (83).

**Waves 84-90 — TelegramEngine.Resources facade migrations**
Per-method Shape-A/B sweeps converting `<ctx>.account.postbox.mediaBox.X(...)`
to `<ctx>.engine.resources.X(...)`. Wave 90 was a single-commit big sweep:
40 fetchedMediaResource sites in 25 files migrated to engine.resources.fetch
facade in one atomic pass with first-pass-clean build.

Methods covered: storeResourceData, completedResourcePath, cancelInteractiveResourceFetch,
resourceRangesStatus, resourceStatus, fetch (fetchedMediaResource).

**Waves 91-92 — additional type migrations**
Wave 91: ItemListWebsiteItem.peer + RecentSessionsController enum-case payload
+ openWebSession callback Peer? -> EnginePeer?.
Wave 92: ChatListController StateHolder.EntryContext status type
MediaResourceStatus -> EngineMediaResource.FetchStatus.

**Wave 93 — speculative `import Postbox` drop sweep**
Drop import from 7 wave-touched files where it became unused; restore in 5
files where bare PeerId/Message/MediaId/StoryId references escaped the
pre-flight regex. Includes one MediaId(...) -> EngineMedia.Id(...) swap in
InAppPurchaseManager to unlock its import drop.

## Build state

Final state at squash: clean Telegram/Telegram build at debug_sim_arm64.

## Persistent-state notes

- Pre-existing WIP unchanged across the squashed range:
  - build-system/bazel-rules/sourcekit-bazel-bsp submodule marker
  - Untracked: build-system/tulsi/, submodules/TgVoip/, third-party/libx264/

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 20:48:15 +04:00

2692 lines
125 KiB
Swift

import Foundation
import UIKit
import Postbox
import TelegramCore
import SwiftSignalKit
import AccountContext
import PeerPresenceStatusManager
import TelegramStringFormatting
import TelegramPresentationData
import PeerAvatarGalleryUI
import TelegramUIPreferences
import TelegramNotices
import AccountUtils
import DeviceAccess
import PeerInfoVisualMediaPaneNode
import PhotoResources
import PeerInfoPaneNode
import WebUI
enum PeerInfoUpdatingAvatar {
case none
case image(TelegramMediaImageRepresentation)
}
enum AvatarUploadProgress {
case value(CGFloat)
case indefinite
}
final class PeerInfoState {
let isEditing: Bool
let selectedMessageIds: Set<MessageId>?
let selectedStoryIds: Set<Int32>?
let paneIsReordering: Bool
let updatingAvatar: PeerInfoUpdatingAvatar?
let updatingBio: String?
let updatingNote: NSAttributedString?
let avatarUploadProgress: AvatarUploadProgress?
let highlightedButton: PeerInfoHeaderButtonKey?
let isEditingBirthDate: Bool
let updatingBirthDate: TelegramBirthday??
let personalChannels: [TelegramAdminedPublicChannel]?
init(
isEditing: Bool,
selectedMessageIds: Set<MessageId>?,
selectedStoryIds: Set<Int32>?,
paneIsReordering: Bool,
updatingAvatar: PeerInfoUpdatingAvatar?,
updatingBio: String?,
updatingNote: NSAttributedString?,
avatarUploadProgress: AvatarUploadProgress?,
highlightedButton: PeerInfoHeaderButtonKey?,
isEditingBirthDate: Bool,
updatingBirthDate: TelegramBirthday??,
personalChannels: [TelegramAdminedPublicChannel]?
) {
self.isEditing = isEditing
self.selectedMessageIds = selectedMessageIds
self.selectedStoryIds = selectedStoryIds
self.paneIsReordering = paneIsReordering
self.updatingAvatar = updatingAvatar
self.updatingBio = updatingBio
self.updatingNote = updatingNote
self.avatarUploadProgress = avatarUploadProgress
self.highlightedButton = highlightedButton
self.isEditingBirthDate = isEditingBirthDate
self.updatingBirthDate = updatingBirthDate
self.personalChannels = personalChannels
}
func withIsEditing(_ isEditing: Bool) -> PeerInfoState {
return PeerInfoState(
isEditing: isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withSelectedMessageIds(_ selectedMessageIds: Set<MessageId>?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withSelectedStoryIds(_ selectedStoryIds: Set<Int32>?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withPaneIsReordering(_ paneIsReordering: Bool) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withUpdatingAvatar(_ updatingAvatar: PeerInfoUpdatingAvatar?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withUpdatingBio(_ updatingBio: String?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withUpdatingNote(_ updatingNote: NSAttributedString?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withAvatarUploadProgress(_ avatarUploadProgress: AvatarUploadProgress?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withHighlightedButton(_ highlightedButton: PeerInfoHeaderButtonKey?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withIsEditingBirthDate(_ isEditingBirthDate: Bool) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withUpdatingBirthDate(_ updatingBirthDate: TelegramBirthday??) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: updatingBirthDate,
personalChannels: self.personalChannels
)
}
func withPersonalChannels(_ personalChannels: [TelegramAdminedPublicChannel]?) -> PeerInfoState {
return PeerInfoState(
isEditing: self.isEditing,
selectedMessageIds: self.selectedMessageIds,
selectedStoryIds: self.selectedStoryIds,
paneIsReordering: self.paneIsReordering,
updatingAvatar: self.updatingAvatar,
updatingBio: self.updatingBio,
updatingNote: self.updatingNote,
avatarUploadProgress: self.avatarUploadProgress,
highlightedButton: self.highlightedButton,
isEditingBirthDate: self.isEditingBirthDate,
updatingBirthDate: self.updatingBirthDate,
personalChannels: personalChannels
)
}
}
final class TelegramGlobalSettings {
let suggestPhoneNumberConfirmation: Bool
let suggestPasswordConfirmation: Bool
let suggestPasswordSetup: Bool
let premiumGracePeriod: Bool
let accountsAndPeers: [(AccountContext, EnginePeer, Int32)]
let activeSessionsContext: ActiveSessionsContext?
let webSessionsContext: WebSessionsContext?
let otherSessionsCount: Int?
let proxySettings: ProxySettings
let notificationAuthorizationStatus: AccessType
let notificationWarningSuppressed: Bool
let notificationExceptions: NotificationExceptionsList?
let inAppNotificationSettings: InAppNotificationSettings
let privacySettings: AccountPrivacySettings?
let unreadTrendingStickerPacks: Int
let archivedStickerPacks: [ArchivedStickerPackItem]?
let userLimits: EngineConfiguration.UserLimits
let bots: [AttachMenuBot]
let hasPassport: Bool
let hasWatchApp: Bool
let enableQRLogin: Bool
init(
suggestPhoneNumberConfirmation: Bool,
suggestPasswordConfirmation: Bool,
suggestPasswordSetup: Bool,
premiumGracePeriod: Bool,
accountsAndPeers: [(AccountContext, EnginePeer, Int32)],
activeSessionsContext: ActiveSessionsContext?,
webSessionsContext: WebSessionsContext?,
otherSessionsCount: Int?,
proxySettings: ProxySettings,
notificationAuthorizationStatus: AccessType,
notificationWarningSuppressed: Bool,
notificationExceptions: NotificationExceptionsList?,
inAppNotificationSettings: InAppNotificationSettings,
privacySettings: AccountPrivacySettings?,
unreadTrendingStickerPacks: Int,
archivedStickerPacks: [ArchivedStickerPackItem]?,
userLimits: EngineConfiguration.UserLimits,
bots: [AttachMenuBot],
hasPassport: Bool,
hasWatchApp: Bool,
enableQRLogin: Bool
) {
self.suggestPhoneNumberConfirmation = suggestPhoneNumberConfirmation
self.suggestPasswordConfirmation = suggestPasswordConfirmation
self.suggestPasswordSetup = suggestPasswordSetup
self.premiumGracePeriod = premiumGracePeriod
self.accountsAndPeers = accountsAndPeers
self.activeSessionsContext = activeSessionsContext
self.webSessionsContext = webSessionsContext
self.otherSessionsCount = otherSessionsCount
self.proxySettings = proxySettings
self.notificationAuthorizationStatus = notificationAuthorizationStatus
self.notificationWarningSuppressed = notificationWarningSuppressed
self.notificationExceptions = notificationExceptions
self.inAppNotificationSettings = inAppNotificationSettings
self.privacySettings = privacySettings
self.unreadTrendingStickerPacks = unreadTrendingStickerPacks
self.archivedStickerPacks = archivedStickerPacks
self.userLimits = userLimits
self.bots = bots
self.hasPassport = hasPassport
self.hasWatchApp = hasWatchApp
self.enableQRLogin = enableQRLogin
}
}
final class PeerInfoPersonalChannelData: Equatable {
let peer: EngineRenderedPeer
let subscriberCount: Int?
let topMessages: [EngineMessage]
let storyStats: PeerStoryStats?
let isLoading: Bool
init(peer: EngineRenderedPeer, subscriberCount: Int?, topMessages: [EngineMessage], storyStats: PeerStoryStats?, isLoading: Bool) {
self.peer = peer
self.subscriberCount = subscriberCount
self.topMessages = topMessages
self.storyStats = storyStats
self.isLoading = isLoading
}
static func ==(lhs: PeerInfoPersonalChannelData, rhs: PeerInfoPersonalChannelData) -> Bool {
if lhs === rhs {
return true
}
if lhs.peer != rhs.peer {
return false
}
if lhs.subscriberCount != rhs.subscriberCount {
return false
}
if lhs.topMessages != rhs.topMessages {
return false
}
if lhs.storyStats != rhs.storyStats {
return false
}
if lhs.isLoading != rhs.isLoading {
return false
}
return true
}
}
final class PeerInfoScreenData {
let peer: EnginePeer?
let chatPeer: EnginePeer?
let savedMessagesPeer: EnginePeer?
let cachedData: CachedPeerData?
let status: PeerInfoStatusData?
let peerNotificationSettings: TelegramPeerNotificationSettings?
let threadNotificationSettings: TelegramPeerNotificationSettings?
let globalNotificationSettings: EngineGlobalNotificationSettings?
let availablePanes: [PeerInfoPaneKey]
let groupsInCommon: GroupsInCommonContext?
let linkedDiscussionPeer: EnginePeer?
let linkedMonoforumPeer: EnginePeer?
let members: PeerInfoMembersData?
let storyListContext: StoryListContext?
let storyArchiveListContext: StoryListContext?
let botPreviewStoryListContext: StoryListContext?
let encryptionKeyFingerprint: SecretChatKeyFingerprint?
let globalSettings: TelegramGlobalSettings?
let invitations: PeerExportedInvitationsState?
let requests: PeerInvitationImportersState?
let requestsContext: PeerInvitationImportersContext?
let threadData: MessageHistoryThreadData?
let appConfiguration: AppConfiguration?
let isPowerSavingEnabled: Bool?
let accountIsPremium: Bool
let hasSavedMessageTags: Bool
let hasBotPreviewItems: Bool
let isPremiumRequiredForStoryPosting: Bool
let personalChannel: PeerInfoPersonalChannelData?
let starsState: StarsContext.State?
let tonState: StarsContext.State?
let starsRevenueStatsState: StarsRevenueStats?
let starsRevenueStatsContext: StarsRevenueStatsContext?
let revenueStatsState: StarsRevenueStats?
let revenueStatsContext: StarsRevenueStatsContext?
let profileGiftsContext: ProfileGiftsContext?
let profileGiftsCollectionsContext: ProfileGiftsCollectionsContext?
let premiumGiftOptions: [PremiumGiftCodeOption]
let webAppPermissions: WebAppPermissionsState?
let savedMusicContext: ProfileSavedMusicContext?
let savedMusicState: ProfileSavedMusicContext.State?
let managedByBot: EnginePeer?
let businessConnectedBot: EnginePeer?
let _isContact: Bool
var forceIsContact: Bool = false
var isContact: Bool {
if self.forceIsContact {
return true
} else {
return self._isContact
}
}
init(
peer: EnginePeer?,
chatPeer: EnginePeer?,
savedMessagesPeer: EnginePeer?,
cachedData: CachedPeerData?,
status: PeerInfoStatusData?,
peerNotificationSettings: TelegramPeerNotificationSettings?,
threadNotificationSettings: TelegramPeerNotificationSettings?,
globalNotificationSettings: EngineGlobalNotificationSettings?,
isContact: Bool,
availablePanes: [PeerInfoPaneKey],
groupsInCommon: GroupsInCommonContext?,
linkedDiscussionPeer: EnginePeer?,
linkedMonoforumPeer: EnginePeer?,
members: PeerInfoMembersData?,
storyListContext: StoryListContext?,
storyArchiveListContext: StoryListContext?,
botPreviewStoryListContext: StoryListContext?,
encryptionKeyFingerprint: SecretChatKeyFingerprint?,
globalSettings: TelegramGlobalSettings?,
invitations: PeerExportedInvitationsState?,
requests: PeerInvitationImportersState?,
requestsContext: PeerInvitationImportersContext?,
threadData: MessageHistoryThreadData?,
appConfiguration: AppConfiguration?,
isPowerSavingEnabled: Bool?,
accountIsPremium: Bool,
hasSavedMessageTags: Bool,
hasBotPreviewItems: Bool,
isPremiumRequiredForStoryPosting: Bool,
personalChannel: PeerInfoPersonalChannelData?,
starsState: StarsContext.State?,
tonState: StarsContext.State?,
starsRevenueStatsState: StarsRevenueStats?,
starsRevenueStatsContext: StarsRevenueStatsContext?,
revenueStatsState: StarsRevenueStats?,
revenueStatsContext: StarsRevenueStatsContext?,
profileGiftsContext: ProfileGiftsContext?,
profileGiftsCollectionsContext: ProfileGiftsCollectionsContext?,
premiumGiftOptions: [PremiumGiftCodeOption],
webAppPermissions: WebAppPermissionsState?,
savedMusicContext: ProfileSavedMusicContext?,
savedMusicState: ProfileSavedMusicContext.State?,
managedByBot: EnginePeer?,
businessConnectedBot: EnginePeer?
) {
self.peer = peer
self.chatPeer = chatPeer
self.savedMessagesPeer = savedMessagesPeer
self.cachedData = cachedData
self.status = status
self.peerNotificationSettings = peerNotificationSettings
self.threadNotificationSettings = threadNotificationSettings
self.globalNotificationSettings = globalNotificationSettings
self._isContact = isContact
self.availablePanes = availablePanes
self.groupsInCommon = groupsInCommon
self.linkedDiscussionPeer = linkedDiscussionPeer
self.linkedMonoforumPeer = linkedMonoforumPeer
self.members = members
self.storyListContext = storyListContext
self.storyArchiveListContext = storyArchiveListContext
self.botPreviewStoryListContext = botPreviewStoryListContext
self.encryptionKeyFingerprint = encryptionKeyFingerprint
self.globalSettings = globalSettings
self.invitations = invitations
self.requests = requests
self.requestsContext = requestsContext
self.threadData = threadData
self.appConfiguration = appConfiguration
self.isPowerSavingEnabled = isPowerSavingEnabled
self.accountIsPremium = accountIsPremium
self.hasSavedMessageTags = hasSavedMessageTags
self.hasBotPreviewItems = hasBotPreviewItems
self.isPremiumRequiredForStoryPosting = isPremiumRequiredForStoryPosting
self.personalChannel = personalChannel
self.starsState = starsState
self.tonState = tonState
self.starsRevenueStatsState = starsRevenueStatsState
self.starsRevenueStatsContext = starsRevenueStatsContext
self.revenueStatsState = revenueStatsState
self.revenueStatsContext = revenueStatsContext
self.profileGiftsContext = profileGiftsContext
self.profileGiftsCollectionsContext = profileGiftsCollectionsContext
self.premiumGiftOptions = premiumGiftOptions
self.webAppPermissions = webAppPermissions
self.savedMusicContext = savedMusicContext
self.savedMusicState = savedMusicState
self.managedByBot = managedByBot
self.businessConnectedBot = businessConnectedBot
}
}
private enum PeerInfoScreenInputUserKind {
case user
case bot
case support
case settings
}
private enum PeerInfoScreenInputData: Equatable {
case none
case settings
case user(userId: PeerId, secretChatId: PeerId?, kind: PeerInfoScreenInputUserKind)
case channel
case group(groupId: PeerId)
}
public func hasAvailablePeerInfoMediaPanes(context: AccountContext, peerId: PeerId) -> Signal<Bool, NoError> {
let chatLocationContextHolder = Atomic<ChatLocationContextHolder?>(value: nil)
let mediaPanes = peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: .peer(id: peerId), isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: nil)
|> map { panes -> Bool in
if let panes {
return !panes.isEmpty
} else {
return false
}
}
let hasSavedMessagesChats: Signal<Bool, NoError>
if peerId == context.account.peerId {
hasSavedMessagesChats = context.engine.messages.savedMessagesHasPeersOtherThanSaved()
|> distinctUntilChanged
} else {
hasSavedMessagesChats = .single(false)
}
return combineLatest(queue: .mainQueue(), [mediaPanes, hasSavedMessagesChats])
|> map { values in
return values.contains(true)
}
}
private func peerInfoAvailableMediaPanes(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, isMyProfile: Bool, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>, sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?) -> Signal<[PeerInfoPaneKey]?, NoError> {
var peerId = peerId
var chatLocation = chatLocation
var chatLocationContextHolder = chatLocationContextHolder
if let sharedMediaFromForumTopic {
peerId = sharedMediaFromForumTopic.0
chatLocation = .replyThread(message: ChatReplyThreadMessage(
peerId: sharedMediaFromForumTopic.0,
threadId: sharedMediaFromForumTopic.1,
channelMessageId: nil,
isChannelPost: false,
isForumPost: true,
isMonoforumPost: true,
maxMessage: nil,
maxReadIncomingMessageId: nil,
maxReadOutgoingMessageId: nil,
unreadCount: 0,
initialFilledHoles: IndexSet(),
initialAnchor: .automatic,
isNotAvailable: false
))
chatLocationContextHolder = Atomic(value: nil)
}
let _ = peerId
var tags: [(MessageTags, PeerInfoPaneKey)] = []
if !isMyProfile {
tags = [
(.photoOrVideo, .media),
(.file, .files),
(.music, .music),
(.voiceOrInstantVideo, .voice),
(.webPage, .links),
(.gif, .gifs),
(.polls, .polls)
]
}
enum PaneState {
case loading
case empty
case present
}
let loadedOnce = Atomic<Bool>(value: false)
return combineLatest(queue: .mainQueue(), tags.map { tagAndKey -> Signal<(PeerInfoPaneKey, PaneState), NoError> in
let (tag, key) = tagAndKey
let location = context.chatLocationInput(for: chatLocation, contextHolder: chatLocationContextHolder)
return context.account.viewTracker.aroundMessageHistoryViewForLocation(location, index: .upperBound, anchorIndex: .upperBound, count: 20, clipHoles: false, fixedCombinedReadStates: nil, tag: .tag(tag))
|> map { (view, _, _) -> (PeerInfoPaneKey, PaneState) in
if view.entries.isEmpty {
if view.isLoading {
return (key, .loading)
} else {
return (key, .empty)
}
} else {
return (key, .present)
}
}
})
|> map { keysAndStates -> [PeerInfoPaneKey]? in
let loadedOnceValue = loadedOnce.with { $0 }
var result: [PeerInfoPaneKey] = []
var hasNonLoaded = false
for (key, state) in keysAndStates {
switch state {
case .present:
result.append(key)
case .empty:
break
case .loading:
hasNonLoaded = true
}
}
if !hasNonLoaded || loadedOnceValue {
if !loadedOnceValue {
let _ = loadedOnce.swap(true)
}
return result
} else {
return nil
}
}
|> distinctUntilChanged
}
enum PeerInfoMembersData: Equatable {
case shortList(membersContext: PeerInfoMembersContext, members: [PeerInfoMember])
case longList(PeerInfoMembersContext)
var membersContext: PeerInfoMembersContext {
switch self {
case let .shortList(membersContext, _):
return membersContext
case let .longList(membersContext):
return membersContext
}
}
}
private func peerInfoScreenInputData(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoScreenInputData, NoError> {
return `deferred` {
return context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> mapToSignal { peer -> Signal<PeerInfoScreenInputData, NoError> in
guard let peer = peer else {
return .single(.none)
}
if case let .user(user) = peer {
if isSettings && user.id == context.account.peerId {
return .single(.settings)
} else {
let kind: PeerInfoScreenInputUserKind
if user.flags.contains(.isSupport) {
kind = .support
} else if user.botInfo != nil {
kind = .bot
} else {
kind = .user
}
return .single(.user(userId: user.id, secretChatId: nil, kind: kind))
}
} else if case let .channel(channel) = peer {
if case .group = channel.info {
return .single(.group(groupId: channel.id))
} else {
return .single(.channel)
}
} else if case let .legacyGroup(group) = peer {
return .single(.group(groupId: group.id))
} else if case let .secretChat(secretChat) = peer {
return .single(.user(userId: secretChat.regularPeerId, secretChatId: peer.id, kind: .user))
} else {
return .single(.none)
}
}
|> distinctUntilChanged
}
}
public func keepPeerInfoScreenDataHot(context: AccountContext, peerId: PeerId, chatLocation: ChatLocation, chatLocationContextHolder: Atomic<ChatLocationContextHolder?>) -> Signal<Never, NoError> {
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: false)
|> mapToSignal { inputData -> Signal<Never, NoError> in
switch inputData {
case .none, .settings:
return .complete()
case .user, .channel, .group:
var signals: [Signal<Never, NoError>] = []
signals.append(context.peerChannelMemberCategoriesContextsManager.profileData(postbox: context.account.postbox, network: context.account.network, peerId: peerId, customData: peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: nil) |> ignoreValues) |> ignoreValues)
signals.append(context.peerChannelMemberCategoriesContextsManager.profilePhotos(postbox: context.account.postbox, network: context.account.network, peerId: peerId, fetch: peerInfoProfilePhotos(context: context, peerId: peerId)) |> ignoreValues)
if case .user = inputData {
signals.append(Signal { _ in
let listContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
let expiringListContext = PeerExpiringStoryListContext(account: context.account, peerId: peerId)
return ActionDisposable {
let _ = listContext
let _ = expiringListContext
}
})
}
return combineLatest(signals)
|> ignoreValues
}
}
}
private func peerInfoPersonalOrLinkedChannel(context: AccountContext, peerId: EnginePeer.Id, isSettings: Bool) -> Signal<PeerInfoPersonalChannelData?, NoError> {
let personalChannel: Signal<TelegramEngine.EngineData.Item.Peer.PersonalChannel.Result, NoError> = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.Peer(id: peerId)
)
|> mapToSignal { peer -> Signal<TelegramEngine.EngineData.Item.Peer.PersonalChannel.Result, NoError> in
guard let peer else {
return .single(.known(nil))
}
if case .user = peer {
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.PersonalChannel(id: peerId)
)
} else if case let .channel(channel) = peer, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
return .single(CachedTelegramPersonalChannel.known(TelegramPersonalChannel(peerId: linkedMonoforumId, subscriberCount: nil, topMessageId: nil)))
}
return .single(.known(nil))
}
return personalChannel
|> distinctUntilChanged
|> mapToSignal { personalChannel -> Signal<PeerInfoPersonalChannelData?, NoError> in
guard case let .known(personalChannelValue) = personalChannel, let personalChannelValue else {
return .single(nil)
}
return context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.RenderedPeer(id: personalChannelValue.peerId),
TelegramEngine.EngineData.Item.Peer.ParticipantCount(id: personalChannelValue.peerId)
)
|> mapToSignal { channelRenderedPeer, participantCount -> Signal<PeerInfoPersonalChannelData?, NoError> in
guard let channelRenderedPeer, let channelPeer = channelRenderedPeer.peer else {
return .single(nil)
}
let polledChannel: Signal<Void, NoError> = Signal<Void, NoError>.single(Void())
|> then(
context.account.viewTracker.polledChannel(peerId: channelPeer.id)
|> ignoreValues
|> map { _ -> Void in
}
)
return combineLatest(
context.account.postbox.aroundMessageHistoryViewForLocation(.peer(peerId: channelPeer.id, threadId: nil), anchor: .upperBound, ignoreMessagesInTimestampRange: nil, ignoreMessageIds: Set(), count: 10, clipHoles: false, fixedCombinedReadStates: nil, topTaggedMessageIdNamespaces: Set(), tag: nil, appendMessagesFromTheSameGroup: false, namespaces: .not(Namespaces.Message.allNonRegular), orderStatistics: []),
context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.StoryStats(id: channelPeer.id)
),
polledChannel
)
|> map { viewData, storyStats, _ -> PeerInfoPersonalChannelData? in
let (view, _, _) = viewData
var messages: [EngineMessage] = []
for i in (0 ..< view.entries.count).reversed() {
if messages.isEmpty {
messages.append(EngineMessage(view.entries[i].message))
} else if messages[0].groupingKey != nil && messages[0].groupingKey == view.entries[i].message.groupingKey {
messages.append(EngineMessage(view.entries[i].message))
}
}
messages = messages.reversed()
var isLoading = false
if messages.isEmpty && view.isLoading {
isLoading = true
}
var mappedParticipantCount: Int?
if let participantCount {
mappedParticipantCount = participantCount
} else if let subscriberCount = personalChannelValue.subscriberCount {
mappedParticipantCount = Int(subscriberCount)
}
return PeerInfoPersonalChannelData(
peer: channelRenderedPeer,
subscriberCount: mappedParticipantCount,
topMessages: messages,
storyStats: storyStats,
isLoading: isLoading
)
}
}
}
|> distinctUntilChanged
}
func peerInfoScreenSettingsData(context: AccountContext, peerId: EnginePeer.Id, accountsAndPeers: Signal<[(AccountContext, EnginePeer, Int32)], NoError>, activeSessionsContextAndCount: Signal<(ActiveSessionsContext, Int, WebSessionsContext)?, NoError>, notificationExceptions: Signal<NotificationExceptionsList?, NoError>, privacySettings: Signal<AccountPrivacySettings?, NoError>, archivedStickerPacks: Signal<[ArchivedStickerPackItem]?, NoError>, hasPassport: Signal<Bool, NoError>, starsContext: StarsContext?, tonContext: StarsContext?) -> Signal<PeerInfoScreenData, NoError> {
let preferences = context.sharedContext.accountManager.sharedData(keys: [
SharedDataKeys.proxySettings,
ApplicationSpecificSharedDataKeys.inAppNotificationSettings,
ApplicationSpecificSharedDataKeys.experimentalUISettings
])
let notificationsAuthorizationStatus = Promise<AccessType>(.allowed)
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
notificationsAuthorizationStatus.set(
.single(.allowed)
|> then(DeviceAccess.authorizationStatus(applicationInForeground: context.sharedContext.applicationBindings.applicationInForeground, subject: .notifications)
)
)
}
let notificationsWarningSuppressed = Promise<Bool>(true)
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
notificationsWarningSuppressed.set(
.single(true)
|> then(context.sharedContext.accountManager.noticeEntry(key: ApplicationSpecificNotice.permissionWarningKey(permission: .notifications)!)
|> map { noticeView -> Bool in
let timestamp = noticeView.value.flatMap({ ApplicationSpecificNotice.getTimestampValue($0) })
if let timestamp = timestamp, timestamp > 0 {
return true
} else {
return false
}
}
)
)
}
let hasPassword: Signal<Bool?, NoError> = .single(nil) |> then(
context.engine.auth.twoStepVerificationConfiguration()
|> map { configuration -> Bool? in
var notSet = false
switch configuration {
case let .notSet(pendingEmail):
if pendingEmail == nil {
notSet = true
}
case .set:
break
}
return !notSet
}
)
|> distinctUntilChanged
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
let botsKey = ValueBoxKey(length: 8)
botsKey.setInt64(0, value: 0)
let bots = context.engine.data.subscribe(TelegramEngine.EngineData.Item.ItemCache.Item(collectionId: Namespaces.CachedItemCollection.attachMenuBots, id: botsKey))
|> mapToSignal { entry -> Signal<[AttachMenuBot], NoError> in
let bots: [AttachMenuBots.Bot] = entry?.get(AttachMenuBots.self)?.bots ?? []
return context.engine.data.subscribe(
EngineDataMap(bots.map(\.peerId).map(TelegramEngine.EngineData.Item.Peer.Peer.init))
)
|> mapToSignal { peersMap -> Signal<[AttachMenuBot], NoError> in
var result: [Signal<AttachMenuBot?, NoError>] = []
for bot in bots {
if let maybePeer = peersMap[bot.peerId], let peer = maybePeer {
let resultBot = AttachMenuBot(peer: peer, shortName: bot.name, icons: bot.icons, peerTypes: bot.peerTypes, flags: bot.flags)
if bot.flags.contains(.showInSettings) {
result.append(.single(resultBot))
}
}
}
return combineLatest(result)
|> map { bots in
var result: [AttachMenuBot] = []
for bot in bots {
if let bot {
result.append(bot)
}
}
return result
}
|> distinctUntilChanged
}
}
let starsState: Signal<StarsContext.State?, NoError>
if let starsContext {
starsState = starsContext.state
} else {
starsState = .single(nil)
}
let tonState: Signal<StarsContext.State?, NoError>
if let tonContext {
tonState = tonContext.state
} else {
tonState = .single(nil)
}
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
let businessConnectedBot = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: context.account.peerId)
)
|> map { bot -> EnginePeer.Id? in
return bot?.id
}
|> distinctUntilChanged
|> mapToSignal { botPeerId -> Signal<EnginePeer?, NoError> in
guard let botPeerId else {
return .single(nil)
}
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId))
}
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
accountsAndPeers,
activeSessionsContextAndCount,
privacySettings,
preferences,
combineLatest(notificationExceptions, notificationsAuthorizationStatus.get(), notificationsWarningSuppressed.get()),
combineLatest(context.account.viewTracker.featuredStickerPacks(), archivedStickerPacks),
hasPassport,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)),
context.engine.notices.getServerProvidedSuggestions(),
context.engine.data.get(
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: false),
TelegramEngine.EngineData.Item.Configuration.UserLimits(isPremium: true)
),
hasPassword,
context.sharedContext.automaticMediaDownloadSettings
|> mapToSignal { settings -> Signal<Bool, NoError> in
return automaticEnergyUsageShouldBeOn(settings: settings)
}
|> distinctUntilChanged,
hasStories,
bots,
peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: true),
starsState,
tonState,
businessConnectedBot
)
|> map { peerView, accountsAndPeers, accountSessions, privacySettings, sharedPreferences, notifications, stickerPacks, hasPassport, accountPreferences, suggestions, limits, hasPassword, isPowerSavingEnabled, hasStories, bots, personalChannel, starsState, tonState, businessConnectedBot -> PeerInfoScreenData in
let (notificationExceptions, notificationsAuthorizationStatus, notificationsWarningSuppressed) = notifications
let (featuredStickerPacks, archivedStickerPacks) = stickerPacks
let proxySettings: ProxySettings = sharedPreferences.entries[SharedDataKeys.proxySettings]?.get(ProxySettings.self) ?? ProxySettings.defaultSettings
let inAppNotificationSettings: InAppNotificationSettings = sharedPreferences.entries[ApplicationSpecificSharedDataKeys.inAppNotificationSettings]?.get(InAppNotificationSettings.self) ?? InAppNotificationSettings.defaultSettings
let unreadTrendingStickerPacks = featuredStickerPacks.reduce(0, { count, item -> Int in
return item.unread ? count + 1 : count
})
var enableQRLogin = false
let appConfiguration = accountPreferences?.get(AppConfiguration.self)
if let appConfiguration, let data = appConfiguration.data, let enableQR = data["qr_login_camera"] as? Bool, enableQR {
enableQRLogin = true
}
var suggestPasswordSetup = false
if suggestions.contains(.setupPassword), let hasPassword, !hasPassword {
suggestPasswordSetup = true
}
let peer = peerView.peers[peerId]
let globalSettings = TelegramGlobalSettings(
suggestPhoneNumberConfirmation: suggestions.contains(.validatePhoneNumber),
suggestPasswordConfirmation: suggestions.contains(.validatePassword),
suggestPasswordSetup: suggestPasswordSetup,
premiumGracePeriod: suggestions.contains(.gracePremium),
accountsAndPeers: accountsAndPeers,
activeSessionsContext: accountSessions?.0,
webSessionsContext: accountSessions?.2,
otherSessionsCount: accountSessions?.1,
proxySettings: proxySettings,
notificationAuthorizationStatus: notificationsAuthorizationStatus,
notificationWarningSuppressed: notificationsWarningSuppressed,
notificationExceptions: notificationExceptions,
inAppNotificationSettings: inAppNotificationSettings,
privacySettings: privacySettings,
unreadTrendingStickerPacks: unreadTrendingStickerPacks,
archivedStickerPacks: archivedStickerPacks,
userLimits: peer?.isPremium == true ? limits.1 : limits.0,
bots: bots,
hasPassport: hasPassport,
hasWatchApp: false,
enableQRLogin: enableQRLogin
)
return PeerInfoScreenData(
peer: peer.flatMap(EnginePeer.init),
chatPeer: peer.flatMap(EnginePeer.init),
savedMessagesPeer: nil,
cachedData: peerView.cachedData,
status: nil,
peerNotificationSettings: nil,
threadNotificationSettings: nil,
globalNotificationSettings: nil,
isContact: false,
availablePanes: [],
groupsInCommon: nil,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: hasStories == true ? storyListContext : nil,
storyArchiveListContext: nil,
botPreviewStoryListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: globalSettings,
invitations: nil,
requests: nil,
requestsContext: nil,
threadData: nil,
appConfiguration: appConfiguration,
isPowerSavingEnabled: isPowerSavingEnabled,
accountIsPremium: peer?.isPremium ?? false,
hasSavedMessageTags: false,
hasBotPreviewItems: false,
isPremiumRequiredForStoryPosting: true,
personalChannel: personalChannel,
starsState: starsState,
tonState: tonState,
starsRevenueStatsState: nil,
starsRevenueStatsContext: nil,
revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: profileGiftsContext,
profileGiftsCollectionsContext: nil,
premiumGiftOptions: [],
webAppPermissions: nil,
savedMusicContext: nil,
savedMusicState: nil,
managedByBot: nil,
businessConnectedBot: businessConnectedBot
)
}
}
func peerInfoScreenData(
context: AccountContext,
peerId: PeerId,
strings: PresentationStrings,
dateTimeFormat: PresentationDateTimeFormat,
isSettings: Bool,
isMyProfile: Bool,
hintGroupInCommon: PeerId?,
existingRequestsContext: PeerInvitationImportersContext?,
existingProfileGiftsContext: ProfileGiftsContext?,
existingProfileGiftsCollectionsContext: ProfileGiftsCollectionsContext?,
chatLocation: ChatLocation,
chatLocationContextHolder: Atomic<ChatLocationContextHolder?>,
sharedMediaFromForumTopic: (EnginePeer.Id, Int64)?,
privacySettings: Signal<AccountPrivacySettings?, NoError>,
forceHasGifts: Bool,
switchToUpgradableGifts: Bool
) -> Signal<PeerInfoScreenData, NoError> {
return peerInfoScreenInputData(context: context, peerId: peerId, isSettings: isSettings)
|> mapToSignal { inputData -> Signal<PeerInfoScreenData, NoError> in
let wasUpgradedGroup = Atomic<Bool?>(value: nil)
switch inputData {
case .none, .settings:
return .single(PeerInfoScreenData(
peer: nil,
chatPeer: nil,
savedMessagesPeer: nil,
cachedData: nil,
status: nil,
peerNotificationSettings: nil,
threadNotificationSettings: nil,
globalNotificationSettings: nil,
isContact: false,
availablePanes: [],
groupsInCommon: nil,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: nil,
storyArchiveListContext: nil,
botPreviewStoryListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: nil,
requests: nil,
requestsContext: nil,
threadData: nil,
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: false,
hasSavedMessageTags: false,
hasBotPreviewItems: false,
isPremiumRequiredForStoryPosting: true,
personalChannel: nil,
starsState: nil,
tonState: nil,
starsRevenueStatsState: nil,
starsRevenueStatsContext: nil,
revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: nil,
profileGiftsCollectionsContext: nil,
premiumGiftOptions: [],
webAppPermissions: nil,
savedMusicContext: nil,
savedMusicState: nil,
managedByBot: nil,
businessConnectedBot: nil
))
case let .user(userPeerId, secretChatId, kind):
let groupsInCommon: GroupsInCommonContext?
if isMyProfile {
groupsInCommon = nil
} else if [.user, .bot].contains(kind) {
groupsInCommon = GroupsInCommonContext(account: context.account, peerId: userPeerId, hintGroupInCommon: hintGroupInCommon)
} else {
groupsInCommon = nil
}
let premiumGiftOptions: Signal<[PremiumGiftCodeOption], NoError>
let profileGiftsContext: ProfileGiftsContext?
let profileGiftsCollectionsContext: ProfileGiftsCollectionsContext?
if case .user = kind {
if isMyProfile || userPeerId != context.account.peerId {
profileGiftsContext = existingProfileGiftsContext ?? ProfileGiftsContext(account: context.account, peerId: userPeerId)
profileGiftsCollectionsContext = existingProfileGiftsCollectionsContext ?? ProfileGiftsCollectionsContext(account: context.account, peerId: userPeerId, allGiftsContext: profileGiftsContext)
if switchToUpgradableGifts {
profileGiftsContext?.updateFilter([.displayed, .hidden, .limitedUpgradable])
}
} else {
profileGiftsContext = nil
profileGiftsCollectionsContext = nil
}
premiumGiftOptions = .single([])
|> then(
context.engine.payments.premiumGiftCodeOptions(peerId: nil, onlyCached: true)
)
} else {
profileGiftsContext = nil
profileGiftsCollectionsContext = nil
premiumGiftOptions = .single([])
}
enum StatusInputData: Equatable {
case none
case presence(TelegramUserPresence)
case bot(subscriberCount: Int32?)
case support
}
let status = Signal<PeerInfoStatusData?, NoError> { subscriber in
class Manager {
var currentValue: TelegramUserPresence? = nil
var updateManager: QueueLocalObject<PeerPresenceStatusManager>? = nil
}
let manager = Atomic<Manager>(value: Manager())
let notify: () -> Void = {
let data = manager.with { manager -> PeerInfoStatusData? in
if let presence = manager.currentValue {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
let (text, isActivity) = stringAndActivityForUserPresence(strings: strings, dateTimeFormat: dateTimeFormat, presence: EnginePeer.Presence(presence), relativeTo: Int32(timestamp), expanded: true)
var isHiddenStatus = false
switch presence.status {
case .recently(let isHidden), .lastWeek(let isHidden), .lastMonth(let isHidden):
isHiddenStatus = isHidden
default:
break
}
return PeerInfoStatusData(text: text, isActivity: isActivity, isHiddenStatus: isHiddenStatus, key: nil)
} else {
return nil
}
}
subscriber.putNext(data)
}
let disposable = (context.account.viewTracker.peerView(userPeerId, updateData: false)
|> map { view -> StatusInputData in
guard let user = view.peers[userPeerId] as? TelegramUser else {
return .none
}
if user.id == context.account.peerId {
return .none
}
if user.isDeleted {
return .none
}
if user.flags.contains(.isSupport) {
return .support
}
if user.botInfo != nil {
return .bot(subscriberCount: user.subscriberCount)
}
guard let presence = view.peerPresences[userPeerId] as? TelegramUserPresence else {
return .none
}
return .presence(presence)
}
|> distinctUntilChanged).start(next: { inputData in
switch inputData {
case let .bot(subscriberCount):
if let subscriberCount, subscriberCount > 0 {
subscriber.putNext(PeerInfoStatusData(text: strings.Conversation_StatusBotSubscribers(subscriberCount), isActivity: false, key: nil))
} else {
subscriber.putNext(PeerInfoStatusData(text: strings.Bot_GenericBotStatus, isActivity: false, key: nil))
}
case .support:
subscriber.putNext(PeerInfoStatusData(text: strings.Bot_GenericSupportStatus, isActivity: false, key: nil))
default:
var presence: TelegramUserPresence?
if case let .presence(value) = inputData {
presence = value
}
let _ = manager.with { manager -> Void in
manager.currentValue = presence
if let presence = presence {
let updateManager: QueueLocalObject<PeerPresenceStatusManager>
if let current = manager.updateManager {
updateManager = current
} else {
updateManager = QueueLocalObject<PeerPresenceStatusManager>(queue: .mainQueue(), generate: {
return PeerPresenceStatusManager(update: {
notify()
})
})
}
updateManager.with { updateManager in
updateManager.reset(presence: EnginePeer.Presence(presence))
}
} else if let _ = manager.updateManager {
manager.updateManager = nil
}
}
notify()
}
})
return disposable
}
|> distinctUntilChanged
var secretChatKeyFingerprint: Signal<EngineSecretChatKeyFingerprint?, NoError> = .single(nil)
if let secretChatId {
secretChatKeyFingerprint = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.SecretChatKeyFingerprint(id: secretChatId))
}
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
let hasStoryArchive: Signal<Bool?, NoError>
var storyArchiveListContext: StoryListContext?
if isMyProfile {
let storyArchiveListContextValue = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: true, folderId: nil)
storyArchiveListContext = storyArchiveListContextValue
hasStoryArchive = storyArchiveListContextValue.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
} else {
hasStoryArchive = .single(false)
}
let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
}
|> distinctUntilChanged
let savedMessagesPeer: Signal<EnginePeer?, NoError>
if peerId == context.account.peerId, case let .replyThread(replyThreadMessage) = chatLocation {
savedMessagesPeer = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: PeerId(replyThreadMessage.threadId)))
} else {
savedMessagesPeer = .single(nil)
}
let hasSavedMessages: Signal<Bool, NoError>
let hasSavedMessagesChats: Signal<Bool, NoError>
if case .peer = chatLocation {
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|> map { count -> Bool in
if let count, count != 0 {
return true
} else {
return false
}
}
|> distinctUntilChanged
if peerId == context.account.peerId {
hasSavedMessagesChats = combineLatest(
context.engine.messages.savedMessagesHasPeersOtherThanSaved(),
context.engine.data.get(
TelegramEngine.EngineData.Item.Peer.DisplaySavedChatsAsTopics()
)
)
|> map { hasChats, displayAsTopics -> Bool in
return hasChats || displayAsTopics
}
|> distinctUntilChanged
} else {
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|> map { headPeerId -> Bool in
return headPeerId != nil
}
|> distinctUntilChanged
}
} else {
hasSavedMessages = .single(false)
hasSavedMessagesChats = .single(false)
}
let hasSavedMessageTags: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
if case .peer = chatLocation {
if peerId != context.account.peerId {
hasSavedMessageTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64())
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
} else {
hasSavedMessageTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: nil)
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
}
} else {
hasSavedMessageTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64())
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
}
} else {
hasSavedMessageTags = .single(false)
}
let recommendedBots: Signal<RecommendedBots?, NoError>
var botPreviewStoryListContext: StoryListContext?
let hasBotPreviewItems: Signal<Bool, NoError>
let starsRevenueContextAndState: Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError>
let revenueContextAndState: Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError>
let webAppPermissions: Signal<WebAppPermissionsState?, NoError>
if case .bot = kind {
recommendedBots = context.engine.peers.recommendedBots(peerId: userPeerId)
let botPreviewStoryListContextValue = BotPreviewStoryListContext(account: context.account, engine: context.engine, peerId: peerId, language: nil, assumeEmpty: false)
botPreviewStoryListContext = botPreviewStoryListContextValue
hasBotPreviewItems = botPreviewStoryListContextValue.state
|> map { state in
return !state.items.isEmpty
}
|> distinctUntilChanged
starsRevenueContextAndState = combineLatest(
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> distinctUntilChanged,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId))
|> distinctUntilChanged
)
|> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
var canViewStarsRevenue = canViewRevenue
if let peer, case let .user(user) = peer, let botInfo = user.botInfo, botInfo.flags.contains(.canEdit) || context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests {
canViewStarsRevenue = true
}
#if DEBUG
canViewStarsRevenue = peerId != context.account.peerId
#endif
guard canViewStarsRevenue else {
return .single((nil, nil))
}
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: false)
return starsRevenueStatsContext.state
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
return (starsRevenueStatsContext, state.stats)
}
}
revenueContextAndState = combineLatest(
context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> distinctUntilChanged,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId))
|> distinctUntilChanged
)
|> mapToSignal { peer, canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
var canViewRevenue = canViewRevenue
if let peer, case let .user(user) = peer, let _ = user.botInfo, context.sharedContext.applicationBindings.appBuildType == .internal || context.sharedContext.immediateExperimentalUISettings.devRequests {
canViewRevenue = true
}
#if DEBUG
canViewRevenue = peerId != context.account.peerId
#endif
guard canViewRevenue else {
return .single((nil, nil))
}
let revenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: true)
return revenueStatsContext.state
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
return (revenueStatsContext, state.stats)
}
}
webAppPermissions = context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: peerId))
|> mapToSignal { peer -> Signal<WebAppPermissionsState?, NoError> in
if let peer, case let .user(user) = peer, let _ = user.botInfo {
return webAppPermissionsState(context: context, peerId: peerId)
} else {
return .single(nil)
}
}
} else {
recommendedBots = .single(nil)
hasBotPreviewItems = .single(false)
starsRevenueContextAndState = .single((nil, nil))
revenueContextAndState = .single((nil, nil))
webAppPermissions = .single(nil)
}
let savedMusicContext = ProfileSavedMusicContext(account: context.account, peerId: peerId)
let businessConnectedBot: Signal<EnginePeer?, NoError>
if isMyProfile {
businessConnectedBot = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.BusinessConnectedBot(id: context.account.peerId)
)
|> map { bot -> EnginePeer.Id? in
return bot?.id
}
|> distinctUntilChanged
|> mapToSignal { botPeerId -> Signal<EnginePeer?, NoError> in
guard let botPeerId else {
return .single(nil)
}
return context.engine.data.get(TelegramEngine.EngineData.Item.Peer.Peer(id: botPeerId))
}
} else {
businessConnectedBot = .single(nil)
}
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: isMyProfile, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: sharedMediaFromForumTopic),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
secretChatKeyFingerprint,
status,
hasStories,
hasStoryArchive,
recommendedBots,
accountIsPremium,
savedMessagesPeer,
hasSavedMessagesChats,
hasSavedMessages,
hasSavedMessageTags,
hasBotPreviewItems,
peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: false),
privacySettings,
starsRevenueContextAndState,
revenueContextAndState,
premiumGiftOptions,
webAppPermissions,
savedMusicContext.state,
businessConnectedBot
)
|> map { peerView, availablePanes, globalNotificationSettings, encryptionKeyFingerprint, status, hasStories, hasStoryArchive, recommendedBots, accountIsPremium, savedMessagesPeer, hasSavedMessagesChats, hasSavedMessages, hasSavedMessageTags, hasBotPreviewItems, personalChannel, privacySettings, starsRevenueContextAndState, revenueContextAndState, premiumGiftOptions, webAppPermissions, savedMusicState, businessConnectedBot -> PeerInfoScreenData in
var availablePanes = availablePanes
if isMyProfile {
availablePanes?.insert(.stories, at: 0)
if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData {
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
availablePanes?.insert(.gifts, at: 1)
}
}
if let hasStoryArchive, hasStoryArchive {
availablePanes?.append(.storyArchive)
}
} else if let hasStories {
if hasStories, peerView.peers[peerView.peerId] is TelegramUser, peerView.peerId != context.account.peerId {
availablePanes?.insert(.stories, at: 0)
}
if availablePanes != nil, profileGiftsContext != nil, let cachedData = peerView.cachedData as? CachedUserData, peerView.peerId != context.account.peerId {
if let starGiftsCount = cachedData.starGiftsCount, starGiftsCount > 0 {
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
}
}
if availablePanes != nil, groupsInCommon != nil, let cachedData = peerView.cachedData as? CachedUserData {
if cachedData.commonGroupCount != 0 {
availablePanes?.append(.groupsInCommon)
}
}
if case .peer = chatLocation {
if peerId == context.account.peerId {
if hasSavedMessagesChats {
availablePanes?.insert(.savedMessagesChats, at: 0)
}
} else if hasSavedMessages && hasSavedMessagesChats {
if var availablePanesValue = availablePanes {
if let index = availablePanesValue.firstIndex(of: .media) {
availablePanesValue.insert(.savedMessages, at: index + 1)
} else {
availablePanesValue.insert(.savedMessages, at: 0)
}
availablePanes = availablePanesValue
}
}
if let user = peerView.peers[peerView.peerId] as? TelegramUser, let botInfo = user.botInfo, botInfo.flags.contains(.hasWebApp), botInfo.flags.contains(.canEdit) {
availablePanes?.insert(.botPreview, at: 0)
} else if let cachedData = peerView.cachedData as? CachedUserData, let botPreview = cachedData.botPreview, !botPreview.items.isEmpty {
availablePanes?.insert(.botPreview, at: 0)
}
}
if let recommendedBots, recommendedBots.count > 0 {
availablePanes?.append(.similarBots)
}
} else {
availablePanes = nil
}
if var currentAvailablePanes = availablePanes, let cachedData = peerView.cachedData as? CachedUserData, let mainProfileTab = cachedData.mainProfileTab {
let mainTabKey = PeerInfoPaneKey(tab: mainProfileTab)
if currentAvailablePanes.contains(mainTabKey) && currentAvailablePanes.first != mainTabKey {
currentAvailablePanes = currentAvailablePanes.filter { $0 != mainTabKey }
currentAvailablePanes.insert(mainTabKey, at: 0)
availablePanes = currentAvailablePanes
}
}
let peer = peerView.peers[userPeerId]
var managedByBot: EnginePeer?
if let cachedData = peerView.cachedData as? CachedUserData, let botManagerId = cachedData.botManagerId {
if let peer = peerView.peers[botManagerId] {
managedByBot = EnginePeer(peer)
}
}
var globalSettings: TelegramGlobalSettings?
if let privacySettings {
globalSettings = TelegramGlobalSettings(
suggestPhoneNumberConfirmation: false,
suggestPasswordConfirmation: false,
suggestPasswordSetup: false,
premiumGracePeriod: false,
accountsAndPeers: [],
activeSessionsContext: nil,
webSessionsContext: nil,
otherSessionsCount: nil,
proxySettings: ProxySettings(enabled: false, servers: [], activeServer: nil, useForCalls: false),
notificationAuthorizationStatus: .notDetermined,
notificationWarningSuppressed: false,
notificationExceptions: nil,
inAppNotificationSettings: InAppNotificationSettings(playSounds: false, vibrate: false, displayPreviews: false, totalUnreadCountDisplayStyle: .filtered, totalUnreadCountDisplayCategory: .chats, totalUnreadCountIncludeTags: .all, displayNameOnLockscreen: false, displayNotificationsFromAllAccounts: false, customSound: nil),
privacySettings: privacySettings,
unreadTrendingStickerPacks: 0,
archivedStickerPacks: nil,
userLimits: context.userLimits,
bots: [],
hasPassport: false,
hasWatchApp: false,
enableQRLogin: false)
}
return PeerInfoScreenData(
peer: peer.flatMap(EnginePeer.init),
chatPeer: peerView.peers[peerId].flatMap(EnginePeer.init),
savedMessagesPeer: savedMessagesPeer,
cachedData: peerView.cachedData,
status: status,
peerNotificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
threadNotificationSettings: nil,
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes ?? [],
groupsInCommon: groupsInCommon,
linkedDiscussionPeer: nil,
linkedMonoforumPeer: nil,
members: nil,
storyListContext: storyListContext,
storyArchiveListContext: storyArchiveListContext,
botPreviewStoryListContext: botPreviewStoryListContext,
encryptionKeyFingerprint: encryptionKeyFingerprint,
globalSettings: globalSettings,
invitations: nil,
requests: nil,
requestsContext: nil,
threadData: nil,
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags,
hasBotPreviewItems: hasBotPreviewItems,
isPremiumRequiredForStoryPosting: false,
personalChannel: personalChannel,
starsState: nil,
tonState: nil,
starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: revenueContextAndState.1,
revenueStatsContext: revenueContextAndState.0,
profileGiftsContext: profileGiftsContext,
profileGiftsCollectionsContext: profileGiftsCollectionsContext,
premiumGiftOptions: premiumGiftOptions,
webAppPermissions: webAppPermissions,
savedMusicContext: savedMusicContext,
savedMusicState: savedMusicState,
managedByBot: managedByBot,
businessConnectedBot: businessConnectedBot
)
}
case .channel:
let status = context.account.viewTracker.peerView(peerId, updateData: false)
|> map { peerView -> PeerInfoStatusData? in
guard let _ = peerView.peers[peerId] as? TelegramChannel else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false, key: nil)
}
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = cachedChannelData.participantsSummary.memberCount, memberCount != 0 {
return PeerInfoStatusData(text: strings.Conversation_StatusSubscribers(memberCount), isActivity: false, key: nil)
} else {
return PeerInfoStatusData(text: strings.Channel_Status, isActivity: false, key: nil)
}
}
|> distinctUntilChanged
let invitationsContextPromise = Promise<PeerExportedInvitationsContext?>(nil)
let invitationsStatePromise = Promise<PeerExportedInvitationsState?>(nil)
let requestsContextPromise = Promise<PeerInvitationImportersContext?>(nil)
let requestsStatePromise = Promise<PeerInvitationImportersState?>(nil)
let storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
let hasStories: Signal<Bool?, NoError> = storyListContext.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
}
|> distinctUntilChanged
let hasSavedMessages: Signal<Bool, NoError>
let hasSavedMessagesChats: Signal<Bool, NoError>
if case .peer = chatLocation {
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|> map { count -> Bool in
if let count, count != 0 {
return true
} else {
return false
}
}
|> distinctUntilChanged
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|> map { headPeerId -> Bool in
return headPeerId != nil
}
|> distinctUntilChanged
} else {
hasSavedMessages = .single(false)
hasSavedMessagesChats = .single(false)
}
let hasSavedMessageTags: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
hasSavedMessageTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64())
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
} else {
hasSavedMessageTags = .single(false)
}
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
let starsRevenueContextAndState = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.CanViewStarsRevenue(id: peerId)
)
|> distinctUntilChanged
|> mapToSignal { canViewStarsRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
guard canViewStarsRevenue else {
return .single((nil, nil))
}
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: false)
return starsRevenueStatsContext.state
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
return (starsRevenueStatsContext, state.stats)
}
}
let revenueContextAndState = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.CanViewRevenue(id: peerId)
)
|> distinctUntilChanged
|> mapToSignal { canViewRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
guard canViewRevenue else {
return .single((nil, nil))
}
let revenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: true)
return revenueStatsContext.state
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
return (revenueStatsContext, state.stats)
}
}
let profileGiftsContext = ProfileGiftsContext(account: context.account, peerId: peerId)
let profileGiftsCollectionsContext = ProfileGiftsCollectionsContext(account: context.account, peerId: peerId, allGiftsContext: profileGiftsContext)
let personalChannel = peerInfoPersonalOrLinkedChannel(context: context, peerId: peerId, isSettings: false)
return combineLatest(
context.account.viewTracker.peerView(peerId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: peerId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: sharedMediaFromForumTopic),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
status,
invitationsContextPromise.get(),
invitationsStatePromise.get(),
requestsContextPromise.get(),
requestsStatePromise.get(),
hasStories,
accountIsPremium,
context.engine.peers.recommendedChannels(peerId: peerId),
hasSavedMessages,
hasSavedMessagesChats,
hasSavedMessageTags,
isPremiumRequiredForStoryPosting,
starsRevenueContextAndState,
revenueContextAndState,
profileGiftsContext.state,
personalChannel
)
|> map { peerView, availablePanes, globalNotificationSettings, status, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, accountIsPremium, recommendedChannels, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState, revenueContextAndState, profileGiftsState, personalChannel -> PeerInfoScreenData in
var availablePanes = availablePanes
if let hasStories {
if hasStories {
availablePanes?.insert(.stories, at: 0)
}
if let recommendedChannels, !recommendedChannels.channels.isEmpty {
availablePanes?.append(.similarChannels)
}
if case .peer = chatLocation {
if hasSavedMessages, hasSavedMessagesChats, var availablePanesValue = availablePanes {
if let index = availablePanesValue.firstIndex(of: .media) {
availablePanesValue.insert(.savedMessages, at: index + 1)
} else if let index = availablePanesValue.firstIndex(of: .stories) {
availablePanesValue.insert(.savedMessages, at: index + 1)
} else {
availablePanesValue.insert(.savedMessages, at: 0)
}
availablePanes = availablePanesValue
}
}
if availablePanes != nil, let cachedData = peerView.cachedData as? CachedChannelData {
if (cachedData.starGiftsCount ?? 0) > 0 || (profileGiftsState.count ?? 0) > 0 || forceHasGifts {
availablePanes?.insert(.gifts, at: hasStories ? 1 : 0)
}
}
} else {
availablePanes = nil
}
if var currentAvailablePanes = availablePanes, let cachedData = peerView.cachedData as? CachedChannelData, let mainProfileTab = cachedData.mainProfileTab {
let mainTabKey = PeerInfoPaneKey(tab: mainProfileTab)
if currentAvailablePanes.contains(mainTabKey) && currentAvailablePanes.first != mainTabKey {
currentAvailablePanes = currentAvailablePanes.filter { $0 != mainTabKey }
currentAvailablePanes.insert(mainTabKey, at: 0)
availablePanes = currentAvailablePanes
}
}
var discussionPeer: EnginePeer?
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = EnginePeer(peer)
}
var monoforumPeer: EnginePeer?
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
monoforumPeer = peerView.peers[linkedMonoforumId].flatMap(EnginePeer.init)
}
var canManageInvitations = false
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, let _ = peerView.cachedData as? CachedChannelData, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
canManageInvitations = true
}
if currentInvitationsContext == nil {
if canManageInvitations {
let invitationsContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
}
if currentRequestsContext == nil {
if canManageInvitations {
let requestsContext = existingRequestsContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
requestsContextPromise.set(.single(requestsContext))
requestsStatePromise.set(requestsContext.state |> map(Optional.init))
}
}
return PeerInfoScreenData(
peer: peerView.peers[peerId].flatMap(EnginePeer.init),
chatPeer: peerView.peers[peerId].flatMap(EnginePeer.init),
savedMessagesPeer: nil,
cachedData: peerView.cachedData,
status: status,
peerNotificationSettings: peerView.notificationSettings as? TelegramPeerNotificationSettings,
threadNotificationSettings: nil,
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes ?? [],
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
linkedMonoforumPeer: monoforumPeer,
members: nil,
storyListContext: storyListContext,
storyArchiveListContext: nil,
botPreviewStoryListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: invitations,
requests: requests,
requestsContext: currentRequestsContext,
threadData: nil,
appConfiguration: nil,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags,
hasBotPreviewItems: false,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
personalChannel: personalChannel,
starsState: nil,
tonState: nil,
starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: revenueContextAndState.1,
revenueStatsContext: revenueContextAndState.0,
profileGiftsContext: profileGiftsContext,
profileGiftsCollectionsContext: profileGiftsCollectionsContext,
premiumGiftOptions: [],
webAppPermissions: nil,
savedMusicContext: nil,
savedMusicState: nil,
managedByBot: nil,
businessConnectedBot: nil
)
}
case let .group(groupId):
var onlineMemberCount: Signal<(total: Int32?, recent: Int32?), NoError> = .single((nil, nil))
if peerId.namespace == Namespaces.Peer.CloudChannel {
onlineMemberCount = context.account.viewTracker.peerView(groupId, updateData: false)
|> 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))
}
}
}
let status = combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: false),
onlineMemberCount
)
|> map { peerView, memberCountData -> PeerInfoStatusData? in
let (preciseTotalMemberCount, onlineMemberCount) = memberCountData
if let cachedChannelData = peerView.cachedData as? CachedChannelData, let memberCount = preciseTotalMemberCount ?? cachedChannelData.participantsSummary.memberCount {
if let onlineMemberCount, onlineMemberCount > 1 {
var string = ""
string.append("\(strings.Conversation_StatusMembers(Int32(memberCount))), ")
string.append(strings.Conversation_StatusOnline(Int32(onlineMemberCount)))
return PeerInfoStatusData(text: string, isActivity: false, key: nil)
} else if memberCount > 0 {
return PeerInfoStatusData(text: strings.Conversation_StatusMembers(Int32(memberCount)), isActivity: false, key: nil)
}
} else if let group = peerView.peers[groupId] as? TelegramGroup, let cachedGroupData = peerView.cachedData as? CachedGroupData {
var onlineCount = 0
if let participants = cachedGroupData.participants {
let timestamp = CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970
for participant in participants.participants {
if let presence = peerView.peerPresences[participant.peerId] as? TelegramUserPresence {
let relativeStatus = relativeUserPresenceStatus(EnginePeer.Presence(presence), relativeTo: Int32(timestamp))
switch relativeStatus {
case .online:
onlineCount += 1
default:
break
}
}
}
}
if onlineCount > 1 {
var string = ""
string.append("\(strings.Conversation_StatusMembers(Int32(group.participantCount))), ")
string.append(strings.Conversation_StatusOnline(Int32(onlineCount)))
return PeerInfoStatusData(text: string, isActivity: false, key: nil)
} else {
return PeerInfoStatusData(text: strings.Conversation_StatusMembers(Int32(group.participantCount)), isActivity: false, key: nil)
}
}
return PeerInfoStatusData(text: strings.Group_Status, isActivity: false, key: nil)
}
|> distinctUntilChanged
let membersData: Signal<PeerInfoMembersData?, NoError>
if case .peer = chatLocation {
let membersContext = PeerInfoMembersContext(context: context, peerId: groupId)
membersData = combineLatest(membersContext.state, context.account.viewTracker.peerView(groupId, updateData: false))
|> map { state, view -> PeerInfoMembersData? in
if state.members.count > 5 {
return .longList(membersContext)
} else {
return .shortList(membersContext: membersContext, members: state.members)
}
}
|> distinctUntilChanged
} else {
membersData = .single(nil)
}
let invitationsContextPromise = Promise<PeerExportedInvitationsContext?>(nil)
let invitationsStatePromise = Promise<PeerExportedInvitationsState?>(nil)
let requestsContextPromise = Promise<PeerInvitationImportersContext?>(nil)
let requestsStatePromise = Promise<PeerInvitationImportersState?>(nil)
let storyListContext: StoryListContext?
let hasStories: Signal<Bool?, NoError>
if peerId.namespace == Namespaces.Peer.CloudChannel {
storyListContext = PeerStoryListContext(account: context.account, peerId: peerId, isArchived: false, folderId: nil)
hasStories = storyListContext!.state
|> map { state -> Bool? in
if !state.hasCache {
return nil
}
return !state.items.isEmpty
}
|> distinctUntilChanged
} else {
storyListContext = nil
hasStories = .single(false)
}
let threadData: Signal<MessageHistoryThreadData?, NoError>
if case let .replyThread(message) = chatLocation {
let threadId = message.threadId
let viewKey: PostboxViewKey = .messageHistoryThreadInfo(peerId: peerId, threadId: threadId)
threadData = context.account.postbox.combinedView(keys: [viewKey])
|> map { views -> MessageHistoryThreadData? in
guard let view = views.views[viewKey] as? MessageHistoryThreadInfoView else {
return nil
}
return view.info?.data.get(MessageHistoryThreadData.self)
}
} else {
threadData = .single(nil)
}
let accountIsPremium = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Peer.Peer(id: context.account.peerId))
|> map { peer -> Bool in
return peer?.isPremium ?? false
}
|> distinctUntilChanged
let hasSavedMessages: Signal<Bool, NoError>
let hasSavedMessagesChats: Signal<Bool, NoError>
if case .peer = chatLocation {
hasSavedMessages = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Messages.MessageCount(peerId: context.account.peerId, threadId: peerId.toInt64(), tag: MessageTags()))
|> map { count -> Bool in
if let count, count != 0 {
return true
} else {
return false
}
}
|> distinctUntilChanged
hasSavedMessagesChats = context.engine.messages.savedMessagesPeerListHead()
|> map { headPeerId -> Bool in
return headPeerId != nil
}
|> distinctUntilChanged
} else {
hasSavedMessages = .single(false)
hasSavedMessagesChats = .single(false)
}
let hasSavedMessageTags: Signal<Bool, NoError>
if let peerId = chatLocation.peerId {
hasSavedMessageTags = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Messages.SavedMessageTagStats(peerId: context.account.peerId, threadId: peerId.toInt64())
)
|> map { tags -> Bool in
return !tags.isEmpty
}
|> distinctUntilChanged
} else {
hasSavedMessageTags = .single(false)
}
let starsRevenueContextAndState = context.engine.data.subscribe(
TelegramEngine.EngineData.Item.Peer.CanViewStarsRevenue(id: peerId)
)
|> distinctUntilChanged
|> mapToSignal { canViewStarsRevenue -> Signal<(StarsRevenueStatsContext?, StarsRevenueStats?), NoError> in
guard canViewStarsRevenue else {
return .single((nil, nil))
}
let starsRevenueStatsContext = StarsRevenueStatsContext(account: context.account, peerId: peerId, ton: false)
return starsRevenueStatsContext.state
|> map { state -> (StarsRevenueStatsContext?, StarsRevenueStats?) in
return (starsRevenueStatsContext, state.stats)
}
}
let isPremiumRequiredForStoryPosting: Signal<Bool, NoError> = isPremiumRequiredForStoryPosting(context: context)
return combineLatest(queue: .mainQueue(),
context.account.viewTracker.peerView(groupId, updateData: true),
peerInfoAvailableMediaPanes(context: context, peerId: groupId, chatLocation: chatLocation, isMyProfile: false, chatLocationContextHolder: chatLocationContextHolder, sharedMediaFromForumTopic: sharedMediaFromForumTopic),
context.engine.data.subscribe(TelegramEngine.EngineData.Item.NotificationSettings.Global()),
status,
membersData,
invitationsContextPromise.get(),
invitationsStatePromise.get(),
requestsContextPromise.get(),
requestsStatePromise.get(),
hasStories,
threadData,
context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.ApplicationSpecificPreference(key: PreferencesKeys.appConfiguration)),
accountIsPremium,
hasSavedMessages,
hasSavedMessagesChats,
hasSavedMessageTags,
isPremiumRequiredForStoryPosting,
starsRevenueContextAndState
)
|> mapToSignal { peerView, availablePanes, globalNotificationSettings, status, membersData, currentInvitationsContext, invitations, currentRequestsContext, requests, hasStories, threadData, preferencesView, accountIsPremium, hasSavedMessages, hasSavedMessagesChats, hasSavedMessageTags, isPremiumRequiredForStoryPosting, starsRevenueContextAndState -> Signal<PeerInfoScreenData, NoError> in
var discussionPeer: EnginePeer?
if case let .known(maybeLinkedDiscussionPeerId) = (peerView.cachedData as? CachedChannelData)?.linkedDiscussionPeerId, let linkedDiscussionPeerId = maybeLinkedDiscussionPeerId, let peer = peerView.peers[linkedDiscussionPeerId] {
discussionPeer = EnginePeer(peer)
}
var monoforumPeer: EnginePeer?
if let channel = peerViewMainPeer(peerView) as? TelegramChannel, case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), let linkedMonoforumId = channel.linkedMonoforumId {
monoforumPeer = peerView.peers[linkedMonoforumId].flatMap(EnginePeer.init)
}
var availablePanes = availablePanes
if let membersData = membersData, case .longList = membersData {
if availablePanes != nil {
availablePanes?.insert(.members, at: 0)
} else {
availablePanes = [.members]
}
}
if let hasStories {
if hasStories {
availablePanes?.insert(.stories, at: 0)
}
if case .peer = chatLocation {
if hasSavedMessages, hasSavedMessagesChats, var availablePanesValue = availablePanes {
if let index = availablePanesValue.firstIndex(of: .media) {
availablePanesValue.insert(.savedMessages, at: index + 1)
} else if let index = availablePanesValue.firstIndex(of: .stories) {
availablePanesValue.insert(.savedMessages, at: index + 1)
} else {
availablePanesValue.insert(.savedMessages, at: 0)
}
availablePanes = availablePanesValue
}
}
}
var canManageInvitations = false
if let group = peerViewMainPeer(peerView) as? TelegramGroup {
let previousValue = wasUpgradedGroup.swap(group.migrationReference != nil)
if group.migrationReference != nil, let previousValue, !previousValue {
return .never()
}
if case .creator = group.role {
canManageInvitations = true
} else if case let .admin(rights, _) = group.role, rights.rights.contains(.canInviteUsers) {
canManageInvitations = true
}
} else if let channel = peerViewMainPeer(peerView) as? TelegramChannel, channel.flags.contains(.isCreator) || (channel.adminRights?.rights.contains(.canInviteUsers) == true) {
canManageInvitations = true
}
if currentInvitationsContext == nil {
if canManageInvitations {
let invitationsContext = context.engine.peers.peerExportedInvitations(peerId: peerId, adminId: nil, revoked: false, forceUpdate: true)
invitationsContextPromise.set(.single(invitationsContext))
invitationsStatePromise.set(invitationsContext.state |> map(Optional.init))
}
}
if currentRequestsContext == nil {
if canManageInvitations {
let requestsContext = existingRequestsContext ?? context.engine.peers.peerInvitationImporters(peerId: peerId, subject: .requests(query: nil))
requestsContextPromise.set(.single(requestsContext))
requestsStatePromise.set(requestsContext.state |> map(Optional.init))
}
}
let peerNotificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings
let threadNotificationSettings = threadData?.notificationSettings
let appConfiguration: AppConfiguration = preferencesView?.get(AppConfiguration.self) ?? .defaultValue
return .single(PeerInfoScreenData(
peer: peerView.peers[groupId].flatMap(EnginePeer.init),
chatPeer: peerView.peers[groupId].flatMap(EnginePeer.init),
savedMessagesPeer: nil,
cachedData: peerView.cachedData,
status: status,
peerNotificationSettings: peerNotificationSettings,
threadNotificationSettings: threadNotificationSettings,
globalNotificationSettings: globalNotificationSettings,
isContact: peerView.peerIsContact,
availablePanes: availablePanes ?? [],
groupsInCommon: nil,
linkedDiscussionPeer: discussionPeer,
linkedMonoforumPeer: monoforumPeer,
members: membersData,
storyListContext: storyListContext,
storyArchiveListContext: nil,
botPreviewStoryListContext: nil,
encryptionKeyFingerprint: nil,
globalSettings: nil,
invitations: invitations,
requests: requests,
requestsContext: currentRequestsContext,
threadData: threadData,
appConfiguration: appConfiguration,
isPowerSavingEnabled: nil,
accountIsPremium: accountIsPremium,
hasSavedMessageTags: hasSavedMessageTags,
hasBotPreviewItems: false,
isPremiumRequiredForStoryPosting: isPremiumRequiredForStoryPosting,
personalChannel: nil,
starsState: nil,
tonState: nil,
starsRevenueStatsState: starsRevenueContextAndState.1,
starsRevenueStatsContext: starsRevenueContextAndState.0,
revenueStatsState: nil,
revenueStatsContext: nil,
profileGiftsContext: nil,
profileGiftsCollectionsContext: nil,
premiumGiftOptions: [],
webAppPermissions: nil,
savedMusicContext: nil,
savedMusicState: nil,
managedByBot: nil,
businessConnectedBot: nil
))
}
}
}
}
func peerInfoIsCopyProtected(data: PeerInfoScreenData) -> Bool {
var isCopyProtected = false
if let cachedUserData = data.cachedData as? CachedUserData, cachedUserData.flags.contains(.copyProtectionEnabled) || cachedUserData.flags.contains(.myCopyProtectionEnabled) {
isCopyProtected = true
} else if let peer = data.peer, peer.isCopyProtectionEnabled {
isCopyProtected = true
}
return isCopyProtected
}
func canEditPeerInfo(context: AccountContext, peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?) -> Bool {
if context.account.peerId == peer?.id {
return true
}
if case let .user(user) = peer, let botInfo = user.botInfo {
return botInfo.flags.contains(.canEdit)
} else if case let .channel(channel) = peer {
if let threadData = threadData {
if chatLocation.threadId == 1 {
return false
}
if channel.hasPermission(.manageTopics) {
return true
}
if threadData.author == context.account.peerId {
return true
}
} else {
if channel.hasPermission(.changeInfo) {
return true
}
}
} else if case let .legacyGroup(group) = peer {
switch group.role {
case .admin, .creator:
return true
case .member:
break
}
if !group.hasBannedPermission(.banChangeInfo) {
return true
}
}
return false
}
struct PeerInfoMemberActions: OptionSet {
var rawValue: Int32
init(rawValue: Int32) {
self.rawValue = rawValue
}
static let restrict = PeerInfoMemberActions(rawValue: 1 << 0)
static let promote = PeerInfoMemberActions(rawValue: 1 << 1)
static let logout = PeerInfoMemberActions(rawValue: 1 << 2)
static let editRank = PeerInfoMemberActions(rawValue: 1 << 3)
}
func availableActionsForMemberOfPeer(accountPeerId: PeerId, peer: EnginePeer?, member: PeerInfoMember) -> PeerInfoMemberActions {
var result: PeerInfoMemberActions = []
if peer == nil {
result.insert(.logout)
} else if member.id == accountPeerId {
if case let .channel(channel) = peer {
if channel.hasPermission(.editRank) {
result.insert(.editRank)
}
} else if case let .legacyGroup(group) = peer {
if !group.hasBannedPermission(.banEditRank) {
result.insert(.editRank)
}
}
} else {
if case let .channel(channel) = peer {
if channel.flags.contains(.isCreator) {
if !channel.flags.contains(.isGigagroup) {
result.insert(.restrict)
}
result.insert(.promote)
result.insert(.editRank)
} else {
switch member {
case let .channelMember(channelMember, _):
switch channelMember.participant {
case .creator:
break
case let .member(_, _, adminInfo, _, _, _):
if let adminInfo = adminInfo {
if adminInfo.promotedBy == accountPeerId {
if !channel.flags.contains(.isGigagroup) {
result.insert(.restrict)
}
if channel.hasPermission(.addAdmins) {
result.insert(.promote)
}
if channel.hasPermission(.manageRanks) {
result.insert(.editRank)
}
}
} else {
if channel.hasPermission(.banMembers) && !channel.flags.contains(.isGigagroup) {
result.insert(.restrict)
}
if channel.hasPermission(.addAdmins) {
result.insert(.promote)
}
if channel.hasPermission(.manageRanks) {
result.insert(.editRank)
}
}
}
case .legacyGroupMember:
break
case .account:
break
}
}
} else if case let .legacyGroup(group) = peer {
switch group.role {
case .creator:
result.insert(.restrict)
result.insert(.promote)
result.insert(.editRank)
case .admin:
switch member {
case let .legacyGroupMember(_, role, invitedBy, _, _, _):
if case .member = role {
result.insert(.restrict)
if invitedBy == accountPeerId {
result.insert(.promote)
result.insert(.editRank)
}
}
case .channelMember:
break
case .account:
break
}
case .member:
switch member {
case let .legacyGroupMember(_, role, invitedBy, _, _, _):
if case .member = role, invitedBy == accountPeerId {
result.insert(.restrict)
result.insert(.editRank)
}
case .channelMember:
break
case .account:
break
}
}
}
}
return result
}
func peerInfoHeaderButtonIsHiddenWhileExpanded(buttonKey: PeerInfoHeaderButtonKey, isOpenedFromChat: Bool) -> Bool {
var hiddenWhileExpanded = false
if isOpenedFromChat {
switch buttonKey {
case .message, .search, .videoCall, .addMember, .leave, .discussion:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false
}
} else {
switch buttonKey {
case .search, .call, .videoCall, .addMember, .leave, .discussion:
hiddenWhileExpanded = true
default:
hiddenWhileExpanded = false
}
}
return hiddenWhileExpanded
}
func peerInfoHeaderActionButtons(peer: EnginePeer?, isSecretChat: Bool, isContact: Bool) -> [PeerInfoHeaderButtonKey] {
var result: [PeerInfoHeaderButtonKey] = []
if !isContact && !isSecretChat, case let .user(user) = peer, user.botInfo == nil {
result = [.message, .addContact]
}
if "".isEmpty {
return []
}
return result
}
func peerInfoHeaderButtons(peer: EnginePeer?, cachedData: CachedPeerData?, isOpenedFromChat: Bool, isExpanded: Bool, videoCallsEnabled: Bool, isSecretChat: Bool, isContact: Bool, threadInfo: EngineMessageHistoryThread.Info?) -> [PeerInfoHeaderButtonKey] {
var result: [PeerInfoHeaderButtonKey] = []
if case let .user(user) = peer {
if !isOpenedFromChat {
result.append(.message)
}
var callsAvailable = false
var videoCallsAvailable = false
if !user.isDeleted, user.botInfo == nil, !user.flags.contains(.isSupport) {
if let cachedUserData = cachedData as? CachedUserData {
callsAvailable = cachedUserData.voiceCallsAvailable
videoCallsAvailable = cachedUserData.videoCallsAvailable
} else {
callsAvailable = true
videoCallsAvailable = true
}
}
if callsAvailable {
result.append(.call)
if videoCallsEnabled && videoCallsAvailable {
result.append(.videoCall)
}
}
result.append(.mute)
if isOpenedFromChat {
result.append(.search)
}
if user.botInfo != nil, let cachedData = cachedData as? CachedUserData, !cachedData.isBlocked {
result.append(.stop)
}
if (isSecretChat && !isContact) || user.flags.contains(.isSupport) {
} else {
result.append(.more)
}
} else if case let .channel(channel) = peer {
if let _ = threadInfo {
result.append(.mute)
result.append(.search)
} else {
let hasVoiceChat = channel.flags.contains(.hasVoiceChat)
let canStartVoiceChat = !hasVoiceChat && (channel.flags.contains(.isCreator) || channel.hasPermission(.manageCalls))
let canManage = channel.flags.contains(.isCreator) || channel.adminRights != nil
let hasDiscussion: Bool
switch channel.info {
case let .broadcast(info):
hasDiscussion = info.flags.contains(.hasDiscussionGroup)
case .group:
hasDiscussion = false
}
let canLeave: Bool
switch channel.participationStatus {
case .member:
canLeave = true
default:
canLeave = false
}
let canViewStats: Bool
if let cachedChannelData = cachedData as? CachedChannelData {
canViewStats = cachedChannelData.flags.contains(.canViewStats)
} else {
canViewStats = false
}
if hasVoiceChat || canStartVoiceChat {
result.append(.voiceChat)
}
if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) {
result.append(.message)
}
result.append(.mute)
if case let .broadcast(info) = channel.info, info.flags.contains(.hasMonoforum), !channel.hasPermission(.manageDirect) {
} else if hasDiscussion {
result.append(.discussion)
}
result.append(.search)
if canLeave {
result.append(.leave)
}
var canReport = true
if channel.adminRights != nil || channel.flags.contains(.isCreator) {
canReport = false
}
var hasMore = false
if canReport || canViewStats {
hasMore = true
result.append(.more)
}
if hasDiscussion && isExpanded && result.count >= 5 {
result.removeAll(where: { $0 == .search })
if !hasMore {
hasMore = true
result.append(.more)
}
}
if canLeave && isExpanded && (canManage || result.count >= 5) {
result.removeAll(where: { $0 == .leave })
if !hasMore {
hasMore = true
result.append(.more)
}
}
}
} else if case let .legacyGroup(group) = peer {
let hasVoiceChat = group.flags.contains(.hasVoiceChat)
let canStartVoiceChat: Bool
if !hasVoiceChat {
if case .creator = group.role {
canStartVoiceChat = true
} else if case let .admin(rights, _) = group.role, rights.rights.contains(.canManageCalls) {
canStartVoiceChat = true
} else {
canStartVoiceChat = false
}
} else {
canStartVoiceChat = false
}
if hasVoiceChat || canStartVoiceChat {
result.append(.voiceChat)
}
result.append(.mute)
result.append(.search)
result.append(.more)
}
return result
}
func peerInfoCanEdit(peer: EnginePeer?, chatLocation: ChatLocation, threadData: MessageHistoryThreadData?, cachedData: CachedPeerData?, isContact: Bool?) -> Bool {
if case let .user(user) = peer {
if user.isDeleted {
return false
}
if let botInfo = user.botInfo {
return botInfo.flags.contains(.canEdit)
}
if let isContact = isContact, !isContact {
return false
}
return true
} else if case let .channel(peer) = peer {
if peer.isForumOrMonoForum, let threadData = threadData {
if peer.flags.contains(.isCreator) {
return true
} else if threadData.isOwnedByMe {
return true
} else if peer.hasPermission(.manageTopics) {
return true
} else {
return false
}
} else {
if peer.flags.contains(.isCreator) {
return true
} else if peer.hasPermission(.changeInfo) {
return true
} else if let _ = peer.adminRights {
return true
}
return false
}
} else if case let .legacyGroup(peer) = peer {
if case .creator = peer.role {
return true
} else if case let .admin(rights, _) = peer.role {
if rights.rights.contains(.canAddAdmins) || rights.rights.contains(.canBanUsers) || rights.rights.contains(.canChangeInfo) || rights.rights.contains(.canInviteUsers) {
return true
}
return false
} else if !peer.hasBannedPermission(.banChangeInfo) {
return true
}
}
return false
}
func peerInfoIsChatMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, threadNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
func isPeerMuted(peer: EnginePeer?, peerNotificationSettings: TelegramPeerNotificationSettings?, globalNotificationSettings: EngineGlobalNotificationSettings?) -> Bool {
var peerIsMuted = false
if let peerNotificationSettings {
if case .muted = peerNotificationSettings.muteState {
peerIsMuted = true
} else if case .default = peerNotificationSettings.muteState, let globalNotificationSettings {
if let peer {
if case .user = peer {
peerIsMuted = !globalNotificationSettings.privateChats.enabled
} else if case .legacyGroup = peer {
peerIsMuted = !globalNotificationSettings.groupChats.enabled
} else if case let .channel(channel) = peer {
switch channel.info {
case .group:
peerIsMuted = !globalNotificationSettings.groupChats.enabled
case .broadcast:
peerIsMuted = !globalNotificationSettings.channels.enabled
}
}
}
}
}
return peerIsMuted
}
var chatIsMuted = false
if let threadNotificationSettings {
if case .muted = threadNotificationSettings.muteState {
chatIsMuted = true
} else if let peerNotificationSettings {
chatIsMuted = isPeerMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, globalNotificationSettings: globalNotificationSettings)
}
} else {
chatIsMuted = isPeerMuted(peer: peer, peerNotificationSettings: peerNotificationSettings, globalNotificationSettings: globalNotificationSettings)
}
return chatIsMuted
}
private var isPremiumRequired: Bool?
private func isPremiumRequiredForStoryPosting(context: AccountContext) -> Signal<Bool, NoError> {
if let isPremiumRequired {
return .single(isPremiumRequired)
}
return .single(true)
|> then(
context.engine.messages.checkStoriesUploadAvailability(target: .myStories)
|> deliverOnMainQueue
|> map { status -> Bool in
if case .premiumRequired = status {
return true
} else {
return false
}
} |> afterNext { value in
isPremiumRequired = value
}
)
}