From 4336fa0d05e8ece70018b34e6eaf01e5831c99d2 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Tue, 17 Nov 2020 20:31:25 +0400 Subject: [PATCH] Group chats update --- .../Sources/PresentationCallManager.swift | 55 +- .../Sources/ItemListPeerItem.swift | 21 +- .../Sources/ChannelAdminsController.swift | 2 +- .../Sources/ChannelBlacklistController.swift | 2 +- ...hannelDiscussionGroupSetupController.swift | 2 +- .../Sources/ChannelMembersController.swift | 2 +- .../ChannelPermissionsController.swift | 2 +- .../Sources/ChannelVisibilityController.swift | 2 +- .../Sources/PeersNearbyController.swift | 10 +- .../SettingsUI/Sources/DebugController.swift | 16 - .../NotificationExceptionControllerNode.swift | 2 +- ...ectivePrivacySettingsPeersController.swift | 8 +- .../Sources/GroupStatsController.swift | 6 +- .../Sources/MessageStatsController.swift | 2 +- .../SyncCore/Sources/CachedChannelData.swift | 68 +- .../Sources/GroupCallController.swift | 196 -- .../Sources/PresentationCallManager.swift | 18 +- .../Sources/PresentationGroupCall.swift | 126 +- .../Sources/VoiceChatController.swift | 353 ++- .../Sources/AccountIntermediateState.swift | 6 +- .../Sources/AccountStateManagementUtils.swift | 13 +- .../Sources/AccountStateManager.swift | 4 +- .../TelegramCore/Sources/GroupCalls.swift | 35 +- .../Sources/UpdateCachedPeerData.swift | 6 + .../VoiceChatMicOn.imageset/Contents.json | 12 + .../Call/VoiceChatMicOn.imageset/Voice.pdf | Bin 0 -> 4095 bytes .../Sources/ChatCallTitlePanelNode.swift | 153 ++ .../TelegramUI/Sources/ChatController.swift | 53 +- .../ChatInterfaceTitlePanelNodes.swift | 10 + .../ChatPanelInterfaceInteraction.swift | 3 + .../ChatPresentationInterfaceState.swift | 92 +- .../Sources/ChatRecentActionsController.swift | 1 + .../ChatRecentActionsFilterController.swift | 2 +- .../Sources/PeerInfo/PeerInfoScreen.swift | 3 +- .../Sources/SharedAccountContext.swift | 36 +- .../Sources/GroupCallContext.swift | 2002 +---------------- .../OngoingCallThreadLocalContext.h | 6 +- .../Sources/OngoingCallThreadLocalContext.mm | 16 +- .../Sources/WidgetSetupScreen.swift | 8 +- 39 files changed, 1038 insertions(+), 2316 deletions(-) create mode 100644 submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Contents.json create mode 100644 submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Voice.pdf create mode 100644 submodules/TelegramUI/Sources/ChatCallTitlePanelNode.swift diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 025b05f462..d88f8a329a 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -11,6 +11,11 @@ public enum RequestCallResult { case alreadyInProgress(PeerId?) } +public enum RequestOrJoinGroupCallResult { + case requested + case alreadyInProgress(PeerId?) +} + public struct CallAuxiliaryServer { public enum Connection { case stun @@ -151,13 +156,59 @@ public protocol PresentationCall: class { func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) } -public protocol PresentationGroupCall: class { +public struct PresentationGroupCallState: Equatable { + public enum NetworkState { + case connecting + case connected + } + public var networkState: NetworkState + public var isMuted: Bool + + public init( + networkState: NetworkState, + isMuted: Bool + ) { + self.networkState = networkState + self.isMuted = isMuted + } +} + +public struct PresentationGroupCallMemberState: Equatable { + public var ssrc: UInt32 + public var isSpeaking: Bool + + public init( + ssrc: UInt32, + isSpeaking: Bool + ) { + self.ssrc = ssrc + self.isSpeaking = isSpeaking + } +} + +public protocol PresentationGroupCall: class { + var account: Account { get } + var accountContext: AccountContext { get } + var internalId: CallSessionInternalId { get } + var peerId: PeerId { get } + + var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { get } + + var canBeRemoved: Signal { get } + var state: Signal { get } + var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { get } + + func leave() -> Signal + + func toggleIsMuted() + func setIsMuted(_ value: Bool) + func setCurrentAudioOutput(_ output: AudioSessionOutput) } public protocol PresentationCallManager: class { var currentCallSignal: Signal { get } func requestCall(context: AccountContext, peerId: PeerId, isVideo: Bool, endCurrentIfAny: Bool) -> RequestCallResult - func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) + func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult } diff --git a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift index 69fea1777c..7a9036e188 100644 --- a/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift +++ b/submodules/ItemListPeerItem/Sources/ItemListPeerItem.swift @@ -225,8 +225,14 @@ public enum ItemListPeerItemHeight { } public enum ItemListPeerItemText { + public enum TextColor { + case secondary + case accent + case constructive + } + case presence - case text(String) + case text(String, TextColor) case none } @@ -733,8 +739,17 @@ public class ItemListPeerItemNode: ItemListRevealOptionsItemNode, ItemListItemNo } else { statusAttributedString = NSAttributedString(string: item.presentationData.strings.LastSeen_Offline, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) } - case let .text(text): - statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + case let .text(text, textColor): + let textColorValue: UIColor + switch textColor { + case .secondary: + textColorValue = item.presentationData.theme.list.itemSecondaryTextColor + case .accent: + textColorValue = item.presentationData.theme.list.itemAccentColor + case .constructive: + textColorValue = item.presentationData.theme.list.itemDisclosureActions.constructive.fillColor + } + statusAttributedString = NSAttributedString(string: text, font: statusFont, textColor: textColorValue) case .none: break } diff --git a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift index 54e6664322..e76db9a263 100644 --- a/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelAdminsController.swift @@ -236,7 +236,7 @@ private enum ChannelAdminsEntry: ItemListNodeEntry { arguments.openAdmin(participant.participant) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: action, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.removeAdmin(peerId) diff --git a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift index f2751edd1b..8668a35edb 100644 --- a/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelBlacklistController.swift @@ -164,7 +164,7 @@ private enum ChannelBlacklistEntry: ItemListNodeEntry { switch participant.participant { case let .member(_, _, _, banInfo, _): if let banInfo = banInfo, let peer = participant.peers[banInfo.restrictedBy] { - text = .text(strings.Channel_Management_RemovedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).0) + text = .text(strings.Channel_Management_RemovedBy(peer.displayTitle(strings: strings, displayOrder: nameDisplayOrder)).0, .secondary) } default: break diff --git a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift index 87b96c35b7..2d3e18cfc8 100644 --- a/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelDiscussionGroupSetupController.swift @@ -157,7 +157,7 @@ private enum ChannelDiscussionGroupSetupControllerEntry: ItemListNodeEntry { } else { text = strings.Channel_DiscussionGroup_PrivateChannel } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: nameOrder, context: arguments.context, peer: peer, aliasHandling: .standard, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.selectGroup(peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) case let .groupsInfo(theme, title): diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index fcad5942b5..5540394f63 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -194,7 +194,7 @@ private enum ChannelMembersEntry: ItemListNodeEntry { case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, participant, editing, enabled): let text: ItemListPeerItemText if let user = participant.peer as? TelegramUser, let _ = user.botInfo { - text = .text(strings.Bot_GenericBotStatus) + text = .text(strings.Bot_GenericBotStatus, .secondary) } else { text = .presence } diff --git a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift index f1813127e9..8569e49e9c 100644 --- a/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelPermissionsController.swift @@ -264,7 +264,7 @@ private enum ChannelPermissionsEntry: ItemListNodeEntry { } } if !exceptionsString.isEmpty { - text = .text(exceptionsString) + text = .text(exceptionsString, .secondary) } } default: diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index 1d2d08580a..097010cfbc 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -343,7 +343,7 @@ private enum ChannelVisibilityEntry: ItemListNodeEntry { if let addressName = peer.addressName { label = "t.me/" + addressName } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(label, .secondary), label: .none, editing: editing, switchValue: nil, enabled: enabled, selectable: true, sectionId: self.section, action: nil, setPeerIdWithRevealedOptions: { previousId, id in arguments.setPeerIdWithRevealedOptions(previousId, id) }, removePeer: { peerId in arguments.revokePeerId(peerId) diff --git a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift index a0efbd7865..cd39d720cc 100644 --- a/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift +++ b/submodules/PeersNearbyUI/Sources/PeersNearbyController.swift @@ -231,7 +231,7 @@ private enum PeersNearbyEntry: ItemListNodeEntry { if isSelfPeer { text = strings.PeopleNearby_VisibleUntil(humanReadableStringForTimestamp(strings: strings, dateTimeFormat: dateTimeFormat, timestamp: peer.expires)).0 } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, selectable: !isSelfPeer, sectionId: self.section, action: { if !isSelfPeer { arguments.openProfile(peer.peer.0, peer.distance) } @@ -251,9 +251,9 @@ private enum PeersNearbyEntry: ItemListNodeEntry { case let .group(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted): var text: ItemListPeerItemText if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount { - text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)") + text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0), \(memberCount > 0 ? strings.Conversation_StatusMembers(memberCount) : strings.PeopleNearby_NoMembers)", .secondary) } else { - text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0) + text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0, .secondary) } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: { arguments.openChat(peer.peer.0) @@ -265,9 +265,9 @@ private enum PeersNearbyEntry: ItemListNodeEntry { case let .channel(_, _, strings, dateTimeFormat, nameDisplayOrder, peer, highlighted): var text: ItemListPeerItemText if let cachedData = peer.peer.1 as? CachedChannelData, let memberCount = cachedData.participantsSummary.memberCount { - text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0), \(strings.Conversation_StatusSubscribers(memberCount))") + text = .text("\(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0), \(strings.Conversation_StatusSubscribers(memberCount))", .secondary) } else { - text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0) + text = .text(strings.Map_DistanceAway(shortStringForDistance(strings: strings, distance: peer.distance)).0, .secondary) } return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer.peer.0, aliasHandling: .standard, nameColor: .primary, nameStyle: .distinctBold, presence: nil, text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: nil, switchValue: nil, enabled: true, highlighted: highlighted, selectable: true, sectionId: self.section, action: { arguments.openChat(peer.peer.0) diff --git a/submodules/SettingsUI/Sources/DebugController.swift b/submodules/SettingsUI/Sources/DebugController.swift index c5b661209b..01a1c2743e 100644 --- a/submodules/SettingsUI/Sources/DebugController.swift +++ b/submodules/SettingsUI/Sources/DebugController.swift @@ -738,22 +738,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) case .voiceConference: return ItemListDisclosureItem(presentationData: presentationData, title: "Voice Conference (Test)", label: "", sectionId: self.section, style: .blocks, action: { - guard let context = arguments.context else { - return - } - let controller = GroupCallController(context: context) - controller.navigationPresentation = .modal - arguments.pushController(controller) - - /*let _ = (resolvePeerByName(account: context.account, name: "tgbetachat") - |> deliverOnMainQueue).start(next: { peerId in - guard let peerId = peerId else { - return - } - - let controller = VoiceChatController(context: context, peerId: peerId) - arguments.presentController(controller, nil) - })*/ }) case let .preferredVideoCodec(_, title, value, isSelected): return ItemListCheckboxItem(presentationData: presentationData, title: title, style: .right, checked: isSelected, zeroSeparatorInsets: false, sectionId: self.section, action: { diff --git a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift index 0de1e56528..524bb24665 100644 --- a/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift +++ b/submodules/SettingsUI/Sources/Notifications/Exceptions/NotificationExceptionControllerNode.swift @@ -531,7 +531,7 @@ private enum NotificationExceptionEntry : ItemListNodeEntry { arguments.selectPeer() }) case let .peer(_, peer, theme, strings, dateTimeFormat, nameDisplayOrder, value, _, revealed, editing, isSearching): - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(value), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: peer, presence: nil, text: .text(value, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: editing, revealed: revealed), switchValue: nil, enabled: true, selectable: true, sectionId: self.section, action: { arguments.openPeer(peer) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.updateRevealedPeerId(peerId) diff --git a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift index 09d8ba35b3..8107a0f1e7 100644 --- a/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift +++ b/submodules/SettingsUI/Sources/Privacy and Security/SelectivePrivacySettingsPeersController.swift @@ -154,16 +154,16 @@ private enum SelectivePrivacyPeersEntry: ItemListNodeEntry { case let .peerItem(_, theme, strings, dateTimeFormat, nameDisplayOrder, peer, editing, enabled): var text: ItemListPeerItemText = .none if let group = peer.peer as? TelegramGroup { - text = .text(strings.Conversation_StatusMembers(Int32(group.participantCount))) + text = .text(strings.Conversation_StatusMembers(Int32(group.participantCount)), .secondary) } else if let channel = peer.peer as? TelegramChannel { if let participantCount = peer.participantCount { - text = .text(strings.Conversation_StatusMembers(Int32(participantCount))) + text = .text(strings.Conversation_StatusMembers(Int32(participantCount)), .secondary) } else { switch channel.info { case .group: - text = .text(strings.Group_Status) + text = .text(strings.Group_Status, .secondary) case .broadcast: - text = .text(strings.Channel_Status) + text = .text(strings.Channel_Status, .secondary) } } } diff --git a/submodules/StatisticsUI/Sources/GroupStatsController.swift b/submodules/StatisticsUI/Sources/GroupStatsController.swift index 263e5a9ac1..7370caed78 100644 --- a/submodules/StatisticsUI/Sources/GroupStatsController.swift +++ b/submodules/StatisticsUI/Sources/GroupStatsController.swift @@ -412,7 +412,7 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { arguments.openPeer(peer.id) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setPostersPeerIdWithRevealedOptions(peerId, fromPeerId) @@ -443,7 +443,7 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { arguments.openPeer(peer.id) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setAdminsPeerIdWithRevealedOptions(peerId, fromPeerId) @@ -466,7 +466,7 @@ private enum StatsEntry: ItemListNodeEntry { })) } } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", ")), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: .firstLast, context: arguments.context, peer: peer, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(textComponents.joined(separator: ", "), .secondary), label: .none, editing: ItemListPeerItemEditing(editable: true, editing: false, revealed: revealed), revealOptions: ItemListPeerItemRevealOptions(options: options), switchValue: nil, enabled: true, highlighted: false, selectable: arguments.context.account.peerId != peer.id, sectionId: self.section, action: { arguments.openPeer(peer.id) }, setPeerIdWithRevealedOptions: { peerId, fromPeerId in arguments.setInvitersPeerIdWithRevealedOptions(peerId, fromPeerId) diff --git a/submodules/StatisticsUI/Sources/MessageStatsController.swift b/submodules/StatisticsUI/Sources/MessageStatsController.swift index 50dff5ee93..9160956f00 100644 --- a/submodules/StatisticsUI/Sources/MessageStatsController.swift +++ b/submodules/StatisticsUI/Sources/MessageStatsController.swift @@ -144,7 +144,7 @@ private enum StatsEntry: ItemListNodeEntry { } let text: String = presentationData.strings.Stats_MessageViews(views) - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ",", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: message.peers[message.id.peerId]!, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(text), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .military, dateFormat: .dayFirst, dateSeparator: ".", decimalSeparator: ",", groupingSeparator: ""), nameDisplayOrder: .firstLast, context: arguments.context, peer: message.peers[message.id.peerId]!, height: .generic, aliasHandling: .standard, nameColor: .primary, nameStyle: .plain, presence: nil, text: .text(text, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: nil), revealOptions: nil, switchValue: nil, enabled: true, highlighted: false, selectable: true, sectionId: self.section, action: { arguments.openMessage(message.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }, toggleUpdated: nil, contextAction: nil) } diff --git a/submodules/SyncCore/Sources/CachedChannelData.swift b/submodules/SyncCore/Sources/CachedChannelData.swift index 4f7a4ac10e..b8a2b6d324 100644 --- a/submodules/SyncCore/Sources/CachedChannelData.swift +++ b/submodules/SyncCore/Sources/CachedChannelData.swift @@ -174,6 +174,7 @@ public final class CachedChannelData: CachedPeerData { public let statsDatacenterId: Int32 public let invitedBy: PeerId? public let photo: TelegramMediaImage? + public let activeCallMessageId: MessageId? public let peerIds: Set public let messageIds: Set @@ -203,9 +204,10 @@ public final class CachedChannelData: CachedPeerData { self.statsDatacenterId = 0 self.invitedBy = nil self.photo = nil + self.activeCallMessageId = nil } - public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?) { + public init(isNotAccessible: Bool, flags: CachedChannelFlags, about: String?, participantsSummary: CachedChannelParticipantsSummary, exportedInvitation: ExportedInvitation?, botInfos: [CachedPeerBotInfo], peerStatusSettings: PeerStatusSettings?, pinnedMessageId: MessageId?, stickerPack: StickerPackCollectionInfo?, minAvailableMessageId: MessageId?, migrationReference: ChannelMigrationReference?, linkedDiscussionPeerId: LinkedDiscussionPeerId, peerGeoLocation: PeerGeoLocation?, slowModeTimeout: Int32?, slowModeValidUntilTimestamp: Int32?, hasScheduledMessages: Bool, statsDatacenterId: Int32, invitedBy: PeerId?, photo: TelegramMediaImage?, activeCallMessageId: MessageId?) { self.isNotAccessible = isNotAccessible self.flags = flags self.about = about @@ -225,6 +227,7 @@ public final class CachedChannelData: CachedPeerData { self.statsDatacenterId = statsDatacenterId self.invitedBy = invitedBy self.photo = photo + self.activeCallMessageId = activeCallMessageId var peerIds = Set() for botInfo in botInfos { @@ -252,79 +255,83 @@ public final class CachedChannelData: CachedPeerData { } public func withUpdatedIsNotAccessible(_ isNotAccessible: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedFlags(_ flags: CachedChannelFlags) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedAbout(_ about: String?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedParticipantsSummary(_ participantsSummary: CachedChannelParticipantsSummary) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedExportedInvitation(_ exportedInvitation: ExportedInvitation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedBotInfos(_ botInfos: [CachedPeerBotInfo]) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedPeerStatusSettings(_ peerStatusSettings: PeerStatusSettings?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedStickerPack(_ stickerPack: StickerPackCollectionInfo?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedMinAvailableMessageId(_ minAvailableMessageId: MessageId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedMigrationReference(_ migrationReference: ChannelMigrationReference?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedLinkedDiscussionPeerId(_ linkedDiscussionPeerId: LinkedDiscussionPeerId) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedSlowModeTimeout(_ slowModeTimeout: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedSlowModeValidUntilTimestamp(_ slowModeValidUntilTimestamp: Int32?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedStatsDatacenterId(_ statsDatacenterId: Int32) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedInvitedBy(_ invitedBy: PeerId?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: invitedBy, photo: self.photo, activeCallMessageId: self.activeCallMessageId) } public func withUpdatedPhoto(_ photo: TelegramMediaImage?) -> CachedChannelData { - return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo) + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: photo, activeCallMessageId: self.activeCallMessageId) + } + + public func withUpdatedActiveCallMessageId(_ activeCallMessageId: MessageId?) -> CachedChannelData { + return CachedChannelData(isNotAccessible: self.isNotAccessible, flags: self.flags, about: self.about, participantsSummary: self.participantsSummary, exportedInvitation: self.exportedInvitation, botInfos: self.botInfos, peerStatusSettings: self.peerStatusSettings, pinnedMessageId: self.pinnedMessageId, stickerPack: self.stickerPack, minAvailableMessageId: self.minAvailableMessageId, migrationReference: self.migrationReference, linkedDiscussionPeerId: self.linkedDiscussionPeerId, peerGeoLocation: self.peerGeoLocation, slowModeTimeout: self.slowModeTimeout, slowModeValidUntilTimestamp: self.slowModeValidUntilTimestamp, hasScheduledMessages: self.hasScheduledMessages, statsDatacenterId: self.statsDatacenterId, invitedBy: self.invitedBy, photo: self.photo, activeCallMessageId: activeCallMessageId) } public init(decoder: PostboxDecoder) { @@ -349,6 +356,12 @@ public final class CachedChannelData: CachedPeerData { self.pinnedMessageId = nil } + if let activeCallMessagePeerId = decoder.decodeOptionalInt64ForKey("activeCallMessageId.p"), let activeCallMessageNamespace = decoder.decodeOptionalInt32ForKey("activeCallMessageId.n"), let activeCallMessageId = decoder.decodeOptionalInt32ForKey("activeCallMessageId.i") { + self.activeCallMessageId = MessageId(peerId: PeerId(activeCallMessagePeerId), namespace: activeCallMessageNamespace, id: activeCallMessageId) + } else { + self.activeCallMessageId = nil + } + if let stickerPack = decoder.decodeObjectForKey("sp", decoder: { StickerPackCollectionInfo(decoder: $0) }) as? StickerPackCollectionInfo { self.stickerPack = stickerPack } else { @@ -440,6 +453,17 @@ public final class CachedChannelData: CachedPeerData { encoder.encodeNil(forKey: "pm.n") encoder.encodeNil(forKey: "pm.i") } + + if let activeCallMessageId = self.activeCallMessageId { + encoder.encodeInt64(activeCallMessageId.peerId.toInt64(), forKey: "activeCallMessageId.p") + encoder.encodeInt32(activeCallMessageId.namespace, forKey: "activeCallMessageId.n") + encoder.encodeInt32(activeCallMessageId.id, forKey: "activeCallMessageId.i") + } else { + encoder.encodeNil(forKey: "activeCallMessageId.p") + encoder.encodeNil(forKey: "activeCallMessageId.n") + encoder.encodeNil(forKey: "activeCallMessageId.i") + } + if let stickerPack = self.stickerPack { encoder.encodeObject(stickerPack, forKey: "sp") } else { @@ -582,6 +606,10 @@ public final class CachedChannelData: CachedPeerData { return false } + if other.activeCallMessageId != self.activeCallMessageId { + return false + } + return true } } diff --git a/submodules/TelegramCallsUI/Sources/GroupCallController.swift b/submodules/TelegramCallsUI/Sources/GroupCallController.swift index 127ad52dce..8b13789179 100644 --- a/submodules/TelegramCallsUI/Sources/GroupCallController.swift +++ b/submodules/TelegramCallsUI/Sources/GroupCallController.swift @@ -1,197 +1 @@ -import Foundation -import UIKit -import Display -import AsyncDisplayKit -import SwiftSignalKit -import TelegramPresentationData -import TelegramUIPreferences -import TelegramVoip -import TelegramAudio -import AccountContext - -public final class GroupCallController: ViewController { - private final class Node: ViewControllerTracingNode { - private let context: AccountContext - private let presentationData: PresentationData - - private var videoCapturer: OngoingCallVideoCapturer? - private var callContext: GroupCallContext? - private var callDisposable: Disposable? - private var memberCountDisposable: Disposable? - private var isMutedDisposable: Disposable? - private let audioSessionActive = Promise(false) - - private var incomingVideoStreamList: [String] = [] - private var incomingVideoStreamListDisposable: Disposable? - - private var memberCount: Int = 0 - private let memberCountNode: ImmediateTextNode - - private var isMuted: Bool = false - private let isMutedNode: ImmediateTextNode - private let muteButton: HighlightableButtonNode - - private var videoViews: [OngoingCallContextPresentationCallVideoView] = [] - - private var validLayout: ContainerViewLayout? - - init(context: AccountContext) { - self.context = context - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - self.memberCountNode = ImmediateTextNode() - self.isMutedNode = ImmediateTextNode() - - self.muteButton = HighlightableButtonNode() - - super.init() - - self.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - - self.addSubnode(self.memberCountNode) - - self.muteButton.addSubnode(self.isMutedNode) - self.addSubnode(self.muteButton) - - let audioSessionActive = self.audioSessionActive - self.callDisposable = self.context.sharedContext.mediaManager.audioSession.push(audioSessionType: .voiceCall, manualActivate: { audioSessionControl in - audioSessionControl.activate({ _ in }) - audioSessionActive.set(.single(true)) - }, deactivate: { - return Signal { subscriber in - subscriber.putCompletion() - return EmptyDisposable - } - }, availableOutputsChanged: { _, _ in - }) - - //let videoCapturer = OngoingCallVideoCapturer() - //self.videoCapturer = videoCapturer - - let callContext = GroupCallContext(audioSessionActive: self.audioSessionActive.get(), video: videoCapturer) - self.callContext = callContext - - self.memberCountDisposable = (callContext.memberCount - |> deliverOnMainQueue).start(next: { [weak self] value in - guard let strongSelf = self else { - return - } - strongSelf.memberCount = value - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .immediate) - } - }) - - self.incomingVideoStreamListDisposable = (callContext.videoStreamList - |> deliverOnMainQueue).start(next: { [weak self] value in - guard let strongSelf = self else { - return - } - var addedStreamIds: [String] = [] - for id in value { - if !strongSelf.incomingVideoStreamList.contains(id) { - addedStreamIds.append(id) - } - } - strongSelf.incomingVideoStreamList = value - - for id in addedStreamIds { - callContext.makeIncomingVideoView(id: id, completion: { videoView in - guard let strongSelf = self, let videoView = videoView else { - return - } - strongSelf.videoViews.append(videoView) - videoView.view.backgroundColor = .black - strongSelf.view.addSubview(videoView.view) - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .immediate) - } - }) - } - }) - - self.isMutedDisposable = (callContext.isMuted - |> deliverOnMainQueue).start(next: { [weak self] value in - guard let strongSelf = self else { - return - } - strongSelf.isMuted = value - if let layout = strongSelf.validLayout { - strongSelf.containerLayoutUpdated(layout, transition: .immediate) - } - }) - - self.muteButton.addTarget(self, action: #selector(self.muteButtonPressed), forControlEvents: .touchUpInside) - } - - deinit { - self.callDisposable?.dispose() - self.memberCountDisposable?.dispose() - self.incomingVideoStreamListDisposable?.dispose() - } - - @objc private func muteButtonPressed() { - self.callContext?.toggleIsMuted() - } - - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.validLayout = layout - - self.memberCountNode.attributedText = NSAttributedString(string: "Members: \(self.memberCount)", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemPrimaryTextColor) - - self.isMutedNode.attributedText = NSAttributedString(string: self.isMuted ? "Unmute" : "Mute", font: Font.regular(17.0), textColor: self.presentationData.theme.list.itemAccentColor) - - let textSize = self.memberCountNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0)) - let isMutedSize = self.isMutedNode.updateLayout(CGSize(width: layout.size.width - 16.0 * 2.0, height: 100.0)) - - let textFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - textSize.width) / 2.0), y: floor((layout.size.height - textSize.width) / 2.0)), size: textSize) - transition.updateFrameAdditiveToCenter(node: self.memberCountNode, frame: textFrame) - - let isMutedFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - isMutedSize.width) / 2.0), y: textFrame.maxY + 12.0), size: isMutedSize) - transition.updateFrame(node: self.muteButton, frame: isMutedFrame) - self.isMutedNode.frame = CGRect(origin: CGPoint(), size: isMutedFrame.size) - - let videoSize = CGSize(width: 200.0, height: 360.0) - var nextVideoOrigin = CGPoint() - for videoView in self.videoViews { - videoView.view.frame = CGRect(origin: nextVideoOrigin, size: videoSize) - nextVideoOrigin.x += videoSize.width - if nextVideoOrigin.x + videoSize.width > layout.size.width { - nextVideoOrigin.x = 0.0 - nextVideoOrigin.y += videoSize.height - } - } - } - } - - private let context: AccountContext - private let presentationData: PresentationData - - private var controllerNode: Node { - return self.displayNode as! Node - } - - public init(context: AccountContext) { - self.context = context - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } - - super.init(navigationBarPresentationData: NavigationBarPresentationData(presentationData: self.presentationData)) - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override public func loadDisplayNode() { - self.displayNode = Node(context: self.context) - - self.displayNodeDidLoad() - } - - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - - self.controllerNode.containerLayoutUpdated(layout, transition: transition) - } -} diff --git a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift index 2686847069..e809cf1d28 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -592,18 +592,16 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } } - public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) { - let begin: () -> Void = { [weak self] in - guard let strongSelf = self else { - return - } - let _ = strongSelf.startGroupCall(account: context.account, peerId: peerId).start() + public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult { + if let currentGroupCall = self.currentGroupCallValue { + return .alreadyInProgress(currentGroupCall.peerId) } - begin() + let _ = self.startGroupCall(accountContext: context, peerId: peerId).start() + return .requested } private func startGroupCall( - account: Account, + accountContext: AccountContext, peerId: PeerId, internalId: CallSessionInternalId = CallSessionInternalId() ) -> Signal { @@ -647,7 +645,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { } let call = PresentationGroupCallImpl( - account: account, + accountContext: accountContext, audioSession: strongSelf.audioSession, callKitIntegration: nil, getDeviceAccessData: strongSelf.getDeviceAccessData, @@ -658,7 +656,7 @@ public final class PresentationCallManagerImpl: PresentationCallManager { strongSelf.updateCurrentGroupCall(call) strongSelf.currentGroupCallPromise.set(.single(call)) strongSelf.hasActiveCallsPromise.set(true) - strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved.get() + strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved |> deliverOnMainQueue).start(next: { [weak call] value in if value, let strongSelf = self, let call = call { if strongSelf.currentGroupCall === call { diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 7125a26e99..e6e198caa9 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -14,11 +14,20 @@ import DeviceAccess import UniversalMediaPlayer import AccountContext +private extension PresentationGroupCallState { + static var initialValue: PresentationGroupCallState { + return PresentationGroupCallState( + networkState: .connecting, + isMuted: true + ) + } +} + public final class PresentationGroupCallImpl: PresentationGroupCall { private enum InternalState { case requesting case active(GroupCallInfo) - case estabilished(GroupCallInfo, String, [Int32]) + case estabilished(GroupCallInfo, String, [UInt32: PeerId]) var callInfo: GroupCallInfo? { switch self { @@ -33,6 +42,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } public let account: Account + public let accountContext: AccountContext private let audioSession: ManagedAudioSession private let callKitIntegration: CallKitIntegration? public var isIntegratedWithCallKit: Bool { @@ -48,11 +58,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var internalState: InternalState = .requesting private var callContext: OngoingGroupCallContext? + private var ssrcMapping: [UInt32: PeerId] = [:] private var sessionStateDisposable: Disposable? - private let isMutedPromise = ValuePromise(false) - private var isMutedValue = false + private let isMutedPromise = ValuePromise(true) + private var isMutedValue = true public var isMuted: Signal { return self.isMutedPromise.get() } @@ -72,13 +83,45 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private var audioSessionActiveDisposable: Disposable? private var isAudioSessionActive = false - let canBeRemoved = Promise(false) + private let _canBeRemoved = Promise(false) + public var canBeRemoved: Signal { + return self._canBeRemoved.get() + } + + private var stateValue = PresentationGroupCallState.initialValue { + didSet { + if self.stateValue != oldValue { + self.statePromise.set(self.stateValue) + } + } + } + private let statePromise = ValuePromise(PresentationGroupCallState.initialValue) + public var state: Signal { + return self.statePromise.get() + } + + private var membersValue: [PeerId: PresentationGroupCallMemberState] = [:] { + didSet { + if self.membersValue != oldValue { + self.membersPromise.set(self.membersValue) + } + } + } + private let membersPromise = ValuePromise<[PeerId: PresentationGroupCallMemberState]>([:]) + public var members: Signal<[PeerId: PresentationGroupCallMemberState], NoError> { + return self.membersPromise.get() + } private let requestDisposable = MetaDisposable() private var groupCallParticipantUpdatesDisposable: Disposable? + private let networkStateDisposable = MetaDisposable() + private let isMutedDisposable = MetaDisposable() + private let memberStatesDisposable = MetaDisposable() + private let leaveDisposable = MetaDisposable() + init( - account: Account, + accountContext: AccountContext, audioSession: ManagedAudioSession, callKitIntegration: CallKitIntegration?, getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), @@ -86,7 +129,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { peerId: PeerId, peer: Peer? ) { - self.account = account + self.account = accountContext.account + self.accountContext = accountContext self.audioSession = audioSession self.callKitIntegration = callKitIntegration self.getDeviceAccessData = getDeviceAccessData @@ -181,10 +225,12 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } if case let .estabilished(callInfo, _, _) = strongSelf.internalState { - var addedSsrc: [Int32] = [] - for (callId, ssrc) in updates { + var addedSsrc: [UInt32] = [] + for (callId, peerId, ssrc, _) in updates { if callId == callInfo.id { - addedSsrc.append(ssrc) + let mappedSsrc = UInt32(bitPattern: ssrc) + addedSsrc.append(mappedSsrc) + strongSelf.ssrcMapping[mappedSsrc] = peerId } } if !addedSsrc.isEmpty { @@ -201,6 +247,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.audioSessionDisposable?.dispose() self.requestDisposable.dispose() self.groupCallParticipantUpdatesDisposable?.dispose() + self.leaveDisposable.dispose() + self.isMutedDisposable.dispose() + self.memberStatesDisposable.dispose() + self.networkStateDisposable.dispose() } private func updateSessionState(internalState: InternalState, audioSessionControl: ManagedAudioSessionControl?) { @@ -241,10 +291,50 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { return } if let clientParams = joinCallResult.callInfo.clientParams { - strongSelf.updateSessionState(internalState: .estabilished(joinCallResult.callInfo, clientParams, joinCallResult.ssrcs), audioSessionControl: strongSelf.audioSessionControl) + strongSelf.updateSessionState(internalState: .estabilished(joinCallResult.callInfo, clientParams, joinCallResult.ssrcMapping), audioSessionControl: strongSelf.audioSessionControl) } })) })) + + self.isMutedDisposable.set((callContext.isMuted + |> deliverOnMainQueue).start(next: { [weak self] isMuted in + guard let strongSelf = self else { + return + } + strongSelf.stateValue.isMuted = isMuted + })) + + self.networkStateDisposable.set((callContext.networkState + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + let mappedState: PresentationGroupCallState.NetworkState + switch state { + case .connecting: + mappedState = .connecting + case .connected: + mappedState = .connected + } + strongSelf.stateValue.networkState = mappedState + })) + + self.memberStatesDisposable.set((callContext.memberStates + |> deliverOnMainQueue).start(next: { [weak self] memberStates in + guard let strongSelf = self else { + return + } + var result: [PeerId: PresentationGroupCallMemberState] = [:] + for (ssrc, _) in memberStates { + if let peerId = strongSelf.ssrcMapping[ssrc] { + result[peerId] = PresentationGroupCallMemberState( + ssrc: ssrc, + isSpeaking: false + ) + } + } + strongSelf.membersValue = result + })) } } @@ -252,8 +342,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { case .estabilished: break default: - if case let .estabilished(_, clientParams, ssrcs) = internalState { - self.callContext?.setJoinResponse(payload: clientParams, ssrcs: ssrcs) + if case let .estabilished(_, clientParams, ssrcMapping) = internalState { + self.ssrcMapping = ssrcMapping + self.callContext?.setJoinResponse(payload: clientParams, ssrcs: Array(ssrcMapping.keys)) } } } @@ -264,8 +355,15 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { } } - public func hangUp() -> Signal { - return .single(true) + public func leave() -> Signal { + if case let .estabilished(callInfo, _, _) = self.internalState { + self.leaveDisposable.set((leaveGroupCall(account: self.account, callId: callInfo.id, accessHash: callInfo.accessHash) + |> deliverOnMainQueue).start(completed: { [weak self] in + self?._canBeRemoved.set(.single(true)) + })) + } else { + } + return self._canBeRemoved.get() } public func toggleIsMuted() { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 9a56cacd80..fc98673e34 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -85,25 +85,28 @@ private final class VoiceChatActionButton: HighlightTrackingButtonNode { private let foregroundNode: ASImageNode private var validSize: CGSize? + private var isOn: Bool? init() { self.backgroundNode = ASImageNode() self.foregroundNode = ASImageNode() - self.foregroundNode.image = UIImage(bundleImageName: "Call/VoiceChatMicOff") - super.init() self.addSubnode(self.backgroundNode) self.addSubnode(self.foregroundNode) } - func updateLayout(size: CGSize) { + func updateLayout(size: CGSize, isOn: Bool) { if self.validSize != size { self.validSize = size self.backgroundNode.image = generateFilledCircleImage(diameter: size.width, color: UIColor(rgb: 0x1C1C1E)) } + if self.isOn != isOn { + self.isOn = isOn + self.foregroundNode.image = UIImage(bundleImageName: isOn ? "Call/VoiceChatMicOn" : "Call/VoiceChatMicOff") + } self.backgroundNode.frame = CGRect(origin: CGPoint(), size: size) if let image = self.foregroundNode.image { @@ -128,8 +131,15 @@ public final class VoiceChatController: ViewController { } private struct PeerEntry: Comparable, Identifiable { + enum State { + case inactive + case listening + case speaking + } + var participant: RenderedChannelParticipant var activityTimestamp: Int32 + var state: State var stableId: PeerId { return self.participant.peer.id @@ -145,7 +155,19 @@ public final class VoiceChatController: ViewController { func item(context: AccountContext, presentationData: ItemListPresentationData, interaction: Interaction) -> ListViewItem { let peer = self.participant.peer - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: .presence, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { + let text: ItemListPeerItemText + switch self.state { + case .inactive: + text = .presence + case .listening: + //TODO:localize + text = .text("listening", .accent) + case .speaking: + //TODO:localize + text = .text("speaking", .constructive) + } + + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: PresentationDateTimeFormat(timeFormat: .regular, dateFormat: .monthFirst, dateSeparator: ".", decimalSeparator: ".", groupingSeparator: "."), nameDisplayOrder: .firstLast, context: context, peer: peer, height: .peerList, presence: self.participant.presences[self.participant.peer.id], text: text, label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), revealOptions: ItemListPeerItemRevealOptions(options: [ItemListPeerItemRevealOption(type: .destructive, title: presentationData.strings.Common_Delete, action: { //arguments.deleteIncludePeer(peer.peerId) })]), switchValue: nil, enabled: true, selectable: false, sectionId: 0, action: nil, setPeerIdWithRevealedOptions: { lhs, rhs in //arguments.setItemIdWithRevealedOptions(lhs.flatMap { .peer($0) }, rhs.flatMap { .peer($0) }) @@ -166,8 +188,9 @@ public final class VoiceChatController: ViewController { } private weak var controller: VoiceChatController? + private let sharedContext: SharedAccountContext private let context: AccountContext - private let peerId: PeerId + private let call: PresentationGroupCall private var presentationData: PresentationData private var darkTheme: PresentationTheme @@ -181,23 +204,38 @@ public final class VoiceChatController: ViewController { private var enqueuedTransitions: [ListTransition] = [] - private var validLayout: ContainerViewLayout? + private var validLayout: (ContainerViewLayout, CGFloat)? private var didSetContentsReady: Bool = false private var didSetDataReady: Bool = false + private var currentMembers: [RenderedChannelParticipant]? + private var currentMemberStates: [PeerId: PresentationGroupCallMemberState]? + private var currentEntries: [PeerEntry] = [] private var peersDisposable: Disposable? private var peerViewDisposable: Disposable? + private let leaveDisposable = MetaDisposable() + + private var isMutedDisposable: Disposable? + private var callStateDisposable: Disposable? + + private var callState: PresentationGroupCallState? + + private var audioOutputStateDisposable: Disposable? + private var audioOutputState: ([AudioSessionOutput], AudioSessionOutput?)? + + private var memberStatesDisposable: Disposable? private var itemInteraction: Interaction? - init(controller: VoiceChatController, context: AccountContext, peerId: PeerId) { + init(controller: VoiceChatController, sharedContext: SharedAccountContext, call: PresentationGroupCall) { self.controller = controller - self.context = context - self.peerId = peerId + self.sharedContext = sharedContext + self.context = call.accountContext + self.call = call - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.presentationData = sharedContext.currentPresentationData.with { $0 } self.darkTheme = defaultDarkPresentationTheme self.contentContainer = ASDisplayNode() @@ -212,7 +250,6 @@ public final class VoiceChatController: ViewController { self.leaveNode = CallControllerButtonItemNode() self.actionButton = VoiceChatActionButton() self.statusLabel = ImmediateTextNode() - self.statusLabel.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(17.0), textColor: .white) self.radialStatus = RadialStatusNode(backgroundNodeColor: .clear) @@ -231,9 +268,24 @@ public final class VoiceChatController: ViewController { self.addSubnode(self.contentContainer) - let (disposable, loadMoreControl) = context.peerChannelMemberCategoriesContextsManager.recent(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.peerId, updated: { [weak self] state in + let (disposable, loadMoreControl) = self.context.peerChannelMemberCategoriesContextsManager.recent(postbox: self.context.account.postbox, network: self.context.account.network, accountPeerId: self.context.account.peerId, peerId: self.call.peerId, updated: { [weak self] state in Queue.mainQueue().async { - self?.updateMembers(members: state.list) + guard let strongSelf = self else { + return + } + strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: state.list, memberStates: strongSelf.currentMemberStates ?? [:]) + } + }) + + self.memberStatesDisposable = (self.call.members + |> deliverOnMainQueue).start(next: { [weak self] memberStates in + guard let strongSelf = self else { + return + } + if let members = strongSelf.currentMembers { + strongSelf.updateMembers(isMuted: strongSelf.callState?.isMuted ?? true, members: members, memberStates: memberStates) + } else { + strongSelf.currentMemberStates = memberStates } }) @@ -242,13 +294,13 @@ public final class VoiceChatController: ViewController { return } if case let .known(value) = offset, value < 40.0 { - strongSelf.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: strongSelf.peerId, control: loadMoreControl) + strongSelf.context.peerChannelMemberCategoriesContextsManager.loadMore(peerId: strongSelf.call.peerId, control: loadMoreControl) } } self.peersDisposable = disposable - self.peerViewDisposable = (self.context.account.viewTracker.peerView(self.peerId) + self.peerViewDisposable = (self.context.account.viewTracker.peerView(self.call.peerId) |> deliverOnMainQueue).start(next: { [weak self] view in guard let strongSelf = self else { return @@ -274,21 +326,135 @@ public final class VoiceChatController: ViewController { } }) + self.callStateDisposable = (self.call.state + |> deliverOnMainQueue).start(next: { [weak self] state in + guard let strongSelf = self else { + return + } + if strongSelf.callState != state { + let wasMuted = strongSelf.callState?.isMuted ?? true + strongSelf.callState = state + + if wasMuted != state.isMuted, let members = strongSelf.currentMembers { + strongSelf.updateMembers(isMuted: state.isMuted, members: members, memberStates: strongSelf.currentMemberStates ?? [:]) + } + + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) + } + } + }) + + self.audioOutputStateDisposable = (call.audioOutputState + |> deliverOnMainQueue).start(next: { [weak self] state in + if let strongSelf = self { + strongSelf.audioOutputState = state + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout, navigationHeight: navigationHeight, transition: .immediate) + } + } + }) + self.leaveNode.addTarget(self, action: #selector(self.leavePressed), forControlEvents: .touchUpInside) + + self.actionButton.addTarget(self, action: #selector(self.actionButtonPressed), forControlEvents: .touchUpInside) + + self.audioOutputNode.addTarget(self, action: #selector(self.audioOutputPressed), forControlEvents: .touchUpInside) } deinit { self.peersDisposable?.dispose() self.peerViewDisposable?.dispose() + self.leaveDisposable.dispose() + self.isMutedDisposable?.dispose() + self.callStateDisposable?.dispose() + self.audioOutputStateDisposable?.dispose() + self.memberStatesDisposable?.dispose() } @objc private func leavePressed() { - self.controller?.dismiss() + self.leaveDisposable.set((self.call.leave() + |> deliverOnMainQueue).start(completed: { [weak self] in + self?.controller?.dismiss() + })) + } + + @objc private func actionButtonPressed() { + self.call.toggleIsMuted() + } + + @objc private func audioOutputPressed() { + guard let (availableOutputs, currentOutput) = self.audioOutputState else { + return + } + guard availableOutputs.count >= 2 else { + return + } + let hasMute = false + + if availableOutputs.count == 2 { + for output in availableOutputs { + if output != currentOutput { + self.call.setCurrentAudioOutput(output) + break + } + } + } else { + let actionSheet = ActionSheetController(presentationData: self.presentationData) + var items: [ActionSheetItem] = [] + for output in availableOutputs { + if hasMute, case .builtin = output { + continue + } + let title: String + var icon: UIImage? + switch output { + case .builtin: + title = UIDevice.current.model + case .speaker: + title = self.presentationData.strings.Call_AudioRouteSpeaker + icon = generateScaledImage(image: UIImage(bundleImageName: "Call/CallSpeakerButton"), size: CGSize(width: 48.0, height: 48.0), opaque: false) + case .headphones: + title = self.presentationData.strings.Call_AudioRouteHeadphones + case let .port(port): + title = port.name + if port.type == .bluetooth { + var image = UIImage(bundleImageName: "Call/CallBluetoothButton") + let portName = port.name.lowercased() + if portName.contains("airpods pro") { + image = UIImage(bundleImageName: "Call/CallAirpodsProButton") + } else if portName.contains("airpods") { + image = UIImage(bundleImageName: "Call/CallAirpodsButton") + } + icon = generateScaledImage(image: image, size: CGSize(width: 48.0, height: 48.0), opaque: false) + } + } + items.append(CallRouteActionSheetItem(title: title, icon: icon, selected: output == currentOutput, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + self?.call.setCurrentAudioOutput(output) + })) + } + + if hasMute { + items.append(CallRouteActionSheetItem(title: self.presentationData.strings.Call_AudioRouteMute, icon: generateScaledImage(image: UIImage(bundleImageName: "Call/CallMuteButton"), size: CGSize(width: 48.0, height: 48.0), opaque: false), selected: self.callState?.isMuted ?? true, action: { [weak self, weak actionSheet] in + actionSheet?.dismissAnimated() + self?.call.toggleIsMuted() + })) + } + + actionSheet.setItemGroups([ActionSheetItemGroup(items: items), ActionSheetItemGroup(items: [ + ActionSheetButtonItem(title: self.presentationData.strings.Call_AudioRouteHide, color: .accent, font: .bold, action: { [weak actionSheet] in + actionSheet?.dismissAnimated() + }) + ]) + ]) + self.controller?.present(actionSheet, in: .window(.calls)) + } } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationHeight: CGFloat, transition: ContainedViewLayoutTransition) { let isFirstTime = self.validLayout == nil - self.validLayout = layout + self.validLayout = (layout, navigationHeight) transition.updateFrame(node: self.contentContainer, frame: CGRect(origin: CGPoint(), size: layout.size)) @@ -308,22 +474,93 @@ public final class VoiceChatController: ViewController { let centralButtonSize = CGSize(width: 144.0, height: 144.0) let sideButtonInset: CGFloat = 27.0 - self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.grayDimmed), image: .speaker), text: "audio", transition: .immediate) + var audioMode: CallControllerButtonsSpeakerMode = .none + //var hasAudioRouteMenu: Bool = false + if let (availableOutputs, maybeCurrentOutput) = self.audioOutputState, let currentOutput = maybeCurrentOutput { + //hasAudioRouteMenu = availableOutputs.count > 2 + switch currentOutput { + case .builtin: + audioMode = .builtin + case .speaker: + audioMode = .speaker + case .headphones: + audioMode = .headphones + case let .port(port): + var type: CallControllerButtonsSpeakerMode.BluetoothType = .generic + let portName = port.name.lowercased() + if portName.contains("airpods pro") { + type = .airpodsPro + } else if portName.contains("airpods") { + type = .airpods + } + audioMode = .bluetooth(type) + } + if availableOutputs.count <= 1 { + audioMode = .none + } + } + + let soundImage: CallControllerButtonItemNode.Content.Image + var soundAppearance: CallControllerButtonItemNode.Content.Appearance = .color(.grayDimmed) + switch audioMode { + case .none, .builtin: + soundImage = .speaker + case .speaker: + soundImage = .speaker + soundAppearance = .blurred(isFilled: true) + case .headphones: + soundImage = .bluetooth + case let .bluetooth(type): + switch type { + case .generic: + soundImage = .bluetooth + case .airpods: + soundImage = .airpods + case .airpodsPro: + soundImage = .airpodsPro + } + } + + //TODO:localize + self.audioOutputNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: soundAppearance, image: soundImage), text: "audio", transition: .immediate) + + //TODO:localize self.leaveNode.update(size: sideButtonSize, content: CallControllerButtonItemNode.Content(appearance: .color(.redDimmed), image: .end), text: "leave", transition: .immediate) transition.updateFrame(node: self.audioOutputNode, frame: CGRect(origin: CGPoint(x: sideButtonInset, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)) transition.updateFrame(node: self.leaveNode, frame: CGRect(origin: CGPoint(x: layout.size.width - sideButtonInset - sideButtonSize.width, y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - sideButtonSize.height) / 2.0)), size: sideButtonSize)) - self.actionButton.updateLayout(size: centralButtonSize) let actionButtonFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - centralButtonSize.width) / 2.0), y: layout.size.height - bottomAreaHeight + floor((bottomAreaHeight - centralButtonSize.height) / 2.0)), size: centralButtonSize) + + var isMicOn = false + if let callState = callState { + isMicOn = !callState.isMuted + + switch callState.networkState { + case .connecting: + self.radialStatus.isHidden = false + + self.statusLabel.attributedText = NSAttributedString(string: "Connecting...", font: Font.regular(17.0), textColor: .white) + case .connected: + self.radialStatus.isHidden = true + + if isMicOn { + self.statusLabel.attributedText = NSAttributedString(string: "You're Live", font: Font.regular(17.0), textColor: .white) + } else { + self.statusLabel.attributedText = NSAttributedString(string: "Unmute", font: Font.regular(17.0), textColor: .white) + } + } + + let statusSize = self.statusLabel.updateLayout(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude)) + self.statusLabel.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: actionButtonFrame.maxY + 12.0), size: statusSize) + + self.radialStatus.transitionToState(.progress(color: UIColor(rgb: 0x00ACFF), lineWidth: 3.3, value: nil, cancelEnabled: false), animated: false) + self.radialStatus.frame = actionButtonFrame.insetBy(dx: -3.3, dy: -3.3) + } + + self.actionButton.updateLayout(size: centralButtonSize, isOn: isMicOn) transition.updateFrame(node: self.actionButton, frame: actionButtonFrame) - let statusSize = self.statusLabel.updateLayout(CGSize(width: layout.size.width, height: .greatestFiniteMagnitude)) - self.statusLabel.frame = CGRect(origin: CGPoint(x: floor((layout.size.width - statusSize.width) / 2.0), y: actionButtonFrame.maxY + 12.0), size: statusSize) - - self.radialStatus.transitionToState(.progress(color: UIColor(rgb: 0x00ACFF), lineWidth: 3.3, value: nil, cancelEnabled: false), animated: false) - self.radialStatus.frame = actionButtonFrame.insetBy(dx: -3.3, dy: -3.3) - if isFirstTime { while !self.enqueuedTransitions.isEmpty { self.dequeueTransition() @@ -332,6 +569,7 @@ public final class VoiceChatController: ViewController { } func animateIn() { + self.alpha = 1.0 self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.listNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) @@ -344,7 +582,8 @@ public final class VoiceChatController: ViewController { } func animateOut(completion: (() -> Void)?) { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + self.alpha = 0.0 + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, completion: { _ in completion?() }) } @@ -369,6 +608,8 @@ public final class VoiceChatController: ViewController { if transition.crossFade { options.insert(.AnimateCrossfade) } + options.insert(.LowLatency) + options.insert(.PreferSynchronousResourceLoading) self.listNode.transaction(deleteIndices: transition.deletions, insertIndicesAndItems: transition.insertions, updateIndicesAndItems: transition.updates, options: options, updateSizeAndInsets: nil, updateOpaqueState: nil, completion: { [weak self] _ in guard let strongSelf = self else { @@ -381,15 +622,52 @@ public final class VoiceChatController: ViewController { }) } - private func updateMembers(members: [RenderedChannelParticipant]) { + private func updateMembers(isMuted: Bool, members: [RenderedChannelParticipant], memberStates: [PeerId: PresentationGroupCallMemberState]) { + var members = members + members.sort(by: { lhs, rhs in + if lhs.peer.id == self.context.account.peerId { + return true + } else if rhs.peer.id == self.context.account.peerId { + return false + } + let lhsHasState = memberStates[lhs.peer.id] != nil + let rhsHasState = memberStates[rhs.peer.id] != nil + if lhsHasState != rhsHasState { + if lhsHasState { + return true + } else { + return false + } + } + return lhs.peer.id < rhs.peer.id + }) + + self.currentMembers = members + self.currentMemberStates = memberStates + let previousEntries = self.currentEntries var entries: [PeerEntry] = [] var index: Int32 = 0 + for member in members { + let memberState: PeerEntry.State + if member.peer.id == self.context.account.peerId { + if !isMuted { + memberState = .speaking + } else { + memberState = .listening + } + } else if let _ = memberStates[member.peer.id] { + memberState = .listening + } else { + memberState = .inactive + } + entries.append(PeerEntry( participant: member, - activityTimestamp: Int32.max - 1 - index + activityTimestamp: Int32.max - 1 - index, + state: memberState )) index += 1 } @@ -398,13 +676,13 @@ public final class VoiceChatController: ViewController { let presentationData = ItemListPresentationData(theme: self.darkTheme, fontSize: self.presentationData.listsFontSize, strings: self.presentationData.strings) - let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: context, presentationData: presentationData, interaction: self.itemInteraction!) + let transition = preparedTransition(from: previousEntries, to: entries, isLoading: false, isEmpty: false, crossFade: false, context: self.context, presentationData: presentationData, interaction: self.itemInteraction!) self.enqueueTransition(transition) } } - private let context: AccountContext - private let peerId: PeerId + private let sharedContext: SharedAccountContext + public let call: PresentationGroupCall private let presentationData: PresentationData fileprivate let contentsReady = ValuePromise(false, ignoreRepeated: true) @@ -421,10 +699,10 @@ public final class VoiceChatController: ViewController { return self.displayNode as! Node } - public init(context: AccountContext, peerId: PeerId) { - self.context = context - self.peerId = peerId - self.presentationData = context.sharedContext.currentPresentationData.with { $0 } + public init(sharedContext: SharedAccountContext, accountContext: AccountContext, call: PresentationGroupCall) { + self.sharedContext = sharedContext + self.call = call + self.presentationData = sharedContext.currentPresentationData.with { $0 } let darkNavigationTheme = NavigationBarTheme(buttonColor: .white, disabledButtonColor: UIColor(rgb: 0x525252), primaryTextColor: .white, backgroundColor: UIColor(white: 0.0, alpha: 0.6), separatorColor: UIColor(white: 0.0, alpha: 0.8), badgeBackgroundColor: .clear, badgeStrokeColor: .clear, badgeTextColor: .clear) @@ -461,7 +739,7 @@ public final class VoiceChatController: ViewController { } override public func loadDisplayNode() { - self.displayNode = Node(controller: self, context: self.context, peerId: self.peerId) + self.displayNode = Node(controller: self, sharedContext: self.sharedContext, call: self.call) self.displayNodeDidLoad() } @@ -469,6 +747,8 @@ public final class VoiceChatController: ViewController { override public func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + self.isDismissed = false + if !self.didAppearOnce { self.didAppearOnce = true @@ -479,6 +759,7 @@ public final class VoiceChatController: ViewController { override public func dismiss(completion: (() -> Void)? = nil) { if !self.isDismissed { self.isDismissed = true + self.didAppearOnce = false self.controllerNode.animateOut(completion: { [weak self] in completion?() diff --git a/submodules/TelegramCore/Sources/AccountIntermediateState.swift b/submodules/TelegramCore/Sources/AccountIntermediateState.swift index 2bf78f3456..ef6f0a9798 100644 --- a/submodules/TelegramCore/Sources/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/AccountIntermediateState.swift @@ -607,7 +607,7 @@ struct AccountReplayedFinalState { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] - let updatedGroupCallParticipants: [(Int64, Int32)] + let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? @@ -623,7 +623,7 @@ struct AccountFinalStateEvents { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] - let updatedGroupCallParticipants: [(Int64, Int32)] + let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] @@ -639,7 +639,7 @@ struct AccountFinalStateEvents { return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.isEmpty && self.updatedGroupCallParticipants.isEmpty && self.updatedPeersNearby?.isEmpty ?? true && self.isContactUpdates.isEmpty && self.displayAlerts.isEmpty && self.delayNotificatonsUntil == nil && self.updatedMaxMessageId == nil && self.updatedQts == nil && self.externallyUpdatedPeerId.isEmpty && !authorizationListUpdated && self.updatedIncomingThreadReadStates.isEmpty && self.updatedOutgoingThreadReadStates.isEmpty } - init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, Int32)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { + init(addedIncomingMessageIds: [MessageId] = [], wasScheduledMessageIds: [MessageId] = [], deletedMessageIds: [DeletedMessageId] = [], updatedTypingActivities: [PeerActivitySpace: [PeerId: PeerInputActivity?]] = [:], updatedWebpages: [MediaId: TelegramMediaWebpage] = [:], updatedCalls: [Api.PhoneCall] = [], addedCallSignalingData: [(Int64, Data)] = [], updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] = [], updatedPeersNearby: [PeerNearby]? = nil, isContactUpdates: [(PeerId, Bool)] = [], displayAlerts: [(text: String, isDropAuth: Bool)] = [], delayNotificatonsUntil: Int32? = nil, updatedMaxMessageId: Int32? = nil, updatedQts: Int32? = nil, externallyUpdatedPeerId: Set = Set(), authorizationListUpdated: Bool = false, updatedIncomingThreadReadStates: [MessageId: MessageId.Id] = [:], updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] = [:]) { self.addedIncomingMessageIds = addedIncomingMessageIds self.wasScheduledMessageIds = wasScheduledMessageIds self.deletedMessageIds = deletedMessageIds diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index da488ada74..aa728482b0 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -2201,7 +2201,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] - var updatedGroupCallParticipants: [(Int64, Int32)] = [] + var updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] @@ -2932,11 +2932,14 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP case let .AddCallSignalingData(callId, data): addedCallSignalingData.append((callId, data)) case let .UpdateGroupCallParticipant(callId, _, participant): + var peerId: PeerId? var ssrc: Int32? switch participant { - case let .groupCallParticipantAdmin(_, source): + case let .groupCallParticipantAdmin(userId, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) ssrc = source - case let .groupCallParticipant(_, _, _, source): + case let .groupCallParticipant(_, userId, _, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) ssrc = source case .groupCallParticipantLeft: break @@ -2945,8 +2948,8 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP case .groupCallParticipantInvited: break } - if let ssrc = ssrc { - updatedGroupCallParticipants.append((callId, ssrc)) + if let peerId = peerId, let ssrc = ssrc { + updatedGroupCallParticipants.append((callId, peerId, ssrc, true)) } case let .UpdateLangPack(langCode, difference): if let difference = difference { diff --git a/submodules/TelegramCore/Sources/AccountStateManager.swift b/submodules/TelegramCore/Sources/AccountStateManager.swift index 26265cf1d5..2b96b1273b 100644 --- a/submodules/TelegramCore/Sources/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/AccountStateManager.swift @@ -148,8 +148,8 @@ public final class AccountStateManager { return self.threadReadStateUpdatesPipe.signal() } - private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, Int32)]>() - public var groupCallParticipantUpdates: Signal<[(Int64, Int32)], NoError> { + private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, PeerId, Int32, Bool)]>() + public var groupCallParticipantUpdates: Signal<[(Int64, PeerId, Int32, Bool)], NoError> { return self.groupCallParticipantUpdatesPipe.signal() } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift index 3808a406fc..5d812b0ce2 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -203,7 +203,7 @@ public enum JoinGroupCallError { public struct JoinGroupCallResult { public var callInfo: GroupCallInfo - public var ssrcs: [Int32] + public var ssrcMapping: [UInt32: PeerId] } public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { @@ -239,14 +239,17 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo guard let _ = GroupCallInfo(call) else { return .fail(.generic) } - var ssrcs: [Int32] = [] + var ssrcMapping: [UInt32: PeerId] = [:] for participant in participants { - var ssrc: Int32? + var peerId: PeerId? + var ssrc: UInt32? switch participant { - case let .groupCallParticipantAdmin(_, source): - ssrc = source - case let .groupCallParticipant(_, _, _, source): - ssrc = source + case let .groupCallParticipantAdmin(userId, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + ssrc = UInt32(bitPattern: source) + case let .groupCallParticipant(_, userId, _, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + ssrc = UInt32(bitPattern: source) case .groupCallParticipantLeft: break case .groupCallParticipantKicked: @@ -254,14 +257,14 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo case .groupCallParticipantInvited: break } - if let ssrc = ssrc { - ssrcs.append(ssrc) + if let peerId = peerId, let ssrc = ssrc { + ssrcMapping[ssrc] = peerId } } return account.postbox.transaction { transaction -> JoinGroupCallResult in return JoinGroupCallResult( callInfo: parsedCall, - ssrcs: ssrcs + ssrcMapping: ssrcMapping ) } |> castError(JoinGroupCallError.self) @@ -270,6 +273,18 @@ public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, jo } } +public enum LeaveGroupCallError { + case generic +} + +public func leaveGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal { + return account.network.request(Api.functions.phone.leaveGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) + |> mapError { _ -> LeaveGroupCallError in + return .generic + } + |> ignoreValues +} + public enum StopGroupCallError { case generic } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index 70c6d43f0b..bb65bc9005 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -396,6 +396,11 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI pinnedMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: pinnedMsgId) } + var updatedActiveCallMessageId: MessageId? + if let callMsgId = callMsgId { + updatedActiveCallMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: callMsgId) + } + var minAvailableMessageId: MessageId? if let minAvailableMsgId = minAvailableMsgId { minAvailableMessageId = MessageId(peerId: peerId, namespace: Namespaces.Message.Cloud, id: minAvailableMsgId) @@ -510,6 +515,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI .withUpdatedStatsDatacenterId(statsDc ?? 0) .withUpdatedInvitedBy(invitedBy) .withUpdatedPhoto(photo) + .withUpdatedActiveCallMessageId(updatedActiveCallMessageId) }) if let minAvailableMessageId = minAvailableMessageId, minAvailableMessageIdUpdated { diff --git a/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Contents.json b/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Contents.json new file mode 100644 index 0000000000..8065bf20cd --- /dev/null +++ b/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Contents.json @@ -0,0 +1,12 @@ +{ + "images" : [ + { + "filename" : "Voice.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Voice.pdf b/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Voice.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d72275c2cd0b8bfb73578079a7c0f96494b96783 GIT binary patch literal 4095 zcmai%c{r49`^PO)7?MhqRCh9ivdm&EWgm#4;LAr>O4^vMajxYwE;mFKYqPANxl$Hlb|dn$zX zPhI2bx3x?;xY+ZcSBWQPCoy8+46R6!pjHY$AoTt{NjKhc-l*$#fvd@-PX{TdE9GX) zD?n`M?OSC6rD3tigg2`P=XgVdxBVIslsZ9G8t-si zIYhK0W;C9OrkZ^LjlI8e;P!_#1edpLH3r$Jqx5~`bj?)zzT-n%3RunFZc{xqkK=Fo z8gRG=%%r&Fys-!5^nhGGLb{m>)el`1NpF}pc*CoEJ(UCcQu^GDkv?rG02(?qP;9bK z8!sw5RN~fK@jlQfZJ9&ipvGitR8RKDH_rF+N0wa+>*-i7?Jj~aL{k}_!>p!VqTyQ7 z5YAGVR2{Z>F=c^pXm2ztd^+G>)7V}*+j|>7dbcqMVs%GJ!Zcq_!=y^GoD<3-B`EGX zpg3@B?ro3%bXg-y{=TguVOcpd810dJechCliF^0S&5b2OrB&0CkL8MuT;cTSa`vf3 z_GQHD@Rq#lEU8W1^c^N|k2h;zk}L7AQZr_|{(%4HlS|jHGK`^*xBK~~kX)$%g0XA} zNEwpc$nF#yk{clXX8=uhr83stfL)&%?mFl%z1{U+zSF0WZH!1%z?>nft_fHIQfRU> znPPMmZ$kohjfr-H1E{|Qe$$ZoP2(ct1S!>B|GF~F3rMMxJREFDh8VSfIT#@WNBn1x z|L*J5yPlVf2Khp1HL^8b2ZaF0ihwr<@b)?c3`BnaxEzDJYyfS3vK(o1Gc zsE~~YFXyd5LO;hckA8E#fx5={aHJE*Vj=45NXm8#Zgg<+MgO4PqHljClm7QWPy^MJ z*}7$I4-@LndcMz4&*O(_33OT1C(tD}e=(Cd{1F+>O)!I#02`B-SIsn~fncj9bS zTT&wQbA|w}^JuknT4a^}ND73W6$#3)NP>K{%euivs=0(sVH#f(?de&+ufqi6O}6fs zgl0I0AvAc8E`%+!FtNus$yZajBR1si)0n(+>^_Hs!mxy#1N})3<5R7+n@goe8wB{- z_(om&A6`1Nl`^i|Fn<4H@?8yP2+k$hK4!j9aDE=wH(|d~H#mCYrqQ-ytM``i5_FE1 zrW%=s8+D$0ELFqDv?^?N?|5l#f-zH>%U+dn`eVICfN6OTQ`yM#OZPh>Bf@s{W~vjfy}LD& zZ?L?6IhL)JtjL5u%_{7w?9JjkYt91)x7JqM7seF3MI*d_Om3d!v

l>!=DDO{{W7 z6Dg2P@uOLzLC*Co2k)>^AAqF;Bw;M#SM~||Bd@ZK08BytT0E?GStYet98^G0Rh0&q zz^d>8W{+ErSDD@gcz1y=2FhIBYXQ3aa^F=>E`N*)IHX=1#yk?>z#jO8=WwQK{vE#T z{mE*5Jjcw~ucKA<<#<_Np~LR*#Hws`K_4ACb1Odst*^LpnkrDqZh33-0b3U!y?2}0 zH|RhTOAmhE0Mm2-!IJZW?3jl3kLq5B@3Zm09HL(+=TqW-(ZEkX_Jv72*t#K(U8R+s zwOiAa{n(Pa+8tA2<^!jkVuZ3;@-$NJaAzN@h|-F=_L+5WXa+v%2w(Hju~1wjonV6R zBV2&>CfOvJ=rsq+ZV!(Q?+0}LBA8D(I=bh zMDB2yft&*OpSlYOI}2ap5n!KW-wry^AZ-xvNRKKM!gIaJf*|(f_JC?NRuFnIju#su z%x@?xmRuHa3F|0i2~~vFoQi8ZN5oK$;8d*iiiFbvYsmgsW&Lb!$#IDr&_T7jbi z#sZ1b&8VX(t|@IP<0+HUEoJs*oXb?3$8Q~dq_%wVL-R5+DOhNaVEVHch&2l!+uZ1rujhRDB!cwuZ*r+yLqk(rLm#$RO z%4l3p6ZTN)0#sE?X2}N}?y+Xy1NlaU)*D&Z?`@prRE#i!Q`(s`Jn^hBPh!-gZr=BNie&)Wh;{U?`PT~lq z`x?DmkP;(Fl#I0Yx|;FMcqn1aY<9Clw?AsqqsgUdctCM*yL{{He(n7+n2476+_*^r z8mHACiN%*=E*HOuT_Y?yF6JtoQwmdxQaV)YQCr|EwT|Cr+4k5R{@VX#a#G7!O1-rfws^2^d?T>3F| z?s~bg*G(@^kOV_N#}dpoQhIyUzSJhmW-w76S3_nz`>Jg=QrbjP|LckRrHV<(S~^6Z2i zYRDVcDO$0v@EiyA$5n-H#ZES#os3$tTqk{x0VM`K4eC};Y_bfq!4_c;W5-zZRG)ox ze`>uP@HsF3PJEUaGOs)DjYCk`dRdPe1Rq{<))J{GQX|zUodrWzzINzcFg}#2(jB8C z>V~teO1k|+=bKif@IA;a9A~NH(-AuiDgCuU$AO8J&ncf>+mE+>ZQHtuvPF5i+tqsf z2>D)*T5#en4DKtk7^=AFX<1)M|5iI2?E<)H4Ss3YY>y%x8+8pId1vszprrPSed*#v zvs#6k0lh||)12G+mGfce`C;4ALdT&*A*=3hg!WJ2pY$LfVrNHjzLz(KJ640{YaXsX z-17YB!S4BCc)IedX)*z4HP}`3(Xw#7V>xsAMb+iGS`)nawJZA_qKVOEh(}LrE)+Xr zdUI#8X95JGQzmL=d|u3_zHb@ouN!so>tT*!eZ{79f>*HGuXRn|sZiDB75)SLaU*Ru zbEdM+&d=IUeO_xRhK32oJZx!*cJDp^u3z?%Y`jcERBZE;v1={l?T_EkK8FgX^^sW> z>ma#P@=Kpu)=w^`aVZR55BJTV7THc;ecojjZkEyap)c%twv2<1j?d_{e^2W1&f^lw z_jYcqM6M>Ml)kpprp0s~zJE_X-Sg(MT66GHL>PbQxyw|g%CgUIQ;SN9M%n`si>;O_ zD0xFQTi=Zn3+#&IEzkR=`qs>cY)AE-s7kwQJ$KV#`q`pO&GgmM?Ym=zw`=$KPHtLm zNsX*l`i|e;Nj=6Iv3GMpveswE>Vx96@5pvHTi$*vA6*7M`&yM;?zBxtbAeQQLX%D->S%2<)<| zAz=9ne|LHL7ykZ@({6y2E{W)XS0j4?<}gMIQVu}De)6z81G3?Ol(qxWjls#gG|o`_ zjfG|ZA&ACP@y=wsKSAB?FGBwtz~z41zyNTfyA7lD6LrDc=otYfB#N5@*%g4pB;hiE zg{YdlgEJ98z|;`(7Gi*bJDx)I1{kRRlkUB!VhloOw1rg07Ig-EgGJTPAz%m?91fR- zBam_^X)_p1gz+%)bjifs6yU$7{QZYs6p}3%2FQb9{~CY{3WY=gw!p6#N|w<`><+;7 zR}3jFBme(nFc^Yy CGFloat { + let panelHeight: CGFloat = 50.0 + + self.tapButton.frame = CGRect(origin: CGPoint(), size: CGSize(width: width, height: panelHeight)) + + self.activeGroupCallInfo = interfaceState.activeGroupCallInfo + + if self.theme !== interfaceState.theme { + self.theme = interfaceState.theme + + self.backgroundColor = interfaceState.theme.chat.historyNavigation.fillColor + self.separatorNode.backgroundColor = interfaceState.theme.chat.historyNavigation.strokeColor + + self.joinButtonTitleNode.attributedText = NSAttributedString(string: interfaceState.strings.Channel_JoinChannel.uppercased(), font: Font.semibold(15.0), textColor: interfaceState.theme.chat.inputPanel.actionControlForegroundColor) + self.joinButtonBackgroundNode.image = generateStretchableFilledCircleImage(diameter: 28.0, color: interfaceState.theme.chat.inputPanel.actionControlFillColor) + + //TODO:localize + self.titleNode.attributedText = NSAttributedString(string: "Voice Chat", font: Font.semibold(15.0), textColor: interfaceState.theme.chat.inputPanel.primaryTextColor) + self.textNode.attributedText = NSAttributedString(string: "4 members", font: Font.regular(13.0), textColor: interfaceState.theme.chat.inputPanel.secondaryTextColor) + + self.muteIconNode.image = PresentationResourcesChat.chatTitleMuteIcon(interfaceState.theme) + } + + + let joinButtonTitleSize = self.joinButtonTitleNode.updateLayout(CGSize(width: 150.0, height: .greatestFiniteMagnitude)) + let joinButtonSize = CGSize(width: joinButtonTitleSize.width + 20.0, height: 28.0) + let joinButtonFrame = CGRect(origin: CGPoint(x: width - rightInset - 7.0 - joinButtonSize.width, y: floor((panelHeight - joinButtonSize.height) / 2.0)), size: joinButtonSize) + transition.updateFrame(node: self.joinButton, frame: joinButtonFrame) + transition.updateFrame(node: self.joinButtonBackgroundNode, frame: CGRect(origin: CGPoint(), size: joinButtonFrame.size)) + transition.updateFrame(node: self.joinButtonTitleNode, frame: CGRect(origin: CGPoint(x: floorToScreenPixels((joinButtonFrame.width - joinButtonTitleSize.width) / 2.0), y: floorToScreenPixels((joinButtonFrame.height - joinButtonTitleSize.height) / 2.0)), size: joinButtonTitleSize)) + + let titleSize = self.titleNode.updateLayout(CGSize(width: width, height: .greatestFiniteMagnitude)) + let textSize = self.textNode.updateLayout(CGSize(width: width, height: .greatestFiniteMagnitude)) + + let titleFrame = CGRect(origin: CGPoint(x: floor((width - titleSize.width) / 2.0), y: 10.0), size: titleSize) + transition.updateFrame(node: self.titleNode, frame: titleFrame) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: titleFrame.maxY + 1.0), size: textSize)) + + if let image = self.muteIconNode.image { + transition.updateFrame(node: self.muteIconNode, frame: CGRect(origin: CGPoint(x: titleFrame.maxX + 4.0, y: titleFrame.minY + 5.0), size: image.size)) + } + + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: panelHeight - UIScreenPixel), size: CGSize(width: width, height: UIScreenPixel))) + + return panelHeight + } + + @objc private func tapped() { + guard let interfaceInteraction = self.interfaceInteraction else { + return + } + guard let activeGroupCallInfo = self.activeGroupCallInfo else { + return + } + interfaceInteraction.joinGroupCall(activeGroupCallInfo.messageId) + } +} + diff --git a/submodules/TelegramUI/Sources/ChatController.swift b/submodules/TelegramUI/Sources/ChatController.swift index 83f2b82b5a..9d5b4b4893 100644 --- a/submodules/TelegramUI/Sources/ChatController.swift +++ b/submodules/TelegramUI/Sources/ChatController.swift @@ -421,7 +421,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G self.stickerSettings = ChatInterfaceStickerSettings(loopAnimatedStickers: false) - self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, pendingUnpinnedAllMessages: false) + self.presentationInterfaceState = ChatPresentationInterfaceState(chatWallpaper: self.presentationData.chatWallpaper, theme: self.presentationData.theme, strings: self.presentationData.strings, dateTimeFormat: self.presentationData.dateTimeFormat, nameDisplayOrder: self.presentationData.nameDisplayOrder, limitsConfiguration: context.currentLimitsConfiguration.with { $0 }, fontSize: self.presentationData.chatFontSize, bubbleCorners: self.presentationData.chatBubbleCorners, accountPeerId: context.account.peerId, mode: mode, chatLocation: chatLocation, subject: subject, peerNearbyData: peerNearbyData, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil) var mediaAccessoryPanelVisibility = MediaAccessoryPanelVisibility.none if case .standard = mode { @@ -1390,17 +1390,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G } } }) - - /*let _ = (context.account.postbox.transaction { transaction -> (Peer?, Peer?) in - return (transaction.getPeer(peer.id), transaction.getPeer(currentPeerId)) - } - |> deliverOnMainQueue).start(next: { peer, current in - if let peer = peer, let current = current { - strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { - let _ = context.sharedContext.callManager?.requestCall(context: context, peerId: peer.id, isVideo: isVideo, endCurrentIfAny: true) - })]), in: .window(.root)) - } - })*/ } } }) @@ -3752,6 +3741,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var peerIsBlocked: Bool = false var callsAvailable: Bool = true var callsPrivate: Bool = false + var activeGroupCallInfo: ChatActiveGroupCallInfo? var slowmodeState: ChatSlowmodeState? if let cachedData = combinedInitialData.cachedData as? CachedChannelData { pinnedMessageId = cachedData.pinnedMessageId @@ -3760,6 +3750,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) } } + if let messageId = cachedData.activeCallMessageId { + activeGroupCallInfo = ChatActiveGroupCallInfo(messageId: messageId) + } } else if let cachedData = combinedInitialData.cachedData as? CachedUserData { peerIsBlocked = cachedData.isBlocked callsAvailable = cachedData.voiceCallsAvailable @@ -3794,6 +3787,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G updated = updated.updatedPeerIsBlocked(peerIsBlocked) updated = updated.updatedCallsAvailable(callsAvailable) updated = updated.updatedCallsPrivate(callsPrivate) + updated = updated.updatedActiveGroupCallInfo(activeGroupCallInfo) updated = updated.updatedTitlePanelContext({ context in if pinnedMessageId != nil { if !context.contains(where: { @@ -3901,6 +3895,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G var callsAvailable: Bool = false var callsPrivate: Bool = false var slowmodeState: ChatSlowmodeState? + var activeGroupCallInfo: ChatActiveGroupCallInfo? if let cachedData = cachedData as? CachedChannelData { pinnedMessageId = cachedData.pinnedMessageId if let channel = strongSelf.presentationInterfaceState.renderedPeer?.peer as? TelegramChannel, channel.isRestrictedBySlowmode, let timeout = cachedData.slowModeTimeout { @@ -3910,6 +3905,9 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G slowmodeState = ChatSlowmodeState(timeout: timeout, variant: .timestamp(slowmodeUntilTimestamp)) } } + if let messageId = cachedData.activeCallMessageId { + activeGroupCallInfo = ChatActiveGroupCallInfo(messageId: messageId) + } } else if let cachedData = cachedData as? CachedUserData { peerIsBlocked = cachedData.isBlocked callsAvailable = cachedData.voiceCallsAvailable @@ -3949,10 +3947,11 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G let callsDataUpdated = strongSelf.presentationInterfaceState.callsAvailable != callsAvailable || strongSelf.presentationInterfaceState.callsPrivate != callsPrivate - if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage != pinnedMessage || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState { + if strongSelf.presentationInterfaceState.pinnedMessageId != pinnedMessageId || strongSelf.presentationInterfaceState.pinnedMessage != pinnedMessage || strongSelf.presentationInterfaceState.peerIsBlocked != peerIsBlocked || pinnedMessageUpdated || callsDataUpdated || strongSelf.presentationInterfaceState.slowmodeState != slowmodeState || strongSelf.presentationInterfaceState.activeGroupCallInfo != activeGroupCallInfo { strongSelf.updateChatPresentationInterfaceState(animated: strongSelf.willAppear, interactive: strongSelf.willAppear, { state in return state .updatedPinnedMessageId(pinnedMessageId) + .updatedActiveGroupCallInfo(activeGroupCallInfo) .updatedPinnedMessage(pinnedMessage) .updatedPeerIsBlocked(peerIsBlocked) .updatedCallsAvailable(callsAvailable) @@ -5979,6 +5978,34 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G chatController.canReadHistory.set(false) let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: chatController, sourceNode: node, passthroughTouches: true)), items: .single(items), reactionItems: [], gesture: gesture) strongSelf.presentInGlobalOverlay(contextController) + }, joinGroupCall: { [weak self] messageId in + guard let strongSelf = self, let peer = strongSelf.presentationInterfaceState.renderedPeer?.peer else { + return + } + let callResult = strongSelf.context.sharedContext.callManager?.requestOrJoinGroupCall(context: strongSelf.context, peerId: peer.id) + if let callResult = callResult, case let .alreadyInProgress(currentPeerId) = callResult { + if currentPeerId == peer.id { + strongSelf.context.sharedContext.navigateToCurrentCall() + } else { + let presentationData = strongSelf.context.sharedContext.currentPresentationData.with { $0 } + let _ = (strongSelf.context.account.postbox.transaction { transaction -> (Peer?, Peer?) in + return (transaction.getPeer(peer.id), currentPeerId.flatMap(transaction.getPeer)) + } |> deliverOnMainQueue).start(next: { [weak self] peer, current in + if let peer = peer { + if let strongSelf = self, let current = current { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_CallInProgressMessage(current.compactDisplayTitle, peer.compactDisplayTitle).0, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + if let strongSelf = self { + //let _ = strongSelf.context.sharedContext.callManager?.requestCall(context: context, peerId: peerId, isVideo: isVideo, endCurrentIfAny: true) + } + })]), in: .window(.root)) + } else { + strongSelf.present(textAlertController(context: strongSelf.context, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: { + })]), in: .window(.root)) + } + } + }) + } + } }, statuses: ChatPanelInterfaceInteractionStatuses(editingMessage: self.editingMessage.get(), startingBot: self.startingBot.get(), unblockingPeer: self.unblockingPeer.get(), searching: self.searching.get(), loadingMessage: self.loadingMessage.get(), inlineSearch: self.performingInlineSearch.get())) do { diff --git a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift index 90ca8622d4..c698ec6967 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceTitlePanelNodes.swift @@ -64,6 +64,16 @@ func titlePanelForChatPresentationInterfaceState(_ chatPresentationInterfaceStat } } + if chatPresentationInterfaceState.activeGroupCallInfo != nil { + if let currentPanel = currentPanel as? ChatCallTitlePanelNode { + return currentPanel + } else { + let panel = ChatCallTitlePanelNode(context: context) + panel.interfaceInteraction = interfaceInteraction + return panel + } + } + if let selectedContext = selectedContext { switch selectedContext { case .pinnedMessage: diff --git a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift index d1689a584d..e98952d007 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -127,6 +127,7 @@ final class ChatPanelInterfaceInteraction { let viewReplies: (MessageId?, ChatReplyThreadMessage) -> Void let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? + let joinGroupCall: (MessageId) -> Void init( setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, @@ -205,6 +206,7 @@ final class ChatPanelInterfaceInteraction { scrollToTop: @escaping () -> Void, viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, activatePinnedListPreview: @escaping (ASDisplayNode, ContextGesture) -> Void, + joinGroupCall: @escaping (MessageId) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage @@ -283,6 +285,7 @@ final class ChatPanelInterfaceInteraction { self.scrollToTop = scrollToTop self.viewReplies = viewReplies self.activatePinnedListPreview = activatePinnedListPreview + self.joinGroupCall = joinGroupCall self.statuses = statuses } } diff --git a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift index 3c47a37a91..a01e6f496c 100644 --- a/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift +++ b/submodules/TelegramUI/Sources/ChatPresentationInterfaceState.swift @@ -293,6 +293,10 @@ final class ChatPinnedMessage: Equatable { } } +struct ChatActiveGroupCallInfo: Equatable { + var messageId: MessageId +} + final class ChatPresentationInterfaceState: Equatable { let interfaceState: ChatInterfaceState let chatLocation: ChatLocation @@ -338,8 +342,9 @@ final class ChatPresentationInterfaceState: Equatable { let subject: ChatControllerSubject? let peerNearbyData: ChatPeerNearbyData? let pendingUnpinnedAllMessages: Bool + let activeGroupCallInfo: ChatActiveGroupCallInfo? - init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, pendingUnpinnedAllMessages: Bool) { + init(chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, chatLocation: ChatLocation, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?) { self.interfaceState = ChatInterfaceState() self.inputTextPanelState = ChatTextInputPanelState() self.editMessageState = nil @@ -384,9 +389,10 @@ final class ChatPresentationInterfaceState: Equatable { self.subject = subject self.peerNearbyData = peerNearbyData self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages + self.activeGroupCallInfo = activeGroupCallInfo } - init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, pendingUnpinnedAllMessages: Bool) { + init(interfaceState: ChatInterfaceState, chatLocation: ChatLocation, renderedPeer: RenderedPeer?, isNotAccessible: Bool, explicitelyCanPinMessages: Bool, contactStatus: ChatContactStatus?, hasBots: Bool, isArchived: Bool, inputTextPanelState: ChatTextInputPanelState, editMessageState: ChatEditInterfaceMessageState?, recordedMediaPreview: ChatRecordedMediaPreview?, inputQueryResults: [ChatPresentationInputQueryKind: ChatPresentationInputQueryResult], inputMode: ChatInputMode, titlePanelContexts: [ChatTitlePanelContext], keyboardButtonsMessage: Message?, pinnedMessageId: MessageId?, pinnedMessage: ChatPinnedMessage?, peerIsBlocked: Bool, peerIsMuted: Bool, peerDiscussionId: PeerId?, peerGeoLocation: PeerGeoLocation?, callsAvailable: Bool, callsPrivate: Bool, slowmodeState: ChatSlowmodeState?, chatHistoryState: ChatHistoryNodeHistoryState?, botStartPayload: String?, urlPreview: (String, TelegramMediaWebpage)?, editingUrlPreview: (String, TelegramMediaWebpage)?, search: ChatSearchData?, searchQuerySuggestionResult: ChatPresentationInputQueryResult?, chatWallpaper: TelegramWallpaper, theme: PresentationTheme, strings: PresentationStrings, dateTimeFormat: PresentationDateTimeFormat, nameDisplayOrder: PresentationPersonNameOrder, limitsConfiguration: LimitsConfiguration, fontSize: PresentationFontSize, bubbleCorners: PresentationChatBubbleCorners, accountPeerId: PeerId, mode: ChatControllerPresentationMode, hasScheduledMessages: Bool, subject: ChatControllerSubject?, peerNearbyData: ChatPeerNearbyData?, pendingUnpinnedAllMessages: Bool, activeGroupCallInfo: ChatActiveGroupCallInfo?) { self.interfaceState = interfaceState self.chatLocation = chatLocation self.renderedPeer = renderedPeer @@ -431,6 +437,7 @@ final class ChatPresentationInterfaceState: Equatable { self.subject = subject self.peerNearbyData = peerNearbyData self.pendingUnpinnedAllMessages = pendingUnpinnedAllMessages + self.activeGroupCallInfo = activeGroupCallInfo } static func ==(lhs: ChatPresentationInterfaceState, rhs: ChatPresentationInterfaceState) -> Bool { @@ -578,35 +585,38 @@ final class ChatPresentationInterfaceState: Equatable { if lhs.pendingUnpinnedAllMessages != rhs.pendingUnpinnedAllMessages { return false } + if lhs.activeGroupCallInfo != rhs.activeGroupCallInfo { + return false + } return true } func updatedInterfaceState(_ f: (ChatInterfaceState) -> ChatInterfaceState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: f(self.interfaceState), chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPeer(_ f: (RenderedPeer?) -> RenderedPeer?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: f(self.renderedPeer), isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedIsNotAccessible(_ isNotAccessible: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedExplicitelyCanPinMessages(_ explicitelyCanPinMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedContactStatus(_ contactStatus: ChatContactStatus?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedHasBots(_ hasBots: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedIsArchived(_ isArchived: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedInputQueryResult(queryKind: ChatPresentationInputQueryKind, _ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { @@ -617,123 +627,127 @@ final class ChatPresentationInterfaceState: Equatable { } else { inputQueryResults.removeValue(forKey: queryKind) } - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedInputTextPanelState(_ f: (ChatTextInputPanelState) -> ChatTextInputPanelState) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: f(self.inputTextPanelState), editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedEditMessageState(_ editMessageState: ChatEditInterfaceMessageState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedRecordedMediaPreview(_ recordedMediaPreview: ChatRecordedMediaPreview?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedInputMode(_ f: (ChatInputMode) -> ChatInputMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: f(self.inputMode), titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedTitlePanelContext(_ f: ([ChatTitlePanelContext]) -> [ChatTitlePanelContext]) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: f(self.titlePanelContexts), keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedKeyboardButtonsMessage(_ message: Message?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: message, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPinnedMessageId(_ pinnedMessageId: MessageId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPinnedMessage(_ pinnedMessage: ChatPinnedMessage?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPeerIsBlocked(_ peerIsBlocked: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPeerIsMuted(_ peerIsMuted: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPeerDiscussionId(_ peerDiscussionId: PeerId?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPeerGeoLocation(_ peerGeoLocation: PeerGeoLocation?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedCallsAvailable(_ callsAvailable: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedCallsPrivate(_ callsPrivate: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedSlowmodeState(_ slowmodeState: ChatSlowmodeState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedBotStartPayload(_ botStartPayload: String?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedChatHistoryState(_ chatHistoryState: ChatHistoryNodeHistoryState?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedUrlPreview(_ urlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedEditingUrlPreview(_ editingUrlPreview: (String, TelegramMediaWebpage)?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedSearch(_ search: ChatSearchData?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedSearchQuerySuggestionResult(_ f: (ChatPresentationInputQueryResult?) -> ChatPresentationInputQueryResult?) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: f(self.searchQuerySuggestionResult), chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedMode(_ mode: ChatControllerPresentationMode) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedTheme(_ theme: PresentationTheme) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedStrings(_ strings: PresentationStrings) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedDateTimeFormat(_ dateTimeFormat: PresentationDateTimeFormat) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedChatWallpaper(_ chatWallpaper: TelegramWallpaper) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedBubbleCorners(_ bubbleCorners: PresentationChatBubbleCorners) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedHasScheduledMessages(_ hasScheduledMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) } func updatedPendingUnpinnedAllMessages(_ pendingUnpinnedAllMessages: Bool) -> ChatPresentationInterfaceState { - return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages) + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: pendingUnpinnedAllMessages, activeGroupCallInfo: self.activeGroupCallInfo) + } + + func updatedActiveGroupCallInfo(_ activeGroupCallInfo: ChatActiveGroupCallInfo?) -> ChatPresentationInterfaceState { + return ChatPresentationInterfaceState(interfaceState: self.interfaceState, chatLocation: self.chatLocation, renderedPeer: self.renderedPeer, isNotAccessible: self.isNotAccessible, explicitelyCanPinMessages: self.explicitelyCanPinMessages, contactStatus: self.contactStatus, hasBots: self.hasBots, isArchived: self.isArchived, inputTextPanelState: self.inputTextPanelState, editMessageState: self.editMessageState, recordedMediaPreview: self.recordedMediaPreview, inputQueryResults: self.inputQueryResults, inputMode: self.inputMode, titlePanelContexts: self.titlePanelContexts, keyboardButtonsMessage: self.keyboardButtonsMessage, pinnedMessageId: self.pinnedMessageId, pinnedMessage: self.pinnedMessage, peerIsBlocked: self.peerIsBlocked, peerIsMuted: self.peerIsMuted, peerDiscussionId: self.peerDiscussionId, peerGeoLocation: self.peerGeoLocation, callsAvailable: self.callsAvailable, callsPrivate: self.callsPrivate, slowmodeState: self.slowmodeState, chatHistoryState: self.chatHistoryState, botStartPayload: self.botStartPayload, urlPreview: self.urlPreview, editingUrlPreview: self.editingUrlPreview, search: self.search, searchQuerySuggestionResult: self.searchQuerySuggestionResult, chatWallpaper: self.chatWallpaper, theme: self.theme, strings: self.strings, dateTimeFormat: self.dateTimeFormat, nameDisplayOrder: self.nameDisplayOrder, limitsConfiguration: self.limitsConfiguration, fontSize: self.fontSize, bubbleCorners: self.bubbleCorners, accountPeerId: self.accountPeerId, mode: self.mode, hasScheduledMessages: self.hasScheduledMessages, subject: self.subject, peerNearbyData: self.peerNearbyData, pendingUnpinnedAllMessages: self.pendingUnpinnedAllMessages, activeGroupCallInfo: activeGroupCallInfo) } } diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift index fc671058c0..d5ad766a0b 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -132,6 +132,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, scrollToTop: { }, viewReplies: { _, _ in }, activatePinnedListPreview: { _, _ in + }, joinGroupCall: { _ in }, statuses: nil) self.navigationItem.titleView = self.titleView diff --git a/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift b/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift index 4095675a6e..8ff8959812 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsFilterController.swift @@ -233,7 +233,7 @@ private enum ChatRecentActionsFilterEntry: ItemListNodeEntry { case .member: peerText = strings.ChatAdmins_AdminLabel.capitalized } - return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { + return ItemListPeerItem(presentationData: presentationData, dateTimeFormat: dateTimeFormat, nameDisplayOrder: nameDisplayOrder, context: arguments.context, peer: participant.peer, presence: nil, text: .text(peerText, .secondary), label: .none, editing: ItemListPeerItemEditing(editable: false, editing: false, revealed: false), switchValue: ItemListPeerItemSwitch(value: checked, style: .check), enabled: true, selectable: true, sectionId: self.section, action: { arguments.toggleAdmin(participant.peer.id) }, setPeerIdWithRevealedOptions: { _, _ in }, removePeer: { _ in }) diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 806b7838d0..8030b41fc1 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -438,6 +438,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, scrollToTop: { }, viewReplies: { _, _ in }, activatePinnedListPreview: { _, _ in + }, joinGroupCall: { _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -453,7 +454,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { self.backgroundNode.backgroundColor = presentationData.theme.rootController.navigationBar.backgroundColor self.separatorNode.backgroundColor = presentationData.theme.rootController.navigationBar.separatorColor - let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId), subject: nil, peerNearbyData: nil, pendingUnpinnedAllMessages: false) + let interfaceState = ChatPresentationInterfaceState(chatWallpaper: .color(0), theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, nameDisplayOrder: presentationData.nameDisplayOrder, limitsConfiguration: .defaultValue, fontSize: .regular, bubbleCorners: PresentationChatBubbleCorners(mainRadius: 16.0, auxiliaryRadius: 8.0, mergeBubbleCorners: true), accountPeerId: self.context.account.peerId, mode: .standard(previewing: false), chatLocation: .peer(self.peerId), subject: nil, peerNearbyData: nil, pendingUnpinnedAllMessages: false, activeGroupCallInfo: nil) let panelHeight = self.selectionPanel.updateLayout(width: layout.size.width, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, maxHeight: 0.0, isSecondary: false, transition: transition, interfaceState: interfaceState, metrics: layout.metrics) transition.updateFrame(node: self.selectionPanel, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: panelHeight))) diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index c309999b54..f1f76af857 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -101,10 +101,14 @@ public final class SharedAccountContextImpl: SharedAccountContext { private var currentCallStatusText: CallStatusText = .none private var currentCallStatusTextTimer: SwiftSignalKit.Timer? + private var groupCallDisposable: Disposable? + private var callController: CallController? public let hasOngoingCall = ValuePromise(false) private let callState = Promise(nil) + private var groupCallController: VoiceChatController? + private var immediateHasOngoingCallValue = Atomic(value: false) public var immediateHasOngoingCall: Bool { return self.immediateHasOngoingCallValue.with { $0 } @@ -630,6 +634,27 @@ public final class SharedAccountContextImpl: SharedAccountContext { } }) + self.groupCallDisposable = (callManager.currentGroupCallSignal + |> deliverOnMainQueue).start(next: { [weak self] call in + if let strongSelf = self { + if call !== strongSelf.groupCallController?.call { + strongSelf.groupCallController?.dismiss() + strongSelf.groupCallController = nil + strongSelf.hasOngoingCall.set(false) + + if let call = call { + mainWindow.hostView.containerView.endEditing(true) + let groupCallController = VoiceChatController(sharedContext: strongSelf, accountContext: call.accountContext, call: call) + strongSelf.groupCallController = groupCallController + strongSelf.mainWindow?.present(groupCallController, on: .calls) + strongSelf.hasOngoingCall.set(true) + } else { + strongSelf.hasOngoingCall.set(false) + } + } + } + }) + self.callStateDisposable = (self.callState.get() |> deliverOnMainQueue).start(next: { [weak self] state in if let strongSelf = self { @@ -740,6 +765,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { self.inAppNotificationSettingsDisposable?.dispose() self.mediaInputSettingsDisposable?.dispose() self.callDisposable?.dispose() + self.groupCallDisposable?.dispose() self.callStateDisposable?.dispose() self.currentCallStatusTextTimer?.invalidate() } @@ -977,11 +1003,19 @@ public final class SharedAccountContextImpl: SharedAccountContext { } public func navigateToCurrentCall() { - if let mainWindow = self.mainWindow, let callController = self.callController { + guard let mainWindow = self.mainWindow else { + return + } + if let callController = self.callController { if callController.isNodeLoaded && callController.view.superview == nil { mainWindow.hostView.containerView.endEditing(true) mainWindow.present(callController, on: .calls) } + } else if let groupCallController = self.groupCallController { + if groupCallController.isNodeLoaded && groupCallController.view.superview == nil { + mainWindow.hostView.containerView.endEditing(true) + mainWindow.present(groupCallController, on: .calls) + } } } diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index c5e838c05e..b6a71ad57f 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -26,1917 +26,6 @@ private final class ContextQueueImpl: NSObject, OngoingCallThreadLocalContextQue } } -private struct ConferenceDescription { - struct Transport { - struct Candidate { - var id: String - var generation: Int - var component: String - var `protocol`: String - var tcpType: String? - var ip: String - var port: Int - var foundation: String - var priority: Int - var type: String - var network: Int - var relAddr: String? - var relPort: Int? - } - - struct Fingerprint { - var fingerprint: String - var setup: String - var hashType: String - } - - var candidates: [Candidate] - var fingerprints: [Fingerprint] - var ufrag: String - var pwd: String - } - - struct ChannelBundle { - var id: String - var transport: Transport - } - - struct Content { - struct Channel { - struct SsrcGroup { - var sources: [Int] - var semantics: String - } - - struct PayloadType { - var id: Int - var name: String - var clockrate: Int - var channels: Int - var parameters: [String: Any]? - } - - struct RtpHdrExt { - var id: Int - var uri: String - } - - var id: String? - var endpoint: String - var channelBundleId: String - var sources: [Int] - var ssrcs: [Int] - var rtpLevelRelayType: String - var expire: Int? - var initiator: Bool - var direction: String - var ssrcGroups: [SsrcGroup] - var payloadTypes: [PayloadType] - var rtpHdrExts: [RtpHdrExt] - } - - var name: String - var channels: [Channel] - } - - var id: String - var channelBundles: [ChannelBundle] - var contents: [Content] - - init?(json: [String: Any]) { - guard let id = json["id"] as? String else { - assert(false) - return nil - } - self.id = id - - var channelBundles: [ChannelBundle] = [] - if let channelBundlesJson = json["channel-bundles"] as? [Any] { - for channelBundleValue in channelBundlesJson { - if let channelBundleJson = channelBundleValue as? [String: Any] { - if let channelBundle = ChannelBundle(json: channelBundleJson) { - channelBundles.append(channelBundle) - } - } - } - } - self.channelBundles = channelBundles - - var contents: [Content] = [] - if let contentsJson = json["contents"] as? [Any] { - for contentValue in contentsJson { - if let contentJson = contentValue as? [String: Any] { - if let content = Content(json: contentJson) { - contents.append(content) - } - } - } - } - self.contents = contents - } -} - -private extension ConferenceDescription.Transport.Candidate { - init?(json: [String: Any]) { - guard let id = json["id"] as? String else { - assert(false) - return nil - } - self.id = id - - if let generationString = json["generation"] as? String, let generation = Int(generationString) { - self.generation = generation - } else { - self.generation = 0 - } - - guard let component = json["component"] as? String else { - assert(false) - return nil - } - self.component = component - - guard let `protocol` = json["protocol"] as? String else { - assert(false) - return nil - } - self.protocol = `protocol` - - if let tcpType = json["tcptype"] as? String { - self.tcpType = tcpType - } else { - self.tcpType = nil - } - - guard let ip = json["ip"] as? String else { - assert(false) - return nil - } - self.ip = ip - - guard let portString = json["port"] as? String, let port = Int(portString) else { - assert(false) - return nil - } - self.port = port - - guard let foundation = json["foundation"] as? String else { - assert(false) - return nil - } - self.foundation = foundation - - guard let priorityString = json["priority"] as? String, let priority = Int(priorityString) else { - assert(false) - return nil - } - self.priority = priority - - guard let type = json["type"] as? String else { - assert(false) - return nil - } - self.type = type - - guard let networkString = json["network"] as? String, let network = Int(networkString) else { - assert(false) - return nil - } - self.network = network - - if let relAddr = json["rel-addr"] as? String { - self.relAddr = relAddr - } else { - self.relAddr = nil - } - - if let relPortString = json["rel-port"] as? String, let relPort = Int(relPortString) { - self.relPort = relPort - } else { - self.relPort = nil - } - } -} - -private extension ConferenceDescription.Transport.Fingerprint { - init?(json: [String: Any]) { - guard let fingerprint = json["fingerprint"] as? String else { - assert(false) - return nil - } - self.fingerprint = fingerprint - - guard let setup = json["setup"] as? String else { - assert(false) - return nil - } - self.setup = setup - - guard let hashType = json["hash"] as? String else { - assert(false) - return nil - } - self.hashType = hashType - } -} - -private extension ConferenceDescription.Transport { - init?(json: [String: Any]) { - guard let ufrag = json["ufrag"] as? String else { - assert(false) - return nil - } - self.ufrag = ufrag - - guard let pwd = json["pwd"] as? String else { - assert(false) - return nil - } - self.pwd = pwd - - var candidates: [Candidate] = [] - if let candidatesJson = json["candidates"] as? [Any] { - for candidateValue in candidatesJson { - if let candidateJson = candidateValue as? [String: Any] { - if let candidate = Candidate(json: candidateJson) { - candidates.append(candidate) - } - } - } - } - self.candidates = candidates - - var fingerprints: [Fingerprint] = [] - if let fingerprintsJson = json["fingerprints"] as? [Any] { - for fingerprintValue in fingerprintsJson { - if let fingerprintJson = fingerprintValue as? [String: Any] { - if let fingerprint = Fingerprint(json: fingerprintJson) { - fingerprints.append(fingerprint) - } - } - } - } - self.fingerprints = fingerprints - } -} - -private extension ConferenceDescription.ChannelBundle { - init?(json: [String: Any]) { - guard let id = json["id"] as? String else { - assert(false) - return nil - } - self.id = id - - guard let transportJson = json["transport"] as? [String: Any] else { - assert(false) - return nil - } - guard let transport = ConferenceDescription.Transport(json: transportJson) else { - assert(false) - return nil - } - self.transport = transport - } -} - -private extension ConferenceDescription.Content.Channel.SsrcGroup { - init?(json: [String: Any]) { - guard let sources = json["sources"] as? [Int] else { - assert(false) - return nil - } - self.sources = sources - - guard let semantics = json["semantics"] as? String else { - assert(false) - return nil - } - self.semantics = semantics - } -} - -private extension ConferenceDescription.Content.Channel.PayloadType { - init?(json: [String: Any]) { - guard let idString = json["id"] as? String, let id = Int(idString) else { - assert(false) - return nil - } - self.id = id - - guard let name = json["name"] as? String else { - assert(false) - return nil - } - self.name = name - - guard let clockrateString = json["clockrate"] as? String, let clockrate = Int(clockrateString) else { - assert(false) - return nil - } - self.clockrate = clockrate - - guard let channelsString = json["channels"] as? String, let channels = Int(channelsString) else { - assert(false) - return nil - } - self.channels = channels - - self.parameters = json["parameters"] as? [String: Any] - } -} - -private extension ConferenceDescription.Content.Channel.RtpHdrExt { - init?(json: [String: Any]) { - guard let idString = json["id"] as? String, let id = Int(idString) else { - assert(false) - return nil - } - self.id = id - - guard let uri = json["uri"] as? String else { - assert(false) - return nil - } - self.uri = uri - } -} - -private extension ConferenceDescription.Content.Channel { - init?(json: [String: Any]) { - guard let id = json["id"] as? String else { - assert(false) - return nil - } - self.id = id - - guard let endpoint = json["endpoint"] as? String else { - assert(false) - return nil - } - self.endpoint = endpoint - - guard let channelBundleId = json["channel-bundle-id"] as? String else { - assert(false) - return nil - } - self.channelBundleId = channelBundleId - - guard let sources = json["sources"] as? [Int] else { - assert(false) - return nil - } - self.sources = sources - - if let ssrcs = json["ssrcs"] as? [Int] { - self.ssrcs = ssrcs - } else { - self.ssrcs = [] - } - - guard let rtpLevelRelayType = json["rtp-level-relay-type"] as? String else { - assert(false) - return nil - } - self.rtpLevelRelayType = rtpLevelRelayType - - if let expire = json["expire"] as? Int { - self.expire = expire - } else { - self.expire = nil - } - - guard let initiator = json["initiator"] as? Bool else { - assert(false) - return nil - } - self.initiator = initiator - - guard let direction = json["direction"] as? String else { - assert(false) - return nil - } - self.direction = direction - - var ssrcGroups: [SsrcGroup] = [] - if let ssrcGroupsJson = json["ssrc-groups"] as? [Any] { - for ssrcGroupValue in ssrcGroupsJson { - if let ssrcGroupJson = ssrcGroupValue as? [String: Any] { - if let ssrcGroup = SsrcGroup(json: ssrcGroupJson) { - ssrcGroups.append(ssrcGroup) - } - } - } - } - self.ssrcGroups = ssrcGroups - - var payloadTypes: [PayloadType] = [] - if let payloadTypesJson = json["payload-types"] as? [Any] { - for payloadTypeValue in payloadTypesJson { - if let payloadTypeJson = payloadTypeValue as? [String: Any] { - if let payloadType = PayloadType(json: payloadTypeJson) { - payloadTypes.append(payloadType) - } - } - } - } - self.payloadTypes = payloadTypes - - var rtpHdrExts: [RtpHdrExt] = [] - if let rtpHdrExtsJson = json["rtp-hdrexts"] as? [Any] { - for rtpHdrExtValue in rtpHdrExtsJson { - if let rtpHdrExtJson = rtpHdrExtValue as? [String: Any] { - if let rtpHdrExt = RtpHdrExt(json: rtpHdrExtJson) { - rtpHdrExts.append(rtpHdrExt) - } - } - } - } - self.rtpHdrExts = rtpHdrExts - } -} - -private extension ConferenceDescription.Content { - init?(json: [String: Any]) { - guard let name = json["name"] as? String else { - assert(false) - return nil - } - self.name = name - - var channels: [Channel] = [] - if let channelsJson = json["channels"] as? [Any] { - for channelValue in channelsJson { - if let channelJson = channelValue as? [String: Any] { - if let channel = Channel(json: channelJson) { - channels.append(channel) - } - } - } - } - self.channels = channels - } -} - -private extension ConferenceDescription.Content.Channel.SsrcGroup { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["sources"] = self.sources - result["semantics"] = self.semantics - - return result - } -} - -private extension ConferenceDescription.Content.Channel.PayloadType { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["id"] = self.id - result["name"] = self.name - result["channels"] = self.channels - result["clockrate"] = self.clockrate - result["rtcp-fbs"] = [ - [ - "type": "transport-cc" - ] as [String: Any], - [ - "type": "ccm fir" - ] as [String: Any], - [ - "type": "nack" - ] as [String: Any], - [ - "type": "nack pli" - ] as [String: Any], - ] as [Any] - if let parameters = self.parameters { - result["parameters"] = parameters - } - - return result - } -} - -private extension ConferenceDescription.Content.Channel.RtpHdrExt { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["id"] = self.id - result["uri"] = self.uri - - return result - } -} - -private extension ConferenceDescription.Content.Channel { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - if let id = self.id { - result["id"] = id - } - result["expire"] = self.expire ?? 10 - result["initiator"] = self.initiator - result["endpoint"] = self.endpoint - result["direction"] = self.direction - result["channel-bundle-id"] = self.channelBundleId - result["rtp-level-relay-type"] = self.rtpLevelRelayType - if !self.sources.isEmpty { - result["sources"] = self.sources - } - if !self.ssrcs.isEmpty { - result["ssrcs"] = self.ssrcs - } - if !self.ssrcGroups.isEmpty { - result["ssrc-groups"] = self.ssrcGroups.map { $0.outgoingColibriDescription() } - } - if !self.payloadTypes.isEmpty { - result["payload-types"] = self.payloadTypes.map { $0.outgoingColibriDescription() } - } - if !self.rtpHdrExts.isEmpty { - result["rtp-hdrexts"] = self.rtpHdrExts.map { $0.outgoingColibriDescription() } - } - result["rtcp-mux"] = true - - return result - } -} - -private extension ConferenceDescription.Content { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["name"] = self.name - result["channels"] = self.channels.map { $0.outgoingColibriDescription() } - - return result - } -} - -private extension ConferenceDescription.Transport.Fingerprint { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["fingerprint"] = self.fingerprint - result["setup"] = self.setup - result["hash"] = self.hashType - - return result - } -} - -private extension ConferenceDescription.Transport.Candidate { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["id"] = self.id - result["generation"] = self.generation - result["component"] = self.component - result["protocol"] = self.protocol - if let tcpType = self.tcpType { - result["tcptype"] = tcpType - } - result["ip"] = self.ip - result["port"] = self.port - result["foundation"] = self.foundation - result["priority"] = self.priority - result["type"] = self.type - result["network"] = self.network - if let relAddr = self.relAddr { - result["rel-addr"] = relAddr - } - if let relPort = self.relPort { - result["rel-port"] = relPort - } - - return result - } -} - -private extension ConferenceDescription.Transport { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["xmlns"] = "urn:xmpp:jingle:transports:ice-udp:1" - result["rtcp-mux"] = true - - if !self.ufrag.isEmpty { - result["ufrag"] = self.ufrag - result["pwd"] = self.pwd - } - - if !self.fingerprints.isEmpty { - result["fingerprints"] = self.fingerprints.map { $0.outgoingColibriDescription() } - } - - if !self.candidates.isEmpty { - result["candidates"] = self.candidates.map { $0.outgoingColibriDescription() } - } - - return result - } -} - -private extension ConferenceDescription.ChannelBundle { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["id"] = self.id - result["transport"] = self.transport.outgoingColibriDescription() - - return result - } -} - -private struct RemoteOffer { - struct State: Equatable { - struct Item: Equatable { - var isMain: Bool - var audioSsrc: Int - var videoSsrc: Int? - var isRemoved: Bool - } - - var items: [Item] - } - - var sdpList: [String] - var state: State -} - -private extension ConferenceDescription { - func outgoingColibriDescription() -> [String: Any] { - var result: [String: Any] = [:] - - result["id"] = self.id - result["contents"] = self.contents.map { $0.outgoingColibriDescription() } - result["channel-bundles"] = self.channelBundles.map { $0.outgoingColibriDescription() } - - return result - } - - func offerSdp(sessionId: UInt32, bundleId: String, bridgeHost: String, transport: ConferenceDescription.Transport, currentState: RemoteOffer.State?, isAnswer: Bool) -> RemoteOffer? { - struct StreamSpec { - var isMain: Bool - var audioSsrc: Int - var videoSsrc: Int? - var isRemoved: Bool - } - - func createSdp(sessionId: UInt32, bundleStreams: [StreamSpec]) -> String { - var sdp = "" - func appendSdp(_ string: String) { - if !sdp.isEmpty { - sdp.append("\n") - } - sdp.append(string) - } - - appendSdp("v=0") - appendSdp("o=- \(sessionId) 2 IN IP4 0.0.0.0") - appendSdp("s=-") - appendSdp("t=0 0") - - var bundleString = "a=group:BUNDLE" - for stream in bundleStreams { - bundleString.append(" ") - if let videoSsrc = stream.videoSsrc { - let audioMid: String - let videoMid: String - if stream.isMain { - audioMid = "0" - videoMid = "1" - } else { - audioMid = "audio\(stream.audioSsrc)" - videoMid = "video\(videoSsrc)" - } - bundleString.append("\(audioMid) \(videoMid)") - } else { - let audioMid: String - if stream.isMain { - audioMid = "0" - } else { - audioMid = "audio\(stream.audioSsrc)" - } - bundleString.append("\(audioMid)") - } - } - appendSdp(bundleString) - - appendSdp("a=ice-lite") - - for stream in bundleStreams { - let audioMid: String - let videoMid: String? - if stream.isMain { - audioMid = "0" - if let _ = stream.videoSsrc { - videoMid = "1" - } else { - videoMid = nil - } - } else { - audioMid = "audio\(stream.audioSsrc)" - if let videoSsrc = stream.videoSsrc { - videoMid = "video\(videoSsrc)" - } else { - videoMid = nil - } - } - - appendSdp("m=audio \(stream.isMain ? "1" : "0") RTP/SAVPF 111 126") - if stream.isMain { - appendSdp("c=IN IP4 0.0.0.0") - } - appendSdp("a=mid:\(audioMid)") - if stream.isRemoved { - appendSdp("a=inactive") - } else { - if stream.isMain { - appendSdp("a=ice-ufrag:\(transport.ufrag)") - appendSdp("a=ice-pwd:\(transport.pwd)") - - for fingerprint in transport.fingerprints { - appendSdp("a=fingerprint:\(fingerprint.hashType) \(fingerprint.fingerprint)") - //appendSdp("a=setup:\(fingerprint.setup)") - //appendSdp("a=setup:active") - appendSdp("a=setup:passive") - } - - for candidate in transport.candidates { - var candidateString = "a=candidate:" - candidateString.append("\(candidate.foundation) ") - candidateString.append("\(candidate.component) ") - var protocolValue = candidate.protocol - if protocolValue == "ssltcp" { - protocolValue = "tcp" - } - candidateString.append("\(protocolValue) ") - candidateString.append("\(candidate.priority) ") - - var ip = candidate.ip - if ip.hasPrefix("192.") { - ip = bridgeHost - } else { - continue - } - candidateString.append("\(ip) ") - candidateString.append("\(candidate.port) ") - - candidateString.append("typ \(candidate.type) ") - - switch candidate.type { - case "srflx", "prflx", "relay": - if let relAddr = candidate.relAddr, let relPort = candidate.relPort { - candidateString.append("raddr \(relAddr) rport \(relPort) ") - } - break - default: - break - } - - if protocolValue == "tcp" { - guard let tcpType = candidate.tcpType else { - continue - } - candidateString.append("tcptype \(tcpType) ") - } - - candidateString.append("generation \(candidate.generation)") - - appendSdp(candidateString) - } - } - - appendSdp("a=rtpmap:111 opus/48000/2") - appendSdp("a=rtpmap:126 telephone-event/8000") - appendSdp("a=fmtp:111 minptime=10; useinbandfec=1; usedtx=1") - appendSdp("a=rtcp:1 IN IP4 0.0.0.0") - appendSdp("a=rtcp-mux") - appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") - appendSdp("a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") - appendSdp("a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") - appendSdp("a=rtcp-fb:111 transport-cc") - - if isAnswer { - appendSdp("a=recvonly") - } else { - if stream.isMain { - appendSdp("a=sendrecv") - } else { - appendSdp("a=sendonly") - appendSdp("a=bundle-only") - } - - appendSdp("a=ssrc-group:FID \(stream.audioSsrc)") - appendSdp("a=ssrc:\(stream.audioSsrc) cname:stream\(stream.audioSsrc)") - appendSdp("a=ssrc:\(stream.audioSsrc) msid:stream\(stream.audioSsrc) audio\(stream.audioSsrc)") - appendSdp("a=ssrc:\(stream.audioSsrc) mslabel:audio\(stream.audioSsrc)") - appendSdp("a=ssrc:\(stream.audioSsrc) label:audio\(stream.audioSsrc)") - } - } - - if let videoMid = videoMid { - appendSdp("m=video 0 RTP/SAVPF 100") - appendSdp("a=mid:\(videoMid)") - if stream.isRemoved { - appendSdp("a=inactive") - continue - } else { - appendSdp("a=rtpmap:100 VP8/90000") - appendSdp("a=fmtp:100 x-google-start-bitrate=800") - appendSdp("a=rtcp:1 IN IP4 0.0.0.0") - appendSdp("a=rtcp-mux") - - appendSdp("a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") - appendSdp("a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") - - appendSdp("a=rtcp-fb:100 transport-cc") - appendSdp("a=rtcp-fb:100 ccm fir") - appendSdp("a=rtcp-fb:100 nack") - appendSdp("a=rtcp-fb:100 nack pli") - - if stream.isMain { - appendSdp("a=sendrecv") - } else { - appendSdp("a=sendonly") - } - appendSdp("a=bundle-only") - - appendSdp("a=ssrc-group:FID \(stream.videoSsrc)") - appendSdp("a=ssrc:\(stream.videoSsrc) cname:stream\(stream.audioSsrc)") - appendSdp("a=ssrc:\(stream.videoSsrc) msid:stream\(stream.audioSsrc) video\(stream.videoSsrc)") - appendSdp("a=ssrc:\(stream.videoSsrc) mslabel:video\(stream.videoSsrc)") - appendSdp("a=ssrc:\(stream.videoSsrc) label:video\(stream.videoSsrc)") - } - } - } - - appendSdp("") - - return sdp - } - - var streams: [StreamSpec] = [] - var maybeMainStreamAudioSsrc: Int? - - for audioContent in self.contents { - if audioContent.name != "audio" { - continue - } - for audioChannel in audioContent.channels { - if audioChannel.channelBundleId == bundleId { - precondition(audioChannel.sources.count == 1) - streams.append(StreamSpec( - isMain: true, - audioSsrc: audioChannel.sources[0], - videoSsrc: nil, - isRemoved: false - )) - maybeMainStreamAudioSsrc = audioChannel.sources[0] - if false && isAnswer { - precondition(audioChannel.ssrcs.count <= 1) - if audioChannel.ssrcs.count == 1 { - streams.append(StreamSpec( - isMain: false, - audioSsrc: audioChannel.ssrcs[0], - videoSsrc: nil, - isRemoved: false - )) - } - } - } else { - precondition(audioChannel.ssrcs.count <= 1) - if audioChannel.ssrcs.count == 1 { - streams.append(StreamSpec( - isMain: false, - audioSsrc: audioChannel.ssrcs[0], - videoSsrc: nil, - isRemoved: false - )) - } - } - - /*for videoContent in self.contents { - if videoContent.name != "video" { - continue - } - for videoChannel in videoContent.channels { - if videoChannel.channelBundleId == audioChannel.channelBundleId { - if audioChannel.channelBundleId == bundleId { - precondition(audioChannel.sources.count == 1) - precondition(videoChannel.sources.count == 1) - streams.append(StreamSpec( - isMain: true, - audioSsrc: audioChannel.sources[0], - videoSsrc: videoChannel.sources[0], - isRemoved: false - )) - maybeMainStreamAudioSsrc = audioChannel.sources[0] - } else { - precondition(audioChannel.ssrcs.count <= 1) - precondition(videoChannel.ssrcs.count <= 2) - if audioChannel.ssrcs.count == 1 && videoChannel.ssrcs.count <= 2 { - streams.append(StreamSpec( - isMain: false, - audioSsrc: audioChannel.ssrcs[0], - videoSsrc: videoChannel.ssrcs[0], - isRemoved: false - )) - } - } - } - } - }*/ - } - } - - guard let mainStreamAudioSsrc = maybeMainStreamAudioSsrc else { - preconditionFailure() - } - - var bundleStreams: [StreamSpec] = [] - if let currentState = currentState { - for item in currentState.items { - let isRemoved = !streams.contains(where: { $0.audioSsrc == item.audioSsrc }) - bundleStreams.append(StreamSpec( - isMain: item.audioSsrc == mainStreamAudioSsrc, - audioSsrc: item.audioSsrc, - videoSsrc: item.videoSsrc, - isRemoved: isRemoved - )) - } - } - - for stream in streams { - if bundleStreams.contains(where: { $0.audioSsrc == stream.audioSsrc }) { - continue - } - bundleStreams.append(stream) - } - - var sdpList: [String] = [] - - sdpList.append(createSdp(sessionId: sessionId, bundleStreams: bundleStreams)) - - return RemoteOffer( - sdpList: sdpList, - state: RemoteOffer.State( - items: bundleStreams.map { stream in - RemoteOffer.State.Item( - isMain: stream.isMain, - audioSsrc: stream.audioSsrc, - videoSsrc: stream.videoSsrc, - isRemoved: stream.isRemoved - ) - } - ) - ) - } - - mutating func updateLocalChannelFromSdpAnswer(bundleId: String, sdpAnswer: String) { - var maybeAudioChannel: ConferenceDescription.Content.Channel? - var videoChannel: ConferenceDescription.Content.Channel? - for content in self.contents { - for channel in content.channels { - if channel.endpoint == bundleId { - if content.name == "audio" { - maybeAudioChannel = channel - } else if content.name == "video" { - videoChannel = channel - } - break - } - } - } - - guard var audioChannel = maybeAudioChannel else { - assert(false) - return - } - - let lines = sdpAnswer.components(separatedBy: "\n") - - var videoLines: [String] = [] - var audioLines: [String] = [] - var isAudioLine = false - var isVideoLine = false - for line in lines { - if line.hasPrefix("m=audio") { - isAudioLine = true - isVideoLine = false - } else if line.hasPrefix("m=video") { - isVideoLine = true - isAudioLine = false - } - - if isAudioLine { - audioLines.append(line) - } else if isVideoLine { - videoLines.append(line) - } - } - - func getLines(prefix: String) -> [String] { - var result: [String] = [] - for line in lines { - if line.hasPrefix(prefix) { - var cleanLine = String(line[line.index(line.startIndex, offsetBy: prefix.count)...]) - if cleanLine.hasSuffix("\r") { - cleanLine.removeLast() - } - result.append(cleanLine) - } - } - return result - } - - func getLines(prefix: String, isAudio: Bool) -> [String] { - var result: [String] = [] - for line in (isAudio ? audioLines : videoLines) { - if line.hasPrefix(prefix) { - var cleanLine = String(line[line.index(line.startIndex, offsetBy: prefix.count)...]) - if cleanLine.hasSuffix("\r") { - cleanLine.removeLast() - } - result.append(cleanLine) - } - } - return result - } - - var audioSources: [Int] = [] - var videoSources: [Int] = [] - for line in getLines(prefix: "a=ssrc:", isAudio: true) { - let scanner = Scanner(string: line) - if #available(iOS 13.0, *) { - if let ssrc = scanner.scanInt() { - if !audioSources.contains(ssrc) { - audioSources.append(ssrc) - } - } - } - } - for line in getLines(prefix: "a=ssrc:", isAudio: false) { - let scanner = Scanner(string: line) - if #available(iOS 13.0, *) { - if let ssrc = scanner.scanInt() { - if !videoSources.contains(ssrc) { - videoSources.append(ssrc) - } - } - } - } - - audioChannel.sources = audioSources - - audioChannel.payloadTypes = [ - ConferenceDescription.Content.Channel.PayloadType( - id: 111, - name: "opus", - clockrate: 48000, - channels: 2, - parameters: [ - "fmtp": [ - "minptime=10;useinbandfec=1" - ] as [Any], - "rtcp-fbs": [ - [ - "type": "transport-cc" - ] as [String: Any], - ] as [Any] - ] - ), - ConferenceDescription.Content.Channel.PayloadType( - id: 126, - name: "telephone-event", - clockrate: 8000, - channels: 1 - ) - ] - - audioChannel.rtpHdrExts = [ - ConferenceDescription.Content.Channel.RtpHdrExt( - id: 1, - uri: "urn:ietf:params:rtp-hdrext:ssrc-audio-level" - ), - ConferenceDescription.Content.Channel.RtpHdrExt( - id: 3, - uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" - ), - ConferenceDescription.Content.Channel.RtpHdrExt( - id: 5, - uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" - ), - ] - - videoChannel?.sources = videoSources - /*audioChannel.ssrcGroups = [ConferenceDescription.Content.Channel.SsrcGroup( - sources: audioSources, - semantics: "SIM" - )]*/ - - videoChannel?.payloadTypes = [ - ConferenceDescription.Content.Channel.PayloadType( - id: 100, - name: "VP8", - clockrate: 9000, - channels: 1, - parameters: [ - "fmtp": [ - "x-google-start-bitrate=800" - ] as [Any], - "rtcp-fbs": [ - [ - "type": "transport-cc" - ] as [String: Any], - [ - "type": "ccm fir" - ] as [String: Any], - [ - "type": "nack" - ] as [String: Any], - [ - "type": "nack pli" - ] as [String: Any], - ] as [Any] - ] - ) - ] - - audioChannel.rtpHdrExts = [ - ConferenceDescription.Content.Channel.RtpHdrExt( - id: 2, - uri: "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time" - ), - ConferenceDescription.Content.Channel.RtpHdrExt( - id: 4, - uri: "http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01" - ), - ] - - guard let ufrag = getLines(prefix: "a=ice-ufrag:").first else { - assert(false) - return - } - guard let pwd = getLines(prefix: "a=ice-pwd:").first else { - assert(false) - return - } - - var fingerprints: [ConferenceDescription.Transport.Fingerprint] = [] - for line in getLines(prefix: "a=fingerprint:") { - let components = line.components(separatedBy: " ") - if components.count != 2 { - continue - } - fingerprints.append(ConferenceDescription.Transport.Fingerprint( - fingerprint: components[1], - setup: "active", - hashType: components[0] - )) - } - - for i in 0 ..< self.contents.count { - for j in 0 ..< self.contents[i].channels.count { - if self.contents[i].channels[j].endpoint == bundleId { - if self.contents[i].name == "audio" { - self.contents[i].channels[j] = audioChannel - } else if self.contents[i].name == "video", let videoChannel = videoChannel { - self.contents[i].channels[j] = videoChannel - } - } - } - } - - var candidates: [ConferenceDescription.Transport.Candidate] = [] - /*for line in getLines(prefix: "a=candidate:") { - let scanner = Scanner(string: line) - if #available(iOS 13.0, *) { - candidates.append(ConferenceDescription.Transport.Candidate( - id: "", - generation: 0, - component: "", - protocol: "", - tcpType: nil, - ip: "", - port: 0, - foundation: "", - priority: 0, - type: "", - network: 0, - relAddr: nil, - relPort: nil - )) - } - }*/ - - let transport = ConferenceDescription.Transport( - candidates: candidates, - fingerprints: fingerprints, - ufrag: ufrag, - pwd: pwd - ) - - var found = false - for i in 0 ..< self.channelBundles.count { - if self.channelBundles[i].id == bundleId { - self.channelBundles[i].transport = transport - found = true - break - } - } - if !found { - self.channelBundles.append(ConferenceDescription.ChannelBundle( - id: bundleId, - transport: transport - )) - } - } -} - -private enum HttpError { - case generic - case network - case server(String) -} - -private enum HttpMethod { - case get - case post([String: Any]) - case patch([String: Any]) -} - -private func httpJsonRequest(url: String, method: HttpMethod, resultType: T.Type) -> Signal { - return Signal { subscriber in - guard let url = URL(string: url) else { - subscriber.putError(.generic) - return EmptyDisposable - } - let completed = Atomic(value: false) - - var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalAndRemoteCacheData, timeoutInterval: 1000.0) - - switch method { - case .get: - break - case let .post(data): - guard let body = try? JSONSerialization.data(withJSONObject: data, options: []) else { - subscriber.putError(.generic) - return EmptyDisposable - } - - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = body - request.httpMethod = "POST" - case let .patch(data): - guard let body = try? JSONSerialization.data(withJSONObject: data, options: []) else { - subscriber.putError(.generic) - return EmptyDisposable - } - - request.setValue("application/json", forHTTPHeaderField: "Content-Type") - request.httpBody = body - request.httpMethod = "PATCH" - - //print("PATCH: \(String(data: body, encoding: .utf8)!)") - } - - let task = URLSession.shared.dataTask(with: request, completionHandler: { data, _, error in - if let error = error { - print("\(error)") - subscriber.putError(.server("\(error)")) - return - } - - let _ = completed.swap(true) - if let data = data, let json = try? JSONSerialization.jsonObject(with: data, options: []) as? T { - subscriber.putNext(json) - subscriber.putCompletion() - } else { - subscriber.putError(.network) - } - }) - task.resume() - - return ActionDisposable { - if !completed.with({ $0 }) { - task.cancel() - } - } - } -} - -public final class GroupCallContext { - private final class Impl { - private let queue: Queue - private let context: GroupCallThreadLocalContext - private let disposable = MetaDisposable() - - private let colibriHost: String - private let sessionId: UInt32 - - private var audioSessionDisposable: Disposable? - private let pollDisposable = MetaDisposable() - - private var conferenceId: String? - private var localBundleId: String? - private var localTransport: ConferenceDescription.Transport? - - let memberCount = ValuePromise(0, ignoreRepeated: true) - let videoStreamList = ValuePromise<[String]>([], ignoreRepeated: true) - - private var isMutedValue: Bool = false - let isMuted = ValuePromise(false, ignoreRepeated: true) - - init(queue: Queue, audioSessionActive: Signal, video: OngoingCallVideoCapturer?) { - self.queue = queue - - self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) - self.colibriHost = "192.168.93.24" - - var relaySdpAnswerImpl: ((String) -> Void)? - - let videoStreamList = self.videoStreamList - - self.context = GroupCallThreadLocalContext(queue: ContextQueueImpl(queue: queue), relaySdpAnswer: { sdpAnswer in - queue.async { - relaySdpAnswerImpl?(sdpAnswer) - } - }, incomingVideoStreamListUpdated: { streamList in - queue.async { - videoStreamList.set(streamList) - } - }, videoCapturer: video?.impl) - - relaySdpAnswerImpl = { [weak self] sdpAnswer in - guard let strongSelf = self else { - return - } - strongSelf.relaySdpAnswer(sdpAnswer: sdpAnswer) - } - - self.audioSessionDisposable = (audioSessionActive - |> filter { $0 } - |> take(1) - |> deliverOn(queue)).start(next: { [weak self] _ in - guard let strongSelf = self else { - return - } - - strongSelf.requestConference() - }) - } - - deinit { - self.disposable.dispose() - self.audioSessionDisposable?.dispose() - self.pollDisposable.dispose() - } - - func requestConference() { - self.disposable.set((httpJsonRequest(url: "http://\(self.colibriHost):8080/colibri/conferences/", method: .get, resultType: [Any].self) - |> deliverOn(self.queue)).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - - if let conferenceJson = result.first as? [String: Any] { - if let conferenceId = ConferenceDescription(json: conferenceJson)?.id { - strongSelf.disposable.set((httpJsonRequest(url: "http://\(strongSelf.colibriHost):8080/colibri/conferences/\(conferenceId)", method: .get, resultType: [String: Any].self) - |> deliverOn(strongSelf.queue)).start(next: { result in - guard let strongSelf = self else { - return - } - if let conference = ConferenceDescription(json: result) { - strongSelf.allocateChannels(conference: conference) - } - })) - } - } else { - strongSelf.disposable.set((httpJsonRequest(url: "http://\(strongSelf.colibriHost):8080/colibri/conferences/", method: .post([:]), resultType: [String: Any].self) - |> deliverOn(strongSelf.queue)).start(next: { result in - guard let strongSelf = self else { - return - } - if let conference = ConferenceDescription(json: result) { - strongSelf.allocateChannels(conference: conference) - } - })) - } - })) - } - - private var currentOfferState: RemoteOffer.State? - - func allocateChannels(conference: ConferenceDescription) { - let bundleId = UUID().uuidString - - var conference = conference - let audioChannel = ConferenceDescription.Content.Channel( - id: nil, - endpoint: bundleId, - channelBundleId: bundleId, - sources: [], - ssrcs: [], - rtpLevelRelayType: "translator", - expire: 10, - initiator: true, - direction: "sendrecv", - ssrcGroups: [], - payloadTypes: [], - rtpHdrExts: [] - ) - let videoChannel: ConferenceDescription.Content.Channel? = nil /*ConferenceDescription.Content.Channel( - id: nil, - endpoint: bundleId, - channelBundleId: bundleId, - sources: [], - ssrcs: [], - rtpLevelRelayType: "translator", - expire: 10, - initiator: true, - direction: "sendrecv", - ssrcGroups: [], - payloadTypes: [], - rtpHdrExts: [] - )*/ - - var foundAudioContent = false - var foundVideoContent = false - for i in 0 ..< conference.contents.count { - if conference.contents[i].name == "audio" { - for j in 0 ..< conference.contents[i].channels.count { - let channel = conference.contents[i].channels[j] - conference.contents[i].channels[j] = ConferenceDescription.Content.Channel( - id: channel.id, - endpoint: channel.endpoint, - channelBundleId: channel.channelBundleId, - sources: channel.sources, - ssrcs: channel.ssrcs, - rtpLevelRelayType: channel.rtpLevelRelayType, - expire: channel.expire, - initiator: channel.initiator, - direction: channel.direction, - ssrcGroups: [], - payloadTypes: [], - rtpHdrExts: [] - ) - } - conference.contents[i].channels.append(audioChannel) - foundAudioContent = true - break - } else if conference.contents[i].name == "video", let videoChannel = videoChannel { - for j in 0 ..< conference.contents[i].channels.count { - let channel = conference.contents[i].channels[j] - conference.contents[i].channels[j] = ConferenceDescription.Content.Channel( - id: channel.id, - endpoint: channel.endpoint, - channelBundleId: channel.channelBundleId, - sources: channel.sources, - ssrcs: channel.ssrcs, - rtpLevelRelayType: channel.rtpLevelRelayType, - expire: channel.expire, - initiator: channel.initiator, - direction: channel.direction, - ssrcGroups: [], - payloadTypes: [], - rtpHdrExts: [] - ) - } - conference.contents[i].channels.append(videoChannel) - foundVideoContent = true - break - } - } - if !foundAudioContent { - conference.contents.append(ConferenceDescription.Content( - name: "audio", - channels: [audioChannel] - )) - } - if !foundVideoContent, let videoChannel = videoChannel { - conference.contents.append(ConferenceDescription.Content( - name: "video", - channels: [videoChannel] - )) - } - conference.channelBundles.append(ConferenceDescription.ChannelBundle( - id: bundleId, - transport: ConferenceDescription.Transport( - candidates: [], - fingerprints: [], - ufrag: "", - pwd: "" - ) - )) - - var payload = conference.outgoingColibriDescription() - if var contents = payload["contents"] as? [[String: Any]] { - for contentIndex in 0 ..< contents.count { - if var channels = contents[contentIndex]["channels"] as? [Any] { - for i in (0 ..< channels.count).reversed() { - if var channel = channels[i] as? [String: Any] { - if channel["endpoint"] as? String != bundleId { - channel = ["id": channel["id"]!] - channels[i] = channel - channels.remove(at: i) - } - } - } - contents[contentIndex]["channels"] = channels - } - } - payload["contents"] = contents - } - - self.disposable.set((httpJsonRequest(url: "http://\(self.colibriHost):8080/colibri/conferences/\(conference.id)", method: .patch(payload), resultType: [String: Any].self) - |> deliverOn(self.queue)).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - - guard let conference = ConferenceDescription(json: result) else { - return - } - - var maybeTransport: ConferenceDescription.Transport? - for channelBundle in conference.channelBundles { - if channelBundle.id == bundleId { - maybeTransport = channelBundle.transport - break - } - } - - guard let transport = maybeTransport else { - assert(false) - return - } - - strongSelf.conferenceId = conference.id - strongSelf.localBundleId = bundleId - strongSelf.localTransport = transport - - let queue = strongSelf.queue - strongSelf.context.emitOffer(adjustSdp: { sdp in - return sdp - }, completion: { offerSdp in - queue.async { - guard let strongSelf = self else { - return - } - strongSelf.relaySdpAnswer(sdpAnswer: offerSdp) - } - }) - })) - } - - private func relaySdpAnswer(sdpAnswer: String) { - guard let conferenceId = self.conferenceId, let localBundleId = self.localBundleId else { - return - } - - print("===== relaySdpAnswer =====") - print(sdpAnswer) - print("===== -------------- =====") - - self.disposable.set((httpJsonRequest(url: "http://\(self.colibriHost):8080/colibri/conferences/\(conferenceId)", method: .get, resultType: [String: Any].self) - |> deliverOn(self.queue)).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - - guard var conference = ConferenceDescription(json: result) else { - return - } - - conference.updateLocalChannelFromSdpAnswer(bundleId: localBundleId, sdpAnswer: sdpAnswer) - - var payload = conference.outgoingColibriDescription() - if var contents = payload["contents"] as? [[String: Any]] { - for contentIndex in 0 ..< contents.count { - if var channels = contents[contentIndex]["channels"] as? [Any] { - for i in (0 ..< channels.count).reversed() { - if var channel = channels[i] as? [String: Any] { - if channel["endpoint"] as? String != localBundleId { - channel = ["id": channel["id"]!] - channels[i] = channel - channels.remove(at: i) - } - } - } - contents[contentIndex]["channels"] = channels - } - } - payload["contents"] = contents - } - - strongSelf.disposable.set((httpJsonRequest(url: "http://\(strongSelf.colibriHost):8080/colibri/conferences/\(conference.id)", method: .patch(payload), resultType: [String: Any].self) - |> deliverOn(strongSelf.queue)).start(next: { result in - guard let strongSelf = self else { - return - } - - guard let conference = ConferenceDescription(json: result) else { - return - } - - guard let localTransport = strongSelf.localTransport else { - return - } - - if conference.id == strongSelf.conferenceId { - if let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: localBundleId, bridgeHost: strongSelf.colibriHost, transport: localTransport, currentState: strongSelf.currentOfferState, isAnswer: true) { - //strongSelf.currentOfferState = offer.state - - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) - - for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: true) - } - } - - strongSelf.queue.after(2.0, { - self?.pollOnceDelayed() - }) - } - })) - })) - } - - private func pollOnceDelayed() { - guard let conferenceId = self.conferenceId, let localBundleId = self.localBundleId, let localTransport = self.localTransport else { - return - } - self.pollDisposable.set((httpJsonRequest(url: "http://\(self.colibriHost):8080/colibri/conferences/\(conferenceId)", method: .get, resultType: [String: Any].self) - |> delay(1.0, queue: self.queue) - |> deliverOn(self.queue)).start(next: { [weak self] result in - guard let strongSelf = self else { - return - } - - guard let conference = ConferenceDescription(json: result) else { - return - } - - guard conference.id == strongSelf.conferenceId else { - return - } - - if let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: localBundleId, bridgeHost: strongSelf.colibriHost, transport: localTransport, currentState: strongSelf.currentOfferState, isAnswer: false) { - strongSelf.currentOfferState = offer.state - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) - - for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: false) - } - - strongSelf.pollOnceDelayed() - - /*strongSelf.context.emitOffer(adjustSdp: { sdp in - var resultLines: [String] = [] - - func appendSdp(_ line: String) { - resultLines.append(line) - } - - var sharedPort = "0" - - let lines = sdp.components(separatedBy: "\n") - for line in lines { - var cleanLine = line - if cleanLine.hasSuffix("\r") { - cleanLine.removeLast() - } - if cleanLine.isEmpty { - continue - } - - if cleanLine.hasPrefix("a=group:BUNDLE 0 1") { - cleanLine = "a=group:BUNDLE 0 1" - - for item in offer.state.items { - if item.isMain { - continue - } - cleanLine.append(" audio\(item.audioSsrc) video\(item.videoSsrc)") - } - } else if cleanLine.hasPrefix("m=audio ") { - let scanner = Scanner(string: cleanLine) - if #available(iOS 13.0, *) { - if let _ = scanner.scanString("m=audio "), let value = scanner.scanInt() { - sharedPort = "\(value)" - } - } - } - - appendSdp(cleanLine) - } - - for item in offer.state.items { - if item.isMain { - continue - } - - let audioSsrc = item.audioSsrc - let videoSsrc = item.videoSsrc - - appendSdp("m=audio \(sharedPort) RTP/SAVPF 111 126") - appendSdp("a=mid:audio\(audioSsrc)") - - appendSdp("a=rtpmap:111 opus/48000/2") - appendSdp("a=rtpmap:126 telephone-event/8000") - appendSdp("a=fmtp:111 minptime=10; useinbandfec=1; usedtx=1") - appendSdp("a=rtcp:1 IN IP4 0.0.0.0") - appendSdp("a=rtcp-mux") - appendSdp("a=extmap:1 urn:ietf:params:rtp-hdrext:ssrc-audio-level") - appendSdp("a=extmap:3 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") - appendSdp("a=extmap:5 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") - appendSdp("a=rtcp-fb:111 transport-cc") - - appendSdp("a=recvonly") - appendSdp("a=bundle-only") - - appendSdp("a=ssrc-group:FID \(audioSsrc)") - appendSdp("a=ssrc:\(audioSsrc) cname:stream\(audioSsrc)") - appendSdp("a=ssrc:\(audioSsrc) msid:stream\(audioSsrc) audio\(audioSsrc)") - appendSdp("a=ssrc:\(audioSsrc) mslabel:audio\(audioSsrc)") - appendSdp("a=ssrc:\(audioSsrc) label:audio\(audioSsrc)") - - appendSdp("m=video \(sharedPort) RTP/SAVPF 100") - appendSdp("a=mid:video\(videoSsrc)") - - appendSdp("a=rtpmap:100 VP8/90000") - appendSdp("a=fmtp:100 x-google-start-bitrate=800") - appendSdp("a=rtcp:1 IN IP4 0.0.0.0") - appendSdp("a=rtcp-mux") - - appendSdp("a=extmap:2 http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time") - appendSdp("a=extmap:4 http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01") - - appendSdp("a=rtcp-fb:100 transport-cc") - appendSdp("a=rtcp-fb:100 ccm fir") - appendSdp("a=rtcp-fb:100 nack") - appendSdp("a=rtcp-fb:100 nack pli") - - appendSdp("a=recvonly") - appendSdp("a=bundle-only") - - appendSdp("a=ssrc-group:FID \(videoSsrc)") - appendSdp("a=ssrc:\(videoSsrc) cname:stream\(audioSsrc)") - appendSdp("a=ssrc:\(videoSsrc) msid:stream\(audioSsrc) video\(videoSsrc)") - appendSdp("a=ssrc:\(videoSsrc) mslabel:video\(videoSsrc)") - appendSdp("a=ssrc:\(videoSsrc) label:video\(videoSsrc)") - } - - let resultSdp = resultLines.joined(separator: "\n") - print("------ modified local sdp ------") - print(resultSdp) - print("------") - return resultSdp - }, completion: { _ in - queue.async { - guard let strongSelf = self else { - return - } - - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) - - for sdp in offer.sdpList { - print("===== setOffer polled =====") - print(sdp) - print("===== -------------- =====") - strongSelf.context.setOfferSdp(sdp, isPartial: false) - } - - strongSelf.pollOnceDelayed() - } - })*/ - } - })) - } - - func toggleIsMuted() { - self.isMutedValue = !self.isMutedValue - self.isMuted.set(self.isMutedValue) - self.context.setIsMuted(self.isMutedValue) - } - - func setIsMuted(_ isMuted: Bool) { - if self.isMutedValue != isMuted { - self.isMutedValue = isMuted - self.isMuted.set(self.isMutedValue) - self.context.setIsMuted(self.isMutedValue) - } - } - - func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { - self.context.makeIncomingVideoView(withStreamId: id, completion: { view in - if let view = view { - completion(OngoingCallContextPresentationCallVideoView( - view: view, - setOnFirstFrameReceived: { [weak view] f in - view?.setOnFirstFrameReceived(f) - }, - getOrientation: { [weak view] in - if let view = view { - return OngoingCallVideoOrientation(view.orientation) - } else { - return .rotation0 - } - }, - getAspect: { [weak view] in - if let view = view { - return view.aspect - } else { - return 0.0 - } - }, - setOnOrientationUpdated: { [weak view] f in - view?.setOnOrientationUpdated { value, aspect in - f?(OngoingCallVideoOrientation(value), aspect) - } - }, - setOnIsMirroredUpdated: { [weak view] f in - view?.setOnIsMirroredUpdated { value in - f?(value) - } - } - )) - } else { - completion(nil) - } - }) - } - } - - private let queue = Queue() - private let impl: QueueLocalObject - - public init(audioSessionActive: Signal, video: OngoingCallVideoCapturer?) { - let queue = self.queue - self.impl = QueueLocalObject(queue: queue, generate: { - return Impl(queue: queue, audioSessionActive: audioSessionActive, video: video) - }) - } - - public var memberCount: Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - self.impl.with { impl in - disposable.set(impl.memberCount.get().start(next: { value in - subscriber.putNext(value) - })) - } - return disposable - } - } - - public var videoStreamList: Signal<[String], NoError> { - return Signal { subscriber in - let disposable = MetaDisposable() - self.impl.with { impl in - disposable.set(impl.videoStreamList.get().start(next: { value in - subscriber.putNext(value) - })) - } - return disposable - } - } - - public var isMuted: Signal { - return Signal { subscriber in - let disposable = MetaDisposable() - self.impl.with { impl in - disposable.set(impl.isMuted.get().start(next: { value in - subscriber.putNext(value) - })) - } - return disposable - } - } - - public func toggleIsMuted() { - self.impl.with { impl in - impl.toggleIsMuted() - } - } - - public func setIsMuted(_ isMuted: Bool) { - self.impl.with { impl in - impl.setIsMuted(isMuted) - } - } - - public func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { - self.impl.with { impl in - impl.makeIncomingVideoView(id: id, completion: completion) - } - } -} - private struct ParsedJoinPayload { var payload: String var audioSsrc: UInt32 @@ -2343,6 +432,15 @@ private func parseJoinResponseIntoSdp(sessionId: UInt32, mainStreamAudioSsrc: UI } public final class OngoingGroupCallContext { + public enum NetworkState { + case connecting + case connected + } + + public struct MemberState: Equatable { + public var isSpeaking: Bool + } + private final class Impl { let queue: Queue let context: GroupCallThreadLocalContext @@ -2353,15 +451,42 @@ public final class OngoingGroupCallContext { var otherSsrcs: [UInt32] = [] let joinPayload = Promise() + let networkState = ValuePromise(.connecting, ignoreRepeated: true) + let isMuted = ValuePromise(true, ignoreRepeated: true) + let memberStates = ValuePromise<[UInt32: MemberState]>([:], ignoreRepeated: true) init(queue: Queue) { self.queue = queue + var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)? + self.context = GroupCallThreadLocalContext(queue: ContextQueueImpl(queue: queue), relaySdpAnswer: { _ in }, incomingVideoStreamListUpdated: { _ in - }, videoCapturer: nil) + }, videoCapturer: nil, + networkStateUpdated: { state in + networkStateUpdatedImpl?(state) + }) let queue = self.queue + + networkStateUpdatedImpl = { [weak self] state in + queue.async { + guard let strongSelf = self else { + return + } + let mappedState: NetworkState + switch state { + case .connecting: + mappedState = .connecting + case .connected: + mappedState = .connected + @unknown default: + mappedState = .connecting + } + strongSelf.networkState.set(mappedState) + } + } + self.context.emitOffer(adjustSdp: { sdp in return sdp }, completion: { [weak self] offerSdp in @@ -2377,7 +502,7 @@ public final class OngoingGroupCallContext { }) } - func setJoinResponse(payload: String, ssrcs: [Int32]) { + func setJoinResponse(payload: String, ssrcs: [UInt32]) { guard let mainStreamAudioSsrc = self.mainStreamAudioSsrc else { return } @@ -2388,7 +513,7 @@ public final class OngoingGroupCallContext { } } - func addSsrcs(ssrcs: [Int32]) { + func addSsrcs(ssrcs: [UInt32]) { if ssrcs.isEmpty { return } @@ -2398,7 +523,7 @@ public final class OngoingGroupCallContext { guard let initialAnswerPayload = self.initialAnswerPayload else { return } - let mappedSsrcs = ssrcs.map(UInt32.init(bitPattern:)) + let mappedSsrcs = ssrcs var otherSsrcs = self.otherSsrcs for ssrc in mappedSsrcs { if ssrc == mainStreamAudioSsrc { @@ -2410,6 +535,11 @@ public final class OngoingGroupCallContext { } if self.otherSsrcs != otherSsrcs { self.otherSsrcs = otherSsrcs + var memberStatesValue: [UInt32: MemberState] = [:] + for ssrc in otherSsrcs { + memberStatesValue[ssrc] = MemberState(isSpeaking: false) + } + self.memberStates.set(memberStatesValue) if let sdp = parseJoinResponseIntoSdp(sessionId: self.sessionId, mainStreamAudioSsrc: mainStreamAudioSsrc, payload: initialAnswerPayload, isAnswer: false, otherSsrcs: self.otherSsrcs) { self.context.setOfferSdp(sdp, isPartial: false) @@ -2418,6 +548,8 @@ public final class OngoingGroupCallContext { } func setIsMuted(_ isMuted: Bool) { + self.isMuted.set(isMuted) + self.context.setIsMuted(isMuted) } } @@ -2436,6 +568,42 @@ public final class OngoingGroupCallContext { } } + public var networkState: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.networkState.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public var memberStates: Signal<[UInt32: MemberState], NoError> { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.memberStates.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + + public var isMuted: Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.isMuted.get().start(next: { value in + subscriber.putNext(value) + })) + } + return disposable + } + } + public init() { let queue = self.queue self.impl = QueueLocalObject(queue: queue, generate: { @@ -2449,13 +617,13 @@ public final class OngoingGroupCallContext { } } - public func setJoinResponse(payload: String, ssrcs: [Int32]) { + public func setJoinResponse(payload: String, ssrcs: [UInt32]) { self.impl.with { impl in impl.setJoinResponse(payload: payload, ssrcs: ssrcs) } } - public func addSsrcs(ssrcs: [Int32]) { + public func addSsrcs(ssrcs: [UInt32]) { self.impl.with { impl in impl.addSsrcs(ssrcs: ssrcs) } diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 03a03c6928..b8c3e41d6a 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -151,10 +151,14 @@ typedef NS_ENUM(int32_t, OngoingCallDataSavingWebrtc) { - (void)switchAudioInput:(NSString * _Nonnull)deviceId; @end +typedef NS_ENUM(int32_t, GroupCallNetworkState) { + GroupCallNetworkStateConnecting, + GroupCallNetworkStateConnected +}; @interface GroupCallThreadLocalContext : NSObject -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer; +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated; - (void)emitOfferWithAdjustSdp:(NSString * _Nonnull (^ _Nonnull)(NSString * _Nonnull))adjustSdp completion:(void (^ _Nonnull)(NSString * _Nonnull))completion; - (void)setOfferSdp:(NSString * _Nonnull)offerSdp isPartial:(bool)isPartial; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index 515b67b2d7..11808acb5e 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -800,18 +800,21 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; std::unique_ptr _instance; OngoingCallThreadLocalContextVideoCapturer *_videoCapturer; + + void (^_networkStateUpdated)(GroupCallNetworkState); } @end @implementation GroupCallThreadLocalContext -- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer { +- (instancetype _Nonnull)initWithQueue:(id _Nonnull)queue relaySdpAnswer:(void (^ _Nonnull)(NSString * _Nonnull))relaySdpAnswer incomingVideoStreamListUpdated:(void (^ _Nonnull)(NSArray * _Nonnull))incomingVideoStreamListUpdated videoCapturer:(OngoingCallThreadLocalContextVideoCapturer * _Nullable)videoCapturer networkStateUpdated:(void (^ _Nonnull)(GroupCallNetworkState))networkStateUpdated { self = [super init]; if (self != nil) { _queue = queue; _videoCapturer = videoCapturer; + _networkStateUpdated = [networkStateUpdated copy]; __weak GroupCallThreadLocalContext *weakSelf = self; _instance.reset(new tgcalls::GroupInstanceImpl((tgcalls::GroupInstanceDescriptor){ @@ -838,7 +841,16 @@ static void (*InternalVoipLoggingFunction)(NSString *) = NULL; incomingVideoStreamListUpdated(mappedList); }]; }, - .videoCapture = [_videoCapturer getInterface] + .videoCapture = [_videoCapturer getInterface], + .networkStateUpdated = [weakSelf, queue, networkStateUpdated](bool isConnected) { + [queue dispatch:^{ + __strong GroupCallThreadLocalContext *strongSelf = weakSelf; + if (strongSelf == nil) { + return; + } + networkStateUpdated(isConnected ? GroupCallNetworkStateConnected : GroupCallNetworkStateConnecting); + }]; + } })); } return self; diff --git a/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift b/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift index 70ed38eddd..0cd4a2c91a 100644 --- a/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift +++ b/submodules/WidgetSetupScreen/Sources/WidgetSetupScreen.swift @@ -150,16 +150,16 @@ private enum WidgetSetupScreenEntry: ItemListNodeEntry { case let .peerItem(_, dateTimeFormat, nameOrder, peer, editing, enabled): var text: ItemListPeerItemText = .none if let group = peer.peer as? TelegramGroup { - text = .text(presentationData.strings.Conversation_StatusMembers(Int32(group.participantCount))) + text = .text(presentationData.strings.Conversation_StatusMembers(Int32(group.participantCount)), .secondary) } else if let channel = peer.peer as? TelegramChannel { if let participantCount = peer.participantCount { - text = .text(presentationData.strings.Conversation_StatusMembers(Int32(participantCount))) + text = .text(presentationData.strings.Conversation_StatusMembers(Int32(participantCount)), .secondary) } else { switch channel.info { case .group: - text = .text(presentationData.strings.Group_Status) + text = .text(presentationData.strings.Group_Status, .secondary) case .broadcast: - text = .text(presentationData.strings.Channel_Status) + text = .text(presentationData.strings.Channel_Status, .secondary) } } }