diff --git a/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m b/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m index cce25b19cc..c855db593b 100644 --- a/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m +++ b/Telegram/NotificationService/NotificationServiceObjC/Sources/Serialization.m @@ -3,7 +3,7 @@ @implementation Serialization - (NSUInteger)currentLayer { - return 120; + return 122; } - (id _Nullable)parseMessage:(NSData * _Nullable)data { diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index a402df45d4..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,8 +156,59 @@ public protocol PresentationCall: class { func makeOutgoingVideoView(completion: @escaping (PresentationCallVideoView?) -> Void) } +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) -> 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/MergeLists/Sources/MergeLists.swift b/submodules/MergeLists/Sources/MergeLists.swift index 6dad3c27c4..0cd446a615 100644 --- a/submodules/MergeLists/Sources/MergeLists.swift +++ b/submodules/MergeLists/Sources/MergeLists.swift @@ -215,7 +215,7 @@ public func mergeListsStableWithUpdates(leftList: [T], rightList: [T], isLess for item in rightList { rightStableIds.append(getId(item)) } - if Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated { + if false && Set(leftStableIds) == Set(rightStableIds) && leftStableIds != rightStableIds && !allUpdated { var updatedItems: [(T, AnyHashable)] = [] for i in 0 ..< leftList.count { if getId(leftList[i]) != getId(rightList[i]) { 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/SyncCore/Sources/TelegramMediaAction.swift b/submodules/SyncCore/Sources/TelegramMediaAction.swift index 5e2d6757af..d1178621ad 100644 --- a/submodules/SyncCore/Sources/TelegramMediaAction.swift +++ b/submodules/SyncCore/Sources/TelegramMediaAction.swift @@ -46,6 +46,7 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { case peerJoined case phoneNumberRequest case geoProximityReached(from: PeerId, to: PeerId, distance: Int32) + case groupPhoneCall(callId: Int64, accessHash: Int64, duration: Int32?) public init(decoder: PostboxDecoder) { let rawValue: Int32 = decoder.decodeInt32ForKey("_rawValue", orElse: 0) @@ -98,6 +99,8 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { self = .phoneNumberRequest case 21: self = .geoProximityReached(from: PeerId(decoder.decodeInt64ForKey("fromId", orElse: 0)), to: PeerId(decoder.decodeInt64ForKey("toId", orElse: 0)), distance: (decoder.decodeInt32ForKey("dst", orElse: 0))) + case 22: + self = .groupPhoneCall(callId: decoder.decodeInt64ForKey("callId", orElse: 0), accessHash: decoder.decodeInt64ForKey("accessHash", orElse: 0), duration: decoder.decodeOptionalInt32ForKey("duration")) default: self = .unknown } @@ -188,6 +191,15 @@ public enum TelegramMediaActionType: PostboxCoding, Equatable { encoder.encodeInt64(from.toInt64(), forKey: "fromId") encoder.encodeInt64(to.toInt64(), forKey: "toId") encoder.encodeInt32(distance, forKey: "dst") + case let .groupPhoneCall(callId, accessHash, duration): + encoder.encodeInt32(22, forKey: "_rawValue") + encoder.encodeInt64(callId, forKey: "callId") + encoder.encodeInt64(accessHash, forKey: "accessHash") + if let duration = duration { + encoder.encodeInt32(duration, forKey: "duration") + } else { + encoder.encodeNil(forKey: "duration") + } } } diff --git a/submodules/TelegramApi/Sources/Api0.swift b/submodules/TelegramApi/Sources/Api0.swift index 08e6253e2b..9778f2de67 100644 --- a/submodules/TelegramApi/Sources/Api0.swift +++ b/submodules/TelegramApi/Sources/Api0.swift @@ -6,11 +6,14 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[571523412] = { return $0.readDouble() } dict[-1255641564] = { return parseString($0) } dict[-1240849242] = { return Api.messages.StickerSet.parse_stickerSet($0) } + dict[1829443076] = { return Api.GroupCall.parse_groupCallPrivate($0) } + dict[-857633264] = { return Api.GroupCall.parse_groupCall($0) } + dict[2004925620] = { return Api.GroupCall.parse_groupCallDiscarded($0) } dict[-457104426] = { return Api.InputGeoPoint.parse_inputGeoPointEmpty($0) } dict[1210199983] = { return Api.InputGeoPoint.parse_inputGeoPoint($0) } dict[-784000893] = { return Api.payments.ValidatedRequestedInfo.parse_validatedRequestedInfo($0) } dict[461151667] = { return Api.ChatFull.parse_chatFull($0) } - dict[-253335766] = { return Api.ChatFull.parse_channelFull($0) } + dict[-428758403] = { return Api.ChatFull.parse_channelFull($0) } dict[-1159937629] = { return Api.PollResults.parse_pollResults($0) } dict[-925415106] = { return Api.ChatParticipant.parse_chatParticipant($0) } dict[-636267638] = { return Api.ChatParticipant.parse_chatParticipantCreator($0) } @@ -132,6 +135,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1511503333] = { return Api.InputEncryptedFile.parse_inputEncryptedFile($0) } dict[767652808] = { return Api.InputEncryptedFile.parse_inputEncryptedFileBigUploaded($0) } dict[-1456996667] = { return Api.messages.InactiveChats.parse_inactiveChats($0) } + dict[-1513019911] = { return Api.GroupCallParticipant.parse_groupCallParticipantAdmin($0) } + dict[-1985949076] = { return Api.GroupCallParticipant.parse_groupCallParticipant($0) } + dict[1100680690] = { return Api.GroupCallParticipant.parse_groupCallParticipantLeft($0) } + dict[-1648085351] = { return Api.GroupCallParticipant.parse_groupCallParticipantKicked($0) } + dict[-874654354] = { return Api.GroupCallParticipant.parse_groupCallParticipantInvited($0) } dict[1443858741] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedMessage($0) } dict[-1802240206] = { return Api.messages.SentEncryptedMessage.parse_sentEncryptedFile($0) } dict[1571494644] = { return Api.ExportedMessageLink.parse_exportedMessageLink($0) } @@ -258,6 +266,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-13975905] = { return Api.Update.parse_updateChannelUserTyping($0) } dict[-309990731] = { return Api.Update.parse_updatePinnedMessages($0) } dict[-2054649973] = { return Api.Update.parse_updatePinnedChannelMessages($0) } + dict[92188360] = { return Api.Update.parse_updateGroupCallParticipant($0) } + dict[-2046916883] = { return Api.Update.parse_updateGroupCall($0) } dict[136574537] = { return Api.messages.VotesList.parse_votesList($0) } dict[1558266229] = { return Api.PopularContact.parse_popularContact($0) } dict[-373643672] = { return Api.FolderPeer.parse_folderPeer($0) } @@ -329,11 +339,11 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-392411726] = { return Api.WebPage.parse_webPage($0) } dict[1930545681] = { return Api.WebPage.parse_webPageNotModified($0) } dict[1036876423] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageText($0) } - dict[-190472735] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) } dict[1262639204] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageGame($0) } dict[864077702] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaAuto($0) } dict[1098628881] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaVenue($0) } dict[-1494368259] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaContact($0) } + dict[-1768777083] = { return Api.InputBotInlineMessage.parse_inputBotInlineMessageMediaGeo($0) } dict[2002815875] = { return Api.KeyboardButtonRow.parse_keyboardButtonRow($0) } dict[-290164953] = { return Api.StickerSet.parse_stickerSet($0) } dict[354925740] = { return Api.SecureSecretSettings.parse_secureSecretSettings($0) } @@ -392,6 +402,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-666824391] = { return Api.payments.PaymentResult.parse_paymentVerificationNeeded($0) } dict[1694474197] = { return Api.messages.Chats.parse_chats($0) } dict[-1663561404] = { return Api.messages.Chats.parse_chatsSlice($0) } + dict[-659913713] = { return Api.InputGroupCall.parse_inputGroupCall($0) } dict[482797855] = { return Api.InputSingleMedia.parse_inputSingleMedia($0) } dict[1163625789] = { return Api.MessageViews.parse_messageViews($0) } dict[218751099] = { return Api.InputPrivacyRule.parse_inputPrivacyValueAllowContacts($0) } @@ -496,6 +507,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1495959709] = { return Api.MessageReplyHeader.parse_messageReplyHeader($0) } dict[411017418] = { return Api.SecureValue.parse_secureValue($0) } dict[-316748368] = { return Api.SecureValueHash.parse_secureValueHash($0) } + dict[1731723191] = { return Api.phone.GroupCall.parse_groupCall($0) } dict[-398136321] = { return Api.messages.SearchCounter.parse_searchCounter($0) } dict[-2128698738] = { return Api.auth.CheckedPhone.parse_checkedPhone($0) } dict[-1188055347] = { return Api.PageListItem.parse_pageListItemText($0) } @@ -777,6 +789,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-648257196] = { return Api.MessageAction.parse_messageActionSecureValuesSent($0) } dict[-202219658] = { return Api.MessageAction.parse_messageActionContactSignUp($0) } dict[-1730095465] = { return Api.MessageAction.parse_messageActionGeoProximityReached($0) } + dict[2047704898] = { return Api.MessageAction.parse_messageActionGroupCall($0) } dict[1399245077] = { return Api.PhoneCall.parse_phoneCallEmpty($0) } dict[462375633] = { return Api.PhoneCall.parse_phoneCallWaiting($0) } dict[-2014659757] = { return Api.PhoneCall.parse_phoneCallRequested($0) } @@ -914,6 +927,8 @@ public struct Api { switch object { case let _1 as Api.messages.StickerSet: _1.serialize(buffer, boxed) + case let _1 as Api.GroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.InputGeoPoint: _1.serialize(buffer, boxed) case let _1 as Api.payments.ValidatedRequestedInfo: @@ -994,6 +1009,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.InactiveChats: _1.serialize(buffer, boxed) + case let _1 as Api.GroupCallParticipant: + _1.serialize(buffer, boxed) case let _1 as Api.messages.SentEncryptedMessage: _1.serialize(buffer, boxed) case let _1 as Api.ExportedMessageLink: @@ -1122,6 +1139,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.messages.Chats: _1.serialize(buffer, boxed) + case let _1 as Api.InputGroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.InputSingleMedia: _1.serialize(buffer, boxed) case let _1 as Api.MessageViews: @@ -1206,6 +1225,8 @@ public struct Api { _1.serialize(buffer, boxed) case let _1 as Api.SecureValueHash: _1.serialize(buffer, boxed) + case let _1 as Api.phone.GroupCall: + _1.serialize(buffer, boxed) case let _1 as Api.messages.SearchCounter: _1.serialize(buffer, boxed) case let _1 as Api.auth.CheckedPhone: diff --git a/submodules/TelegramApi/Sources/Api1.swift b/submodules/TelegramApi/Sources/Api1.swift index b500198b76..27f88bba3b 100644 --- a/submodules/TelegramApi/Sources/Api1.swift +++ b/submodules/TelegramApi/Sources/Api1.swift @@ -1909,6 +1909,134 @@ public struct messages { } } public extension Api { + public enum GroupCall: TypeConstructorDescription { + case groupCallPrivate(flags: Int32, id: Int64, accessHash: Int64, channelId: Int32?, participantsCount: Int32, adminId: Int32) + case groupCall(flags: Int32, id: Int64, accessHash: Int64, channelId: Int32?, adminId: Int32, reflectorId: Int64, params: Api.DataJSON?) + case groupCallDiscarded(id: Int64, accessHash: Int64, duration: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallPrivate(let flags, let id, let accessHash, let channelId, let participantsCount, let adminId): + if boxed { + buffer.appendInt32(1829443076) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} + serializeInt32(participantsCount, buffer: buffer, boxed: false) + serializeInt32(adminId, buffer: buffer, boxed: false) + break + case .groupCall(let flags, let id, let accessHash, let channelId, let adminId, let reflectorId, let params): + if boxed { + buffer.appendInt32(-857633264) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(channelId!, buffer: buffer, boxed: false)} + serializeInt32(adminId, buffer: buffer, boxed: false) + serializeInt64(reflectorId, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 1) != 0 {params!.serialize(buffer, true)} + break + case .groupCallDiscarded(let id, let accessHash, let duration): + if boxed { + buffer.appendInt32(2004925620) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeInt32(duration, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallPrivate(let flags, let id, let accessHash, let channelId, let participantsCount, let adminId): + return ("groupCallPrivate", [("flags", flags), ("id", id), ("accessHash", accessHash), ("channelId", channelId), ("participantsCount", participantsCount), ("adminId", adminId)]) + case .groupCall(let flags, let id, let accessHash, let channelId, let adminId, let reflectorId, let params): + return ("groupCall", [("flags", flags), ("id", id), ("accessHash", accessHash), ("channelId", channelId), ("adminId", adminId), ("reflectorId", reflectorId), ("params", params)]) + case .groupCallDiscarded(let id, let accessHash, let duration): + return ("groupCallDiscarded", [("id", id), ("accessHash", accessHash), ("duration", duration)]) + } + } + + public static func parse_groupCallPrivate(_ reader: BufferReader) -> GroupCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int32? + _6 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.GroupCall.groupCallPrivate(flags: _1!, id: _2!, accessHash: _3!, channelId: _4, participantsCount: _5!, adminId: _6!) + } + else { + return nil + } + } + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int64? + _3 = reader.readInt64() + var _4: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_4 = reader.readInt32() } + var _5: Int32? + _5 = reader.readInt32() + var _6: Int64? + _6 = reader.readInt64() + var _7: Api.DataJSON? + if Int(_1!) & Int(1 << 1) != 0 {if let signature = reader.readInt32() { + _7 = Api.parse(reader, signature: signature) as? Api.DataJSON + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = (Int(_1!) & Int(1 << 0) == 0) || _4 != nil + let _c5 = _5 != nil + let _c6 = _6 != nil + let _c7 = (Int(_1!) & Int(1 << 1) == 0) || _7 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 { + return Api.GroupCall.groupCall(flags: _1!, id: _2!, accessHash: _3!, channelId: _4, adminId: _5!, reflectorId: _6!, params: _7) + } + else { + return nil + } + } + public static func parse_groupCallDiscarded(_ reader: BufferReader) -> GroupCall? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Int32? + _3 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.GroupCall.groupCallDiscarded(id: _1!, accessHash: _2!, duration: _3!) + } + else { + return nil + } + } + + } public enum InputGeoPoint: TypeConstructorDescription { case inputGeoPointEmpty case inputGeoPoint(flags: Int32, lat: Double, long: Double, accuracyRadius: Int32?) @@ -1969,7 +2097,7 @@ public extension Api { } public enum ChatFull: TypeConstructorDescription { case chatFull(flags: Int32, id: Int32, about: String, participants: Api.ChatParticipants, chatPhoto: Api.Photo?, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo]?, pinnedMsgId: Int32?, folderId: Int32?) - case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32) + case channelFull(flags: Int32, id: Int32, about: String, participantsCount: Int32?, adminsCount: Int32?, kickedCount: Int32?, bannedCount: Int32?, onlineCount: Int32?, readInboxMaxId: Int32, readOutboxMaxId: Int32, unreadCount: Int32, chatPhoto: Api.Photo, notifySettings: Api.PeerNotifySettings, exportedInvite: Api.ExportedChatInvite, botInfo: [Api.BotInfo], migratedFromChatId: Int32?, migratedFromMaxId: Int32?, pinnedMsgId: Int32?, stickerset: Api.StickerSet?, availableMinId: Int32?, folderId: Int32?, linkedChatId: Int32?, location: Api.ChannelLocation?, slowmodeSeconds: Int32?, slowmodeNextSendDate: Int32?, statsDc: Int32?, pts: Int32, callMsgId: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -1992,9 +2120,9 @@ public extension Api { if Int(flags) & Int(1 << 6) != 0 {serializeInt32(pinnedMsgId!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 11) != 0 {serializeInt32(folderId!, buffer: buffer, boxed: false)} break - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts): + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let callMsgId): if boxed { - buffer.appendInt32(-253335766) + buffer.appendInt32(-428758403) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt32(id, buffer: buffer, boxed: false) @@ -2027,6 +2155,7 @@ public extension Api { if Int(flags) & Int(1 << 18) != 0 {serializeInt32(slowmodeNextSendDate!, buffer: buffer, boxed: false)} if Int(flags) & Int(1 << 12) != 0 {serializeInt32(statsDc!, buffer: buffer, boxed: false)} serializeInt32(pts, buffer: buffer, boxed: false) + if Int(flags) & Int(1 << 21) != 0 {serializeInt32(callMsgId!, buffer: buffer, boxed: false)} break } } @@ -2035,8 +2164,8 @@ public extension Api { switch self { case .chatFull(let flags, let id, let about, let participants, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let pinnedMsgId, let folderId): return ("chatFull", [("flags", flags), ("id", id), ("about", about), ("participants", participants), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("pinnedMsgId", pinnedMsgId), ("folderId", folderId)]) - case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts): - return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts)]) + case .channelFull(let flags, let id, let about, let participantsCount, let adminsCount, let kickedCount, let bannedCount, let onlineCount, let readInboxMaxId, let readOutboxMaxId, let unreadCount, let chatPhoto, let notifySettings, let exportedInvite, let botInfo, let migratedFromChatId, let migratedFromMaxId, let pinnedMsgId, let stickerset, let availableMinId, let folderId, let linkedChatId, let location, let slowmodeSeconds, let slowmodeNextSendDate, let statsDc, let pts, let callMsgId): + return ("channelFull", [("flags", flags), ("id", id), ("about", about), ("participantsCount", participantsCount), ("adminsCount", adminsCount), ("kickedCount", kickedCount), ("bannedCount", bannedCount), ("onlineCount", onlineCount), ("readInboxMaxId", readInboxMaxId), ("readOutboxMaxId", readOutboxMaxId), ("unreadCount", unreadCount), ("chatPhoto", chatPhoto), ("notifySettings", notifySettings), ("exportedInvite", exportedInvite), ("botInfo", botInfo), ("migratedFromChatId", migratedFromChatId), ("migratedFromMaxId", migratedFromMaxId), ("pinnedMsgId", pinnedMsgId), ("stickerset", stickerset), ("availableMinId", availableMinId), ("folderId", folderId), ("linkedChatId", linkedChatId), ("location", location), ("slowmodeSeconds", slowmodeSeconds), ("slowmodeNextSendDate", slowmodeNextSendDate), ("statsDc", statsDc), ("pts", pts), ("callMsgId", callMsgId)]) } } @@ -2155,6 +2284,8 @@ public extension Api { if Int(_1!) & Int(1 << 12) != 0 {_26 = reader.readInt32() } var _27: Int32? _27 = reader.readInt32() + var _28: Int32? + if Int(_1!) & Int(1 << 21) != 0 {_28 = reader.readInt32() } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil @@ -2182,8 +2313,9 @@ public extension Api { let _c25 = (Int(_1!) & Int(1 << 18) == 0) || _25 != nil let _c26 = (Int(_1!) & Int(1 << 12) == 0) || _26 != nil let _c27 = _27 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 { - return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!) + let _c28 = (Int(_1!) & Int(1 << 21) == 0) || _28 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 && _c10 && _c11 && _c12 && _c13 && _c14 && _c15 && _c16 && _c17 && _c18 && _c19 && _c20 && _c21 && _c22 && _c23 && _c24 && _c25 && _c26 && _c27 && _c28 { + return Api.ChatFull.channelFull(flags: _1!, id: _2!, about: _3!, participantsCount: _4, adminsCount: _5, kickedCount: _6, bannedCount: _7, onlineCount: _8, readInboxMaxId: _9!, readOutboxMaxId: _10!, unreadCount: _11!, chatPhoto: _12!, notifySettings: _13!, exportedInvite: _14!, botInfo: _15!, migratedFromChatId: _16, migratedFromMaxId: _17, pinnedMsgId: _18, stickerset: _19, availableMinId: _20, folderId: _21, linkedChatId: _22, location: _23, slowmodeSeconds: _24, slowmodeNextSendDate: _25, statsDc: _26, pts: _27!, callMsgId: _28) } else { return nil @@ -5266,6 +5398,148 @@ public extension Api { } } + } + public enum GroupCallParticipant: TypeConstructorDescription { + case groupCallParticipantAdmin(userId: Int32, source: Int32) + case groupCallParticipant(flags: Int32, userId: Int32, date: Int32, source: Int32) + case groupCallParticipantLeft(userId: Int32) + case groupCallParticipantKicked(userId: Int32) + case groupCallParticipantInvited(flags: Int32, userId: Int32, inviterId: Int32, date: Int32) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCallParticipantAdmin(let userId, let source): + if boxed { + buffer.appendInt32(-1513019911) + } + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(source, buffer: buffer, boxed: false) + break + case .groupCallParticipant(let flags, let userId, let date, let source): + if boxed { + buffer.appendInt32(-1985949076) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + serializeInt32(source, buffer: buffer, boxed: false) + break + case .groupCallParticipantLeft(let userId): + if boxed { + buffer.appendInt32(1100680690) + } + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .groupCallParticipantKicked(let userId): + if boxed { + buffer.appendInt32(-1648085351) + } + serializeInt32(userId, buffer: buffer, boxed: false) + break + case .groupCallParticipantInvited(let flags, let userId, let inviterId, let date): + if boxed { + buffer.appendInt32(-874654354) + } + serializeInt32(flags, buffer: buffer, boxed: false) + serializeInt32(userId, buffer: buffer, boxed: false) + serializeInt32(inviterId, buffer: buffer, boxed: false) + serializeInt32(date, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCallParticipantAdmin(let userId, let source): + return ("groupCallParticipantAdmin", [("userId", userId), ("source", source)]) + case .groupCallParticipant(let flags, let userId, let date, let source): + return ("groupCallParticipant", [("flags", flags), ("userId", userId), ("date", date), ("source", source)]) + case .groupCallParticipantLeft(let userId): + return ("groupCallParticipantLeft", [("userId", userId)]) + case .groupCallParticipantKicked(let userId): + return ("groupCallParticipantKicked", [("userId", userId)]) + case .groupCallParticipantInvited(let flags, let userId, let inviterId, let date): + return ("groupCallParticipantInvited", [("flags", flags), ("userId", userId), ("inviterId", inviterId), ("date", date)]) + } + } + + public static func parse_groupCallParticipantAdmin(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.GroupCallParticipant.groupCallParticipantAdmin(userId: _1!, source: _2!) + } + else { + return nil + } + } + public static func parse_groupCallParticipant(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.GroupCallParticipant.groupCallParticipant(flags: _1!, userId: _2!, date: _3!, source: _4!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantLeft(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.GroupCallParticipant.groupCallParticipantLeft(userId: _1!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantKicked(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + let _c1 = _1 != nil + if _c1 { + return Api.GroupCallParticipant.groupCallParticipantKicked(userId: _1!) + } + else { + return nil + } + } + public static func parse_groupCallParticipantInvited(_ reader: BufferReader) -> GroupCallParticipant? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int32? + _3 = reader.readInt32() + var _4: Int32? + _4 = reader.readInt32() + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.GroupCallParticipant.groupCallParticipantInvited(flags: _1!, userId: _2!, inviterId: _3!, date: _4!) + } + else { + return nil + } + } + } public enum ExportedMessageLink: TypeConstructorDescription { case exportedMessageLink(link: String, html: String) @@ -6193,6 +6467,8 @@ public extension Api { case updateChannelUserTyping(flags: Int32, channelId: Int32, topMsgId: Int32?, userId: Int32, action: Api.SendMessageAction) case updatePinnedMessages(flags: Int32, peer: Api.Peer, messages: [Int32], pts: Int32, ptsCount: Int32) case updatePinnedChannelMessages(flags: Int32, channelId: Int32, messages: [Int32], pts: Int32, ptsCount: Int32) + case updateGroupCallParticipant(call: Api.InputGroupCall, participant: Api.GroupCallParticipant) + case updateGroupCall(call: Api.GroupCall) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6928,6 +7204,19 @@ public extension Api { serializeInt32(pts, buffer: buffer, boxed: false) serializeInt32(ptsCount, buffer: buffer, boxed: false) break + case .updateGroupCallParticipant(let call, let participant): + if boxed { + buffer.appendInt32(92188360) + } + call.serialize(buffer, true) + participant.serialize(buffer, true) + break + case .updateGroupCall(let call): + if boxed { + buffer.appendInt32(-2046916883) + } + call.serialize(buffer, true) + break } } @@ -7103,6 +7392,10 @@ public extension Api { return ("updatePinnedMessages", [("flags", flags), ("peer", peer), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)]) case .updatePinnedChannelMessages(let flags, let channelId, let messages, let pts, let ptsCount): return ("updatePinnedChannelMessages", [("flags", flags), ("channelId", channelId), ("messages", messages), ("pts", pts), ("ptsCount", ptsCount)]) + case .updateGroupCallParticipant(let call, let participant): + return ("updateGroupCallParticipant", [("call", call), ("participant", participant)]) + case .updateGroupCall(let call): + return ("updateGroupCall", [("call", call)]) } } @@ -8575,6 +8868,37 @@ public extension Api { return nil } } + public static func parse_updateGroupCallParticipant(_ reader: BufferReader) -> Update? { + var _1: Api.InputGroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _2: Api.GroupCallParticipant? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.GroupCallParticipant + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.Update.updateGroupCallParticipant(call: _1!, participant: _2!) + } + else { + return nil + } + } + public static func parse_updateGroupCall(_ reader: BufferReader) -> Update? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + let _c1 = _1 != nil + if _c1 { + return Api.Update.updateGroupCall(call: _1!) + } + else { + return nil + } + } } public enum PopularContact: TypeConstructorDescription { @@ -10399,11 +10723,11 @@ public extension Api { } public enum InputBotInlineMessage: TypeConstructorDescription { case inputBotInlineMessageText(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?) - case inputBotInlineMessageMediaGeo(flags: Int32, geoPoint: Api.InputGeoPoint, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageGame(flags: Int32, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaAuto(flags: Int32, message: String, entities: [Api.MessageEntity]?, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaVenue(flags: Int32, geoPoint: Api.InputGeoPoint, title: String, address: String, provider: String, venueId: String, venueType: String, replyMarkup: Api.ReplyMarkup?) case inputBotInlineMessageMediaContact(flags: Int32, phoneNumber: String, firstName: String, lastName: String, vcard: String, replyMarkup: Api.ReplyMarkup?) + case inputBotInlineMessageMediaGeo(flags: Int32, geoPoint: Api.InputGeoPoint, heading: Int32?, period: Int32?, proximityNotificationRadius: Int32?, replyMarkup: Api.ReplyMarkup?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -10420,14 +10744,6 @@ public extension Api { }} if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} break - case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let replyMarkup): - if boxed { - buffer.appendInt32(-190472735) - } - serializeInt32(flags, buffer: buffer, boxed: false) - geoPoint.serialize(buffer, true) - if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} - break case .inputBotInlineMessageGame(let flags, let replyMarkup): if boxed { buffer.appendInt32(1262639204) @@ -10472,6 +10788,17 @@ public extension Api { serializeString(vcard, buffer: buffer, boxed: false) if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} break + case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius, let replyMarkup): + if boxed { + buffer.appendInt32(-1768777083) + } + serializeInt32(flags, buffer: buffer, boxed: false) + geoPoint.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(heading!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 1) != 0 {serializeInt32(period!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 3) != 0 {serializeInt32(proximityNotificationRadius!, buffer: buffer, boxed: false)} + if Int(flags) & Int(1 << 2) != 0 {replyMarkup!.serialize(buffer, true)} + break } } @@ -10479,8 +10806,6 @@ public extension Api { switch self { case .inputBotInlineMessageText(let flags, let message, let entities, let replyMarkup): return ("inputBotInlineMessageText", [("flags", flags), ("message", message), ("entities", entities), ("replyMarkup", replyMarkup)]) - case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let replyMarkup): - return ("inputBotInlineMessageMediaGeo", [("flags", flags), ("geoPoint", geoPoint), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageGame(let flags, let replyMarkup): return ("inputBotInlineMessageGame", [("flags", flags), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageMediaAuto(let flags, let message, let entities, let replyMarkup): @@ -10489,6 +10814,8 @@ public extension Api { return ("inputBotInlineMessageMediaVenue", [("flags", flags), ("geoPoint", geoPoint), ("title", title), ("address", address), ("provider", provider), ("venueId", venueId), ("venueType", venueType), ("replyMarkup", replyMarkup)]) case .inputBotInlineMessageMediaContact(let flags, let phoneNumber, let firstName, let lastName, let vcard, let replyMarkup): return ("inputBotInlineMessageMediaContact", [("flags", flags), ("phoneNumber", phoneNumber), ("firstName", firstName), ("lastName", lastName), ("vcard", vcard), ("replyMarkup", replyMarkup)]) + case .inputBotInlineMessageMediaGeo(let flags, let geoPoint, let heading, let period, let proximityNotificationRadius, let replyMarkup): + return ("inputBotInlineMessageMediaGeo", [("flags", flags), ("geoPoint", geoPoint), ("heading", heading), ("period", period), ("proximityNotificationRadius", proximityNotificationRadius), ("replyMarkup", replyMarkup)]) } } @@ -10516,27 +10843,6 @@ public extension Api { return nil } } - public static func parse_inputBotInlineMessageMediaGeo(_ reader: BufferReader) -> InputBotInlineMessage? { - var _1: Int32? - _1 = reader.readInt32() - var _2: Api.InputGeoPoint? - if let signature = reader.readInt32() { - _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint - } - var _3: Api.ReplyMarkup? - if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { - _3 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup - } } - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = (Int(_1!) & Int(1 << 2) == 0) || _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, replyMarkup: _3) - } - else { - return nil - } - } public static func parse_inputBotInlineMessageGame(_ reader: BufferReader) -> InputBotInlineMessage? { var _1: Int32? _1 = reader.readInt32() @@ -10641,6 +10947,36 @@ public extension Api { return nil } } + public static func parse_inputBotInlineMessageMediaGeo(_ reader: BufferReader) -> InputBotInlineMessage? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGeoPoint? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGeoPoint + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + var _4: Int32? + if Int(_1!) & Int(1 << 1) != 0 {_4 = reader.readInt32() } + var _5: Int32? + if Int(_1!) & Int(1 << 3) != 0 {_5 = reader.readInt32() } + var _6: Api.ReplyMarkup? + if Int(_1!) & Int(1 << 2) != 0 {if let signature = reader.readInt32() { + _6 = Api.parse(reader, signature: signature) as? Api.ReplyMarkup + } } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + let _c4 = (Int(_1!) & Int(1 << 1) == 0) || _4 != nil + let _c5 = (Int(_1!) & Int(1 << 3) == 0) || _5 != nil + let _c6 = (Int(_1!) & Int(1 << 2) == 0) || _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.InputBotInlineMessage.inputBotInlineMessageMediaGeo(flags: _1!, geoPoint: _2!, heading: _3, period: _4, proximityNotificationRadius: _5, replyMarkup: _6) + } + else { + return nil + } + } } public enum KeyboardButtonRow: TypeConstructorDescription { @@ -12048,6 +12384,44 @@ public extension Api { } } + } + public enum InputGroupCall: TypeConstructorDescription { + case inputGroupCall(id: Int64, accessHash: Int64) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .inputGroupCall(let id, let accessHash): + if boxed { + buffer.appendInt32(-659913713) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .inputGroupCall(let id, let accessHash): + return ("inputGroupCall", [("id", id), ("accessHash", accessHash)]) + } + } + + public static func parse_inputGroupCall(_ reader: BufferReader) -> InputGroupCall? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.InputGroupCall.inputGroupCall(id: _1!, accessHash: _2!) + } + else { + return nil + } + } + } public enum InputSingleMedia: TypeConstructorDescription { case inputSingleMedia(flags: Int32, media: Api.InputMedia, randomId: Int64, message: String, entities: [Api.MessageEntity]?) @@ -21186,6 +21560,7 @@ public extension Api { case messageActionSecureValuesSent(types: [Api.SecureValueType]) case messageActionContactSignUp case messageActionGeoProximityReached(fromId: Api.Peer, toId: Api.Peer, distance: Int32) + case messageActionGroupCall(flags: Int32, call: Api.InputGroupCall, duration: Int32?) public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -21365,6 +21740,14 @@ public extension Api { toId.serialize(buffer, true) serializeInt32(distance, buffer: buffer, boxed: false) break + case .messageActionGroupCall(let flags, let call, let duration): + if boxed { + buffer.appendInt32(2047704898) + } + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + if Int(flags) & Int(1 << 0) != 0 {serializeInt32(duration!, buffer: buffer, boxed: false)} + break } } @@ -21418,6 +21801,8 @@ public extension Api { return ("messageActionContactSignUp", []) case .messageActionGeoProximityReached(let fromId, let toId, let distance): return ("messageActionGeoProximityReached", [("fromId", fromId), ("toId", toId), ("distance", distance)]) + case .messageActionGroupCall(let flags, let call, let duration): + return ("messageActionGroupCall", [("flags", flags), ("call", call), ("duration", duration)]) } } @@ -21707,6 +22092,25 @@ public extension Api { return nil } } + public static func parse_messageActionGroupCall(_ reader: BufferReader) -> MessageAction? { + var _1: Int32? + _1 = reader.readInt32() + var _2: Api.InputGroupCall? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.InputGroupCall + } + var _3: Int32? + if Int(_1!) & Int(1 << 0) != 0 {_3 = reader.readInt32() } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = (Int(_1!) & Int(1 << 0) == 0) || _3 != nil + if _c1 && _c2 && _c3 { + return Api.MessageAction.messageActionGroupCall(flags: _1!, call: _2!, duration: _3) + } + else { + return nil + } + } } public enum PhoneCall: TypeConstructorDescription { diff --git a/submodules/TelegramApi/Sources/Api3.swift b/submodules/TelegramApi/Sources/Api3.swift index 73a388d40c..0daa2e9f0e 100644 --- a/submodules/TelegramApi/Sources/Api3.swift +++ b/submodules/TelegramApi/Sources/Api3.swift @@ -1648,6 +1648,72 @@ public struct photos { } public extension Api { public struct phone { + public enum GroupCall: TypeConstructorDescription { + case groupCall(call: Api.GroupCall, participants: [Api.GroupCallParticipant], chats: [Api.Chat], users: [Api.User]) + + public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .groupCall(let call, let participants, let chats, let users): + if boxed { + buffer.appendInt32(1731723191) + } + call.serialize(buffer, true) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(participants.count)) + for item in participants { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(chats.count)) + for item in chats { + item.serialize(buffer, true) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(users.count)) + for item in users { + item.serialize(buffer, true) + } + break + } + } + + public func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .groupCall(let call, let participants, let chats, let users): + return ("groupCall", [("call", call), ("participants", participants), ("chats", chats), ("users", users)]) + } + } + + public static func parse_groupCall(_ reader: BufferReader) -> GroupCall? { + var _1: Api.GroupCall? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.GroupCall + } + var _2: [Api.GroupCallParticipant]? + if let _ = reader.readInt32() { + _2 = Api.parseVector(reader, elementSignature: 0, elementType: Api.GroupCallParticipant.self) + } + var _3: [Api.Chat]? + if let _ = reader.readInt32() { + _3 = Api.parseVector(reader, elementSignature: 0, elementType: Api.Chat.self) + } + var _4: [Api.User]? + if let _ = reader.readInt32() { + _4 = Api.parseVector(reader, elementSignature: 0, elementType: Api.User.self) + } + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.phone.GroupCall.groupCall(call: _1!, participants: _2!, chats: _3!, users: _4!) + } + else { + return nil + } + } + + } public enum PhoneCall: TypeConstructorDescription { case phoneCall(phoneCall: Api.PhoneCall, users: [Api.User]) @@ -7118,6 +7184,111 @@ public extension Api { return result }) } + + public static func createGroupCall(flags: Int32, channel: Api.InputChannel, randomId: Int32) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1542553507) + serializeInt32(flags, buffer: buffer, boxed: false) + channel.serialize(buffer, true) + serializeInt32(randomId, buffer: buffer, boxed: false) + return (FunctionDescription(name: "phone.createGroupCall", parameters: [("flags", flags), ("channel", channel), ("randomId", randomId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func joinGroupCall(call: Api.InputGroupCall, params: Api.DataJSON) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(893342305) + call.serialize(buffer, true) + params.serialize(buffer, true) + return (FunctionDescription(name: "phone.joinGroupCall", parameters: [("call", call), ("params", params)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func leaveGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1625919071) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.leaveGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func editGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1662282468) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "phone.editGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func kickGroupCallMember(flags: Int32, call: Api.InputGroupCall, userId: Api.InputUser) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1731080446) + serializeInt32(flags, buffer: buffer, boxed: false) + call.serialize(buffer, true) + userId.serialize(buffer, true) + return (FunctionDescription(name: "phone.kickGroupCallMember", parameters: [("flags", flags), ("call", call), ("userId", userId)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func discardGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(2054648117) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.discardGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Updates? in + let reader = BufferReader(buffer) + var result: Api.Updates? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Updates + } + return result + }) + } + + public static func getGroupCall(call: Api.InputGroupCall) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(209498135) + call.serialize(buffer, true) + return (FunctionDescription(name: "phone.getGroupCall", parameters: [("call", call)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.phone.GroupCall? in + let reader = BufferReader(buffer) + var result: Api.phone.GroupCall? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.phone.GroupCall + } + return result + }) + } } } } 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 3c9274bc4b..e809cf1d28 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationCallManager.swift @@ -60,6 +60,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { private var currentCallDisposable = MetaDisposable() private let removeCurrentCallDisposable = MetaDisposable() + private var currentGroupCallValue: PresentationGroupCallImpl? + private var currentGroupCall: PresentationGroupCallImpl? { + return self.currentGroupCallValue + } + private var ringingStatesDisposable: Disposable? private let hasActiveCallsPromise = ValuePromise(false, ignoreRepeated: true) @@ -72,6 +77,11 @@ public final class PresentationCallManagerImpl: PresentationCallManager { return self.currentCallPromise.get() } + private let currentGroupCallPromise = Promise(nil) + public var currentGroupCallSignal: Signal { + return self.currentGroupCallPromise.get() + } + private let startCallDisposable = MetaDisposable() private var proxyServer: ProxyServerSettings? @@ -566,4 +576,98 @@ public final class PresentationCallManagerImpl: PresentationCallManager { self.resumeMediaPlayback() } } + + private func updateCurrentGroupCall(_ value: PresentationGroupCallImpl?) { + let wasEmpty = self.currentGroupCallValue == nil + let isEmpty = value == nil + if wasEmpty && !isEmpty { + self.resumeMedia = self.isMediaPlaying() + } + + self.currentGroupCallValue = value + + if !wasEmpty && isEmpty && self.resumeMedia { + self.resumeMedia = false + self.resumeMediaPlayback() + } + } + + public func requestOrJoinGroupCall(context: AccountContext, peerId: PeerId) -> RequestOrJoinGroupCallResult { + if let currentGroupCall = self.currentGroupCallValue { + return .alreadyInProgress(currentGroupCall.peerId) + } + let _ = self.startGroupCall(accountContext: context, peerId: peerId).start() + return .requested + } + + private func startGroupCall( + accountContext: AccountContext, + peerId: PeerId, + internalId: CallSessionInternalId = CallSessionInternalId() + ) -> Signal { + let (presentationData, present, openSettings) = self.getDeviceAccessData() + + let isVideo = false + + let accessEnabledSignal: Signal = Signal { subscriber in + DeviceAccess.authorizeAccess(to: .microphone(.voiceCall), presentationData: presentationData, present: { c, a in + present(c, a) + }, openSettings: { + openSettings() + }, { value in + if isVideo && value { + DeviceAccess.authorizeAccess(to: .camera(.videoCall), presentationData: presentationData, present: { c, a in + present(c, a) + }, openSettings: { + openSettings() + }, { value in + subscriber.putNext(value) + subscriber.putCompletion() + }) + } else { + subscriber.putNext(value) + subscriber.putCompletion() + } + }) + return EmptyDisposable + } + |> runOn(Queue.mainQueue()) + + return accessEnabledSignal + |> deliverOnMainQueue + |> mapToSignal { [weak self] accessEnabled -> Signal in + guard let strongSelf = self else { + return .single(false) + } + + if !accessEnabled { + return .single(false) + } + + let call = PresentationGroupCallImpl( + accountContext: accountContext, + audioSession: strongSelf.audioSession, + callKitIntegration: nil, + getDeviceAccessData: strongSelf.getDeviceAccessData, + internalId: internalId, + peerId: peerId, + peer: nil + ) + strongSelf.updateCurrentGroupCall(call) + strongSelf.currentGroupCallPromise.set(.single(call)) + strongSelf.hasActiveCallsPromise.set(true) + strongSelf.removeCurrentCallDisposable.set((call.canBeRemoved + |> deliverOnMainQueue).start(next: { [weak call] value in + if value, let strongSelf = self, let call = call { + if strongSelf.currentGroupCall === call { + strongSelf.updateCurrentGroupCall(nil) + strongSelf.currentGroupCallPromise.set(.single(nil)) + strongSelf.hasActiveCallsPromise.set(false) + } + } + })) + + return .single(true) + } + } } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift new file mode 100644 index 0000000000..e6e198caa9 --- /dev/null +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -0,0 +1,444 @@ +import Foundation +import UIKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import Display +import AVFoundation +import TelegramVoip +import TelegramAudio +import TelegramUIPreferences +import TelegramPresentationData +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, [UInt32: PeerId]) + + var callInfo: GroupCallInfo? { + switch self { + case .requesting: + return nil + case let .active(info): + return info + case let .estabilished(info, _, _): + return info + } + } + } + + public let account: Account + public let accountContext: AccountContext + private let audioSession: ManagedAudioSession + private let callKitIntegration: CallKitIntegration? + public var isIntegratedWithCallKit: Bool { + return self.callKitIntegration != nil + } + + private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void) + + public let internalId: CallSessionInternalId + public let peerId: PeerId + public let peer: Peer? + + private var internalState: InternalState = .requesting + + private var callContext: OngoingGroupCallContext? + private var ssrcMapping: [UInt32: PeerId] = [:] + + private var sessionStateDisposable: Disposable? + + private let isMutedPromise = ValuePromise(true) + private var isMutedValue = true + public var isMuted: Signal { + return self.isMutedPromise.get() + } + + private let audioOutputStatePromise = Promise<([AudioSessionOutput], AudioSessionOutput?)>(([], nil)) + private var audioOutputStateValue: ([AudioSessionOutput], AudioSessionOutput?) = ([], nil) + private var currentAudioOutputValue: AudioSessionOutput = .builtin + public var audioOutputState: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> { + return self.audioOutputStatePromise.get() + } + + private var audioSessionControl: ManagedAudioSessionControl? + private var audioSessionDisposable: Disposable? + private let audioSessionShouldBeActive = ValuePromise(false, ignoreRepeated: true) + private var audioSessionShouldBeActiveDisposable: Disposable? + private let audioSessionActive = Promise(false) + private var audioSessionActiveDisposable: Disposable? + private var isAudioSessionActive = 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( + accountContext: AccountContext, + audioSession: ManagedAudioSession, + callKitIntegration: CallKitIntegration?, + getDeviceAccessData: @escaping () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void), + internalId: CallSessionInternalId, + peerId: PeerId, + peer: Peer? + ) { + self.account = accountContext.account + self.accountContext = accountContext + self.audioSession = audioSession + self.callKitIntegration = callKitIntegration + self.getDeviceAccessData = getDeviceAccessData + + self.internalId = internalId + self.peerId = peerId + self.peer = peer + + var didReceiveAudioOutputs = false + + self.audioSessionDisposable = audioSession.push(audioSessionType: .voiceCall, manualActivate: { [weak self] control in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: control) + } + } + }, deactivate: { [weak self] in + return Signal { subscriber in + Queue.mainQueue().async { + if let strongSelf = self { + strongSelf.updateIsAudioSessionActive(false) + strongSelf.updateSessionState(internalState: strongSelf.internalState, audioSessionControl: nil) + } + subscriber.putCompletion() + } + return EmptyDisposable + } + }, availableOutputsChanged: { [weak self] availableOutputs, currentOutput in + Queue.mainQueue().async { + guard let strongSelf = self else { + return + } + strongSelf.audioOutputStateValue = (availableOutputs, currentOutput) + + var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput)) + if !didReceiveAudioOutputs { + didReceiveAudioOutputs = true + if currentOutput == .speaker { + signal = .single((availableOutputs, .builtin)) + |> then( + signal + |> delay(1.0, queue: Queue.mainQueue()) + ) + } + } + strongSelf.audioOutputStatePromise.set(signal) + } + }) + + self.audioSessionShouldBeActiveDisposable = (self.audioSessionShouldBeActive.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + if value { + if let audioSessionControl = strongSelf.audioSessionControl { + let audioSessionActive: Signal + if let callKitIntegration = strongSelf.callKitIntegration { + audioSessionActive = callKitIntegration.audioSessionActive + |> filter { $0 } + |> timeout(2.0, queue: Queue.mainQueue(), alternate: Signal { subscriber in + if let strongSelf = self, let _ = strongSelf.audioSessionControl { + } + subscriber.putNext(true) + subscriber.putCompletion() + return EmptyDisposable + }) + } else { + audioSessionControl.activate({ _ in }) + audioSessionActive = .single(true) + } + strongSelf.audioSessionActive.set(audioSessionActive) + } else { + strongSelf.audioSessionActive.set(.single(false)) + } + } else { + strongSelf.audioSessionActive.set(.single(false)) + } + } + }) + + self.audioSessionActiveDisposable = (self.audioSessionActive.get() + |> deliverOnMainQueue).start(next: { [weak self] value in + if let strongSelf = self { + strongSelf.updateIsAudioSessionActive(value) + } + }) + + self.requestCall() + + self.groupCallParticipantUpdatesDisposable = (self.account.stateManager.groupCallParticipantUpdates + |> deliverOnMainQueue).start(next: { [weak self] updates in + guard let strongSelf = self else { + return + } + if case let .estabilished(callInfo, _, _) = strongSelf.internalState { + var addedSsrc: [UInt32] = [] + for (callId, peerId, ssrc, _) in updates { + if callId == callInfo.id { + let mappedSsrc = UInt32(bitPattern: ssrc) + addedSsrc.append(mappedSsrc) + strongSelf.ssrcMapping[mappedSsrc] = peerId + } + } + if !addedSsrc.isEmpty { + strongSelf.callContext?.addSsrcs(ssrcs: addedSsrc) + } + } + }) + } + + deinit { + self.audioSessionShouldBeActiveDisposable?.dispose() + self.audioSessionActiveDisposable?.dispose() + self.sessionStateDisposable?.dispose() + 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?) { + let previousControl = self.audioSessionControl + self.audioSessionControl = audioSessionControl + + let previousInternalState = self.internalState + self.internalState = internalState + + if let audioSessionControl = audioSessionControl, previousControl == nil { + audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue)) + audioSessionControl.setup(synchronous: true) + } + + self.audioSessionShouldBeActive.set(true) + + switch previousInternalState { + case .active: + break + default: + if case let .active(callInfo) = internalState { + let callContext = OngoingGroupCallContext() + self.callContext = callContext + self.requestDisposable.set((callContext.joinPayload + |> take(1) + |> deliverOnMainQueue).start(next: { [weak self] joinPayload in + guard let strongSelf = self else { + return + } + strongSelf.requestDisposable.set((joinGroupCall( + account: strongSelf.account, + callId: callInfo.id, + accessHash: callInfo.accessHash, + joinPayload: joinPayload + ) + |> deliverOnMainQueue).start(next: { joinCallResult in + guard let strongSelf = self else { + return + } + if let clientParams = joinCallResult.callInfo.clientParams { + 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 + })) + } + } + + switch previousInternalState { + case .estabilished: + break + default: + if case let .estabilished(_, clientParams, ssrcMapping) = internalState { + self.ssrcMapping = ssrcMapping + self.callContext?.setJoinResponse(payload: clientParams, ssrcs: Array(ssrcMapping.keys)) + } + } + } + + private func updateIsAudioSessionActive(_ value: Bool) { + if self.isAudioSessionActive != value { + self.isAudioSessionActive = value + } + } + + 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() { + self.setIsMuted(!self.isMutedValue) + } + + public func setIsMuted(_ value: Bool) { + self.isMutedValue = value + self.isMutedPromise.set(self.isMutedValue) + self.callContext?.setIsMuted(self.isMutedValue) + } + + public func setCurrentAudioOutput(_ output: AudioSessionOutput) { + guard self.currentAudioOutputValue != output else { + return + } + self.currentAudioOutputValue = output + + self.audioOutputStatePromise.set(.single((self.audioOutputStateValue.0, output)) + |> then( + .single(self.audioOutputStateValue) + |> delay(1.0, queue: Queue.mainQueue()) + )) + + if let audioSessionControl = self.audioSessionControl { + audioSessionControl.setOutputMode(.custom(output)) + } + } + + private func requestCall() { + self.internalState = .requesting + + enum CallError { + case generic + } + + let account = self.account + let peerId = self.peerId + + let currentCall = getCurrentGroupCall(account: account, peerId: peerId) + |> mapError { _ -> CallError in + return .generic + } + + let currentOrRequestedCall = currentCall + |> mapToSignal { callInfo -> Signal in + if let callInfo = callInfo { + return .single(callInfo) + } else { + return createGroupCall(account: account, peerId: peerId) + |> mapError { _ -> CallError in + return .generic + } + } + } + + let restartedCall = currentOrRequestedCall + |> mapToSignal { value -> Signal in + let stopped: Signal = stopGroupCall(account: account, callId: value.id, accessHash: value.accessHash) + |> mapError { _ -> CallError in + return .generic + } + |> map { _ -> GroupCallInfo in + } + + return stopped + |> then(currentOrRequestedCall) + } + + self.requestDisposable.set((currentOrRequestedCall + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + strongSelf.updateSessionState(internalState: .active(value), audioSessionControl: strongSelf.audioSessionControl) + })) + } +} 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 0da4669378..ef6f0a9798 100644 --- a/submodules/TelegramCore/Sources/AccountIntermediateState.swift +++ b/submodules/TelegramCore/Sources/AccountIntermediateState.swift @@ -110,6 +110,7 @@ enum AccountStateMutationOperation { case UpdateChatListFilterOrder(order: [Int32]) case UpdateChatListFilter(id: Int32, filter: Api.DialogFilter?) case UpdateReadThread(threadMessageId: MessageId, readMaxId: Int32, isIncoming: Bool, mainChannelMessage: MessageId?) + case UpdateGroupCallParticipant(id: Int64, accessHash: Int64, participant: Api.GroupCallParticipant) } struct HoleFromPreviousState { @@ -276,6 +277,10 @@ struct AccountMutableState { self.addOperation(.UpdateReadThread(threadMessageId: threadMessageId, readMaxId: readMaxId, isIncoming: isIncoming, mainChannelMessage: mainChannelMessage)) } + mutating func updateGroupCallParticipant(id: Int64, accessHash: Int64, participant: Api.GroupCallParticipant) { + self.addOperation(.UpdateGroupCallParticipant(id: id, accessHash: accessHash, participant: participant)) + } + mutating func readGroupFeedInbox(groupId: PeerGroupId, index: MessageIndex) { self.addOperation(.ReadGroupFeedInbox(groupId, index)) } @@ -484,7 +489,7 @@ struct AccountMutableState { mutating func addOperation(_ operation: AccountStateMutationOperation) { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateMessagesPinned: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .ReadOutbox, .ReadGroupFeedInbox, .MergePeerPresences, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdatePeerChatUnreadMark, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilterOrder, .UpdateChatListFilter, .UpdateReadThread, .UpdateGroupCallParticipant, .UpdateMessagesPinned: break case let .AddMessages(messages, location): for message in messages { @@ -602,6 +607,7 @@ struct AccountReplayedFinalState { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] + let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let delayNotificatonsUntil: Int32? @@ -617,6 +623,7 @@ struct AccountFinalStateEvents { let updatedWebpages: [MediaId: TelegramMediaWebpage] let updatedCalls: [Api.PhoneCall] let addedCallSignalingData: [(Int64, Data)] + let updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] let updatedPeersNearby: [PeerNearby]? let isContactUpdates: [(PeerId, Bool)] let displayAlerts: [(text: String, isDropAuth: Bool)] @@ -629,10 +636,10 @@ struct AccountFinalStateEvents { let updatedOutgoingThreadReadStates: [MessageId: MessageId.Id] var isEmpty: Bool { - return self.addedIncomingMessageIds.isEmpty && self.wasScheduledMessageIds.isEmpty && self.deletedMessageIds.isEmpty && self.updatedTypingActivities.isEmpty && self.updatedWebpages.isEmpty && self.updatedCalls.isEmpty && self.addedCallSignalingData.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 + 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)] = [], 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 @@ -640,6 +647,7 @@ struct AccountFinalStateEvents { self.updatedWebpages = updatedWebpages self.updatedCalls = updatedCalls self.addedCallSignalingData = addedCallSignalingData + self.updatedGroupCallParticipants = updatedGroupCallParticipants self.updatedPeersNearby = updatedPeersNearby self.isContactUpdates = isContactUpdates self.displayAlerts = displayAlerts @@ -660,6 +668,7 @@ struct AccountFinalStateEvents { self.updatedWebpages = state.updatedWebpages self.updatedCalls = state.updatedCalls self.addedCallSignalingData = state.addedCallSignalingData + self.updatedGroupCallParticipants = state.updatedGroupCallParticipants self.updatedPeersNearby = state.updatedPeersNearby self.isContactUpdates = state.isContactUpdates self.displayAlerts = state.state.state.displayAlerts @@ -695,6 +704,6 @@ struct AccountFinalStateEvents { let externallyUpdatedPeerId = self.externallyUpdatedPeerId.union(other.externallyUpdatedPeerId) let authorizationListUpdated = self.authorizationListUpdated || other.authorizationListUpdated - return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs })) + return AccountFinalStateEvents(addedIncomingMessageIds: self.addedIncomingMessageIds + other.addedIncomingMessageIds, wasScheduledMessageIds: self.wasScheduledMessageIds + other.wasScheduledMessageIds, deletedMessageIds: self.deletedMessageIds + other.deletedMessageIds, updatedTypingActivities: self.updatedTypingActivities, updatedWebpages: self.updatedWebpages, updatedCalls: self.updatedCalls + other.updatedCalls, addedCallSignalingData: self.addedCallSignalingData + other.addedCallSignalingData, updatedGroupCallParticipants: self.updatedGroupCallParticipants + other.updatedGroupCallParticipants, isContactUpdates: self.isContactUpdates + other.isContactUpdates, displayAlerts: self.displayAlerts + other.displayAlerts, delayNotificatonsUntil: delayNotificatonsUntil, updatedMaxMessageId: updatedMaxMessageId, updatedQts: updatedQts, externallyUpdatedPeerId: externallyUpdatedPeerId, authorizationListUpdated: authorizationListUpdated, updatedIncomingThreadReadStates: self.updatedIncomingThreadReadStates.merging(other.updatedIncomingThreadReadStates, uniquingKeysWith: { lhs, _ in lhs })) } } diff --git a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift index b053ab26c8..aa728482b0 100644 --- a/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift +++ b/submodules/TelegramCore/Sources/AccountStateManagementUtils.swift @@ -1306,6 +1306,11 @@ private func finalStateWithUpdatesAndServerTime(postbox: Postbox, network: Netwo updatedState.addUpdateCall(phoneCall) case let .updatePhoneCallSignalingData(phoneCallId, data): updatedState.addCallSignalingData(callId: phoneCallId, data: data.makeData()) + case let .updateGroupCallParticipant(call, participant): + switch call { + case let .inputGroupCall(id, accessHash): + updatedState.updateGroupCallParticipant(id: id, accessHash: accessHash, participant: participant) + } case let .updateLangPackTooLong(langCode): updatedState.updateLangPack(langCode: langCode, difference: nil) case let .updateLangPack(difference): @@ -2109,7 +2114,7 @@ private func optimizedOperations(_ operations: [AccountStateMutationOperation]) var currentAddScheduledMessages: OptimizeAddMessagesState? for operation in operations { switch operation { - case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned: + case .DeleteMessages, .DeleteMessagesWithGlobalIds, .EditMessage, .UpdateMessagePoll/*, .UpdateMessageReactions*/, .UpdateMedia, .MergeApiChats, .MergeApiUsers, .MergePeerPresences, .UpdatePeer, .ReadInbox, .ReadOutbox, .ReadGroupFeedInbox, .ResetReadState, .ResetIncomingReadState, .UpdatePeerChatUnreadMark, .ResetMessageTagSummary, .UpdateNotificationSettings, .UpdateGlobalNotificationSettings, .UpdateSecretChat, .AddSecretMessages, .ReadSecretOutbox, .AddPeerInputActivity, .UpdateCachedPeerData, .UpdatePinnedItemIds, .ReadMessageContents, .UpdateMessageImpressionCount, .UpdateMessageForwardsCount, .UpdateInstalledStickerPacks, .UpdateRecentGifs, .UpdateChatInputState, .UpdateCall, .AddCallSignalingData, .UpdateLangPack, .UpdateMinAvailableMessage, .UpdateIsContact, .UpdatePeerChatInclusion, .UpdatePeersNearby, .UpdateTheme, .SyncChatListFilters, .UpdateChatListFilter, .UpdateChatListFilterOrder, .UpdateReadThread, .UpdateMessagesPinned, .UpdateGroupCallParticipant: if let currentAddMessages = currentAddMessages, !currentAddMessages.messages.isEmpty { result.append(.AddMessages(currentAddMessages.messages, currentAddMessages.location)) } @@ -2196,6 +2201,7 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP var updatedWebpages: [MediaId: TelegramMediaWebpage] = [:] var updatedCalls: [Api.PhoneCall] = [] var addedCallSignalingData: [(Int64, Data)] = [] + var updatedGroupCallParticipants: [(Int64, PeerId, Int32, Bool)] = [] var updatedPeersNearby: [PeerNearby]? var isContactUpdates: [(PeerId, Bool)] = [] var stickerPackOperations: [AccountStateUpdateStickerPacksOperation] = [] @@ -2925,6 +2931,26 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP updatedCalls.append(call) 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(userId, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + ssrc = source + case let .groupCallParticipant(_, userId, _, source): + peerId = PeerId(namespace: Namespaces.Peer.CloudUser, id: userId) + ssrc = source + case .groupCallParticipantLeft: + break + case .groupCallParticipantKicked: + break + case .groupCallParticipantInvited: + break + } + if let peerId = peerId, let ssrc = ssrc { + updatedGroupCallParticipants.append((callId, peerId, ssrc, true)) + } case let .UpdateLangPack(langCode, difference): if let difference = difference { if langPackDifferences[langCode] == nil { @@ -3347,5 +3373,5 @@ func replayFinalState(accountManager: AccountManager, postbox: Postbox, accountP requestChatListFiltersSync(transaction: transaction) } - return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates) + return AccountReplayedFinalState(state: finalState, addedIncomingMessageIds: addedIncomingMessageIds, wasScheduledMessageIds: wasScheduledMessageIds, addedSecretMessageIds: addedSecretMessageIds, deletedMessageIds: deletedMessageIds, updatedTypingActivities: updatedTypingActivities, updatedWebpages: updatedWebpages, updatedCalls: updatedCalls, addedCallSignalingData: addedCallSignalingData, updatedGroupCallParticipants: updatedGroupCallParticipants, updatedPeersNearby: updatedPeersNearby, isContactUpdates: isContactUpdates, delayNotificatonsUntil: delayNotificatonsUntil, updatedIncomingThreadReadStates: updatedIncomingThreadReadStates, updatedOutgoingThreadReadStates: updatedOutgoingThreadReadStates) } diff --git a/submodules/TelegramCore/Sources/AccountStateManager.swift b/submodules/TelegramCore/Sources/AccountStateManager.swift index a7cf0516cb..2b96b1273b 100644 --- a/submodules/TelegramCore/Sources/AccountStateManager.swift +++ b/submodules/TelegramCore/Sources/AccountStateManager.swift @@ -148,6 +148,11 @@ public final class AccountStateManager { return self.threadReadStateUpdatesPipe.signal() } + private let groupCallParticipantUpdatesPipe = ValuePipe<[(Int64, PeerId, Int32, Bool)]>() + public var groupCallParticipantUpdates: Signal<[(Int64, PeerId, Int32, Bool)], NoError> { + return self.groupCallParticipantUpdatesPipe.signal() + } + private let deletedMessagesPipe = ValuePipe<[DeletedMessageId]>() public var deletedMessages: Signal<[DeletedMessageId], NoError> { return self.deletedMessagesPipe.signal() @@ -673,6 +678,9 @@ public final class AccountStateManager { strongSelf.callSessionManager.addCallSignalingData(id: id, data: data) } } + if !events.updatedGroupCallParticipants.isEmpty { + strongSelf.groupCallParticipantUpdatesPipe.putNext(events.updatedGroupCallParticipants) + } if !events.updatedIncomingThreadReadStates.isEmpty || !events.updatedOutgoingThreadReadStates.isEmpty { strongSelf.threadReadStateUpdatesPipe.putNext((events.updatedIncomingThreadReadStates, events.updatedOutgoingThreadReadStates)) } diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift new file mode 100644 index 0000000000..5d812b0ce2 --- /dev/null +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -0,0 +1,302 @@ +import Postbox +import SwiftSignalKit +import TelegramApi +import MtProtoKit +import SyncCore + +public struct GroupCallInfo: Equatable { + public var id: Int64 + public var accessHash: Int64 + public var peerId: PeerId? + public var clientParams: String? +} + +private extension GroupCallInfo { + init?(_ call: Api.GroupCall) { + switch call { + case let .groupCallPrivate(_, id, accessHash, channelId, _, _): + self.init( + id: id, + accessHash: accessHash, + peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }, + clientParams: nil + ) + case let .groupCall(_, id, accessHash, channelId, _, _, params): + var clientParams: String? + if let params = params { + switch params { + case let .dataJSON(data): + clientParams = data + } + } + self.init( + id: id, + accessHash: accessHash, + peerId: channelId.flatMap { PeerId(namespace: Namespaces.Peer.CloudChannel, id: $0) }, + clientParams: clientParams + ) + case .groupCallDiscarded: + return nil + } + } +} + +public enum GetCurrentGroupCallError { + case generic +} + +public func getCurrentGroupCall(account: Account, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(GetCurrentGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + return account.network.request(Api.functions.channels.getFullChannel(channel: inputPeer)) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .chatFull(fullChat, _, _): + switch fullChat { + case let .channelFull(_, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, callMsgId): + return .single(callMsgId.flatMap { callMsgId in + MessageId(peerId: peerId, namespace: Namespaces.Peer.CloudChannel, id: callMsgId) + }) + default: + return .single(nil) + } + default: + return .single(nil) + } + } + } + |> mapToSignal { messageId -> Signal in + guard let messageId = messageId else { + return .single(nil) + } + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(GetCurrentGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + return account.network.request(Api.functions.channels.getMessages(channel: inputPeer, id: [.inputMessageID(id: messageId.id)])) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + + switch result { + case let .messages(apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messagesSlice(_, _, _, _, messages: apiMessages, chats: apiChats, users: apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .channelMessages(_, _, _, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case .messagesNotModified: + return .fail(.generic) + } + + guard let apiMessage = messages.first else { + return .single(nil) + } + guard let message = StoreMessage(apiMessage: apiMessage) else { + return .fail(.generic) + } + + var maybeInputCall: Api.InputGroupCall? + loop: for media in message.media { + if let action = media as? TelegramMediaAction { + switch action.action { + case let .groupPhoneCall(callId, accessHash, _): + maybeInputCall = .inputGroupCall(id: callId, accessHash: accessHash) + break loop + default: + break + } + } + } + + guard let inputCall = maybeInputCall else { + return .fail(.generic) + } + + return account.network.request(Api.functions.phone.getGroupCall(call: inputCall)) + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + switch result { + case let .groupCall(call, participants, chats, users): + return account.postbox.transaction { transaction -> GroupCallInfo? in + return GroupCallInfo(call) + } + |> mapError { _ -> GetCurrentGroupCallError in + return .generic + } + } + } + } + } + } +} + +public enum CreateGroupCallError { + case generic +} + +public func createGroupCall(account: Account, peerId: PeerId) -> Signal { + return account.postbox.transaction { transaction -> Api.InputChannel? in + return transaction.getPeer(peerId).flatMap(apiInputChannel) + } + |> castError(CreateGroupCallError.self) + |> mapToSignal { inputPeer -> Signal in + guard let inputPeer = inputPeer else { + return .fail(.generic) + } + + return account.network.request(Api.functions.phone.createGroupCall(flags: 0, channel: inputPeer, randomId: Int32.random(in: Int32.min ... Int32.max))) + |> mapError { _ -> CreateGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + var parsedCall: GroupCallInfo? + loop: for update in result.allUpdates { + switch update { + case let .updateGroupCall(call): + parsedCall = GroupCallInfo(call) + break loop + default: + break + } + } + + if let parsedCall = parsedCall { + return .single(parsedCall) + } else { + return .fail(.generic) + } + } + } +} + +public enum JoinGroupCallError { + case generic +} + +public struct JoinGroupCallResult { + public var callInfo: GroupCallInfo + public var ssrcMapping: [UInt32: PeerId] +} + +public func joinGroupCall(account: Account, callId: Int64, accessHash: Int64, joinPayload: String) -> Signal { + return account.network.request(Api.functions.phone.joinGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash), params: .dataJSON(data: joinPayload))) + |> mapError { _ -> JoinGroupCallError in + return .generic + } + |> mapToSignal { updates -> Signal in + return account.network.request(Api.functions.phone.getGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) + |> mapError { _ -> JoinGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(updates) + + var maybeParsedCall: GroupCallInfo? + loop: for update in updates.allUpdates { + switch update { + case let .updateGroupCall(call): + maybeParsedCall = GroupCallInfo(call) + break loop + default: + break + } + } + + guard let parsedCall = maybeParsedCall else { + return .fail(.generic) + } + + switch result { + case let .groupCall(call, participants, chats, users): + guard let _ = GroupCallInfo(call) else { + return .fail(.generic) + } + var ssrcMapping: [UInt32: PeerId] = [:] + for participant in participants { + var peerId: PeerId? + var ssrc: UInt32? + switch participant { + 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: + break + case .groupCallParticipantInvited: + break + } + if let peerId = peerId, let ssrc = ssrc { + ssrcMapping[ssrc] = peerId + } + } + return account.postbox.transaction { transaction -> JoinGroupCallResult in + return JoinGroupCallResult( + callInfo: parsedCall, + ssrcMapping: ssrcMapping + ) + } + |> castError(JoinGroupCallError.self) + } + } + } +} + +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 +} + +public func stopGroupCall(account: Account, callId: Int64, accessHash: Int64) -> Signal { + return account.network.request(Api.functions.phone.discardGroupCall(call: .inputGroupCall(id: callId, accessHash: accessHash))) + |> mapError { _ -> StopGroupCallError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return .complete() + } +} diff --git a/submodules/TelegramCore/Sources/Serialization.swift b/submodules/TelegramCore/Sources/Serialization.swift index b87bcca4ed..034fd4b593 100644 --- a/submodules/TelegramCore/Sources/Serialization.swift +++ b/submodules/TelegramCore/Sources/Serialization.swift @@ -210,7 +210,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 121 + return 122 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift index 817959bea2..698fe5d4f8 100644 --- a/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/StoreMessage_Telegram.swift @@ -188,7 +188,7 @@ func apiMessagePeerIds(_ message: Api.Message) -> [PeerId] { } switch action { - case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp: + case .messageActionChannelCreate, .messageActionChatDeletePhoto, .messageActionChatEditPhoto, .messageActionChatEditTitle, .messageActionEmpty, .messageActionPinMessage, .messageActionHistoryClear, .messageActionGameScore, .messageActionPaymentSent, .messageActionPaymentSentMe, .messageActionPhoneCall, .messageActionScreenshotTaken, .messageActionCustomAction, .messageActionBotAllowed, .messageActionSecureValuesSent, .messageActionSecureValuesSentMe, .messageActionContactSignUp, .messageActionGroupCall: break case let .messageActionChannelMigrateFrom(_, chatId): result.append(PeerId(namespace: Namespaces.Peer.CloudGroup, id: chatId)) diff --git a/submodules/TelegramCore/Sources/TelegramMediaAction.swift b/submodules/TelegramCore/Sources/TelegramMediaAction.swift index 3af839d70e..eeb04af985 100644 --- a/submodules/TelegramCore/Sources/TelegramMediaAction.swift +++ b/submodules/TelegramCore/Sources/TelegramMediaAction.swift @@ -59,6 +59,11 @@ func telegramMediaActionFromApiAction(_ action: Api.MessageAction) -> TelegramMe return TelegramMediaAction(action: .peerJoined) case let .messageActionGeoProximityReached(fromId, toId, distance): return TelegramMediaAction(action: .geoProximityReached(from: fromId.peerId, to: toId.peerId, distance: distance)) + case let .messageActionGroupCall(_, call, duration): + switch call { + case let .inputGroupCall(id, accessHash): + return TelegramMediaAction(action: .groupPhoneCall(callId: id, accessHash: accessHash, duration: duration)) + } } } diff --git a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift index 6a3f8e49d5..bb65bc9005 100644 --- a/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift +++ b/submodules/TelegramCore/Sources/UpdateCachedPeerData.swift @@ -346,7 +346,7 @@ func fetchAndUpdateCachedPeerData(accountPeerId: PeerId, peerId rawPeerId: PeerI } switch fullChat { - case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts): + case let .channelFull(flags, _, about, participantsCount, adminsCount, kickedCount, bannedCount, _, _, _, _, chatPhoto, _, apiExportedInvite, apiBotInfos, migratedFromChatId, migratedFromMaxId, pinnedMsgId, stickerSet, minAvailableMsgId, folderId, linkedChatId, location, slowmodeSeconds, slowmodeNextSendDate, statsDc, pts, callMsgId): var channelFlags = CachedChannelFlags() if (flags & (1 << 3)) != 0 { channelFlags.insert(.canDisplayParticipants) @@ -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/TelegramCore/Sources/UpdatesApiUtils.swift b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift index acf5d469a5..44c7a8bc40 100644 --- a/submodules/TelegramCore/Sources/UpdatesApiUtils.swift +++ b/submodules/TelegramCore/Sources/UpdatesApiUtils.swift @@ -338,6 +338,21 @@ extension Api.Update { } } +extension Api.Updates { + var allUpdates: [Api.Update] { + switch self { + case let .updates(updates, _, _, _, _): + return updates + case let .updatesCombined(updates, _, _, _, _, _): + return updates + case let .updateShort(update, _): + return [update] + default: + return [] + } + } +} + extension Api.Updates { var rawMessageIds: [Int32] { switch self { diff --git a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift index a8cad395f3..dc8bd8b0d9 100644 --- a/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift +++ b/submodules/TelegramStringFormatting/Sources/ServiceMessageStrings.swift @@ -404,6 +404,15 @@ public func universalServiceMessageString(presentationData: (PresentationTheme, } } attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) + case let .groupPhoneCall(_, _, duration): + //TODO:localize + let titleString: String + if let duration = duration { + titleString = "Group Call \(duration)s" + } else { + titleString = "Group Call" + } + attributedString = NSAttributedString(string: titleString, font: titleFont, textColor: primaryTextColor) case let .customText(text, entities): attributedString = stringWithAppliedEntities(text, entities: entities, baseColor: primaryTextColor, linkColor: primaryTextColor, baseFont: titleFont, linkFont: titleBoldFont, boldFont: titleBoldFont, italicFont: titleFont, boldItalicFont: titleBoldFont, fixedFont: titleFont, blockQuoteFont: titleFont, underlineLinks: false) case let .botDomainAccessGranted(domain): 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 0000000000..d72275c2cd Binary files /dev/null and b/submodules/TelegramUI/Images.xcassets/Call/VoiceChatMicOn.imageset/Voice.pdf differ diff --git a/submodules/TelegramUI/Sources/ChatCallTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatCallTitlePanelNode.swift new file mode 100644 index 0000000000..39d3d821f6 --- /dev/null +++ b/submodules/TelegramUI/Sources/ChatCallTitlePanelNode.swift @@ -0,0 +1,153 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import Postbox +import TelegramCore +import SyncCore +import SwiftSignalKit +import TelegramPresentationData +import TelegramUIPreferences +import AccountContext +import StickerResources +import PhotoResources +import TelegramStringFormatting +import AnimatedCountLabelNode +import AnimatedNavigationStripeNode +import ContextUI +import RadialStatusNode +import AnimatedAvatarSetNode + +final class ChatCallTitlePanelNode: ChatTitleAccessoryPanelNode { + private let context: AccountContext + + private let tapButton: HighlightTrackingButtonNode + + private let joinButton: HighlightableButtonNode + private let joinButtonTitleNode: ImmediateTextNode + private let joinButtonBackgroundNode: ASImageNode + + private let titleNode: ImmediateTextNode + private let textNode: ImmediateTextNode + private let muteIconNode: ASImageNode + + private let separatorNode: ASDisplayNode + + private var theme: PresentationTheme? + private var currentLayout: (CGFloat, CGFloat, CGFloat)? + + private var activeGroupCallInfo: ChatActiveGroupCallInfo? + + private let queue = Queue() + + init(context: AccountContext) { + self.context = context + + self.tapButton = HighlightTrackingButtonNode() + + self.joinButton = HighlightableButtonNode() + self.joinButtonTitleNode = ImmediateTextNode() + self.joinButtonBackgroundNode = ASImageNode() + + self.titleNode = ImmediateTextNode() + self.textNode = ImmediateTextNode() + + self.muteIconNode = ASImageNode() + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + super.init() + + self.tapButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.titleNode.layer.removeAnimation(forKey: "opacity") + strongSelf.titleNode.alpha = 0.4 + strongSelf.textNode.layer.removeAnimation(forKey: "opacity") + strongSelf.textNode.alpha = 0.4 + } else { + strongSelf.titleNode.alpha = 1.0 + strongSelf.titleNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + strongSelf.textNode.alpha = 1.0 + strongSelf.textNode.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + + self.addSubnode(self.titleNode) + self.addSubnode(self.textNode) + self.addSubnode(self.muteIconNode) + + self.tapButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) + self.addSubnode(self.tapButton) + + self.joinButton.addSubnode(self.joinButtonBackgroundNode) + self.joinButton.addSubnode(self.joinButtonTitleNode) + self.addSubnode(self.joinButton) + self.joinButton.addTarget(self, action: #selector(self.tapped), forControlEvents: [.touchUpInside]) + + self.addSubnode(self.separatorNode) + } + + deinit { + } + + override func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition, interfaceState: ChatPresentationInterfaceState) -> 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 bc9347b021..7552abb6b3 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) @@ -5982,6 +5981,33 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G }, editMessageMedia: { [weak self] messageId, draw in if let strongSelf = self { strongSelf.controllerInteraction?.editMessageMedia(messageId, draw) + }, 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())) 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 0be22111e1..5aace93078 100644 --- a/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift +++ b/submodules/TelegramUI/Sources/ChatPanelInterfaceInteraction.swift @@ -128,6 +128,7 @@ final class ChatPanelInterfaceInteraction { let activatePinnedListPreview: (ASDisplayNode, ContextGesture) -> Void let editMessageMedia: (MessageId, Bool) -> Void let statuses: ChatPanelInterfaceInteractionStatuses? + let joinGroupCall: (MessageId) -> Void init( setupReplyMessage: @escaping (MessageId, @escaping (ContainedViewLayoutTransition) -> Void) -> Void, @@ -207,6 +208,7 @@ final class ChatPanelInterfaceInteraction { viewReplies: @escaping (MessageId?, ChatReplyThreadMessage) -> Void, activatePinnedListPreview: @escaping (ASDisplayNode, ContextGesture) -> Void, editMessageMedia: @escaping (MessageId, Bool) -> Void, + joinGroupCall: @escaping (MessageId) -> Void, statuses: ChatPanelInterfaceInteractionStatuses? ) { self.setupReplyMessage = setupReplyMessage @@ -286,6 +288,7 @@ final class ChatPanelInterfaceInteraction { self.viewReplies = viewReplies self.activatePinnedListPreview = activatePinnedListPreview self.editMessageMedia = editMessageMedia + 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 00a534b40e..3d77d119be 100644 --- a/submodules/TelegramUI/Sources/ChatRecentActionsController.swift +++ b/submodules/TelegramUI/Sources/ChatRecentActionsController.swift @@ -133,6 +133,7 @@ final class ChatRecentActionsController: TelegramBaseController { }, viewReplies: { _, _ in }, activatePinnedListPreview: { _, _ in }, editMessageMedia: { _, _ 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/PeerInfoData.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift index 510dee6cb2..ff273acc3e 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoData.swift @@ -949,6 +949,7 @@ func peerInfoHeaderButtons(peer: Peer?, cachedData: CachedPeerData?, isOpenedFro displayLeave = false if channel.flags.contains(.isCreator) || channel.hasPermission(.inviteMembers) { result.append(.addMember) + result.append(.call) } } switch channel.participationStatus { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 4df99bc872..9ada296a69 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -439,6 +439,7 @@ final class PeerInfoSelectionPanelNode: ASDisplayNode { }, viewReplies: { _, _ in }, activatePinnedListPreview: { _, _ in }, editMessageMedia: { _, _ in + }, joinGroupCall: { _ in }, statuses: nil) self.selectionPanel.interfaceInteraction = interfaceInteraction @@ -454,7 +455,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))) @@ -3164,6 +3165,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } private func requestCall(isVideo: Bool) { + if let peer = self.data?.peer as? TelegramChannel { + self.context.sharedContext.callManager?.requestOrJoinGroupCall(context: self.context, peerId: peer.id) + + return + } + guard let peer = self.data?.peer as? TelegramUser, let cachedUserData = self.data?.cachedData as? CachedUserData else { return } 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 e1d6c09ca4..b6a71ad57f 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -26,1872 +26,541 @@ 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? +private struct ParsedJoinPayload { + var payload: String + var audioSsrc: UInt32 +} + +private func parseSdpIntoJoinPayload(sdp: String) -> ParsedJoinPayload? { + let lines = sdp.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 } - struct Fingerprint { - var fingerprint: String - var setup: String - var hashType: String + if isAudioLine { + audioLines.append(line) + } else if isVideoLine { + videoLines.append(line) } - - var candidates: [Candidate] - var fingerprints: [Fingerprint] - var ufrag: String - var pwd: String } - struct ChannelBundle { + 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] = [] + 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) + } + } + } + } + + guard let ssrc = audioSources.first else { + return nil + } + + guard let ufrag = getLines(prefix: "a=ice-ufrag:").first else { + return nil + } + guard let pwd = getLines(prefix: "a=ice-pwd:").first else { + return nil + } + + var resultPayload: [String: Any] = [:] + + var fingerprints: [[String: Any]] = [] + for line in getLines(prefix: "a=fingerprint:") { + let components = line.components(separatedBy: " ") + if components.count != 2 { + continue + } + fingerprints.append([ + "hash": components[0], + "fingerprint": components[1], + "setup": "active" + ]) + } + + resultPayload["fingerprints"] = fingerprints + + resultPayload["ufrag"] = ufrag + resultPayload["pwd"] = pwd + + resultPayload["ssrc"] = ssrc + + guard let payloadData = try? JSONSerialization.data(withJSONObject: resultPayload, options: []) else { + return nil + } + guard let payloadString = String(data: payloadData, encoding: .utf8) else { + return nil + } + + return ParsedJoinPayload( + payload: payloadString, + audioSsrc: UInt32(ssrc) + ) +} + +private func parseJoinResponseIntoSdp(sessionId: UInt32, mainStreamAudioSsrc: UInt32, payload: String, isAnswer: Bool, otherSsrcs: [UInt32]) -> String? { + guard let payloadData = payload.data(using: .utf8) else { + return nil + } + guard let jsonPayload = try? JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any] else { + return nil + } + + guard let transport = jsonPayload["transport"] as? [String: Any] else { + return nil + } + guard let pwd = transport["pwd"] as? String else { + return nil + } + guard let ufrag = transport["ufrag"] as? String else { + return nil + } + + struct ParsedFingerprint { + var hashValue: String + var fingerprint: String + var setup: String + } + + var fingerprints: [ParsedFingerprint] = [] + guard let fingerprintsValue = transport["fingerprints"] as? [[String: Any]] else { + return nil + } + for fingerprintValue in fingerprintsValue { + guard let hashValue = fingerprintValue["hash"] as? String else { + continue + } + guard let fingerprint = fingerprintValue["fingerprint"] as? String else { + continue + } + guard let setup = fingerprintValue["setup"] as? String else { + continue + } + fingerprints.append(ParsedFingerprint( + hashValue: hashValue, + fingerprint: fingerprint, + setup: setup + )) + } + + struct ParsedCandidate { + var port: String + var `protocol`: String + var network: String + var generation: String var id: String - var transport: Transport + var component: String + var foundation: String + var priority: String + var ip: String + var type: String + var tcpType: String? + var relAddr: String? + var relPort: String? } - 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 candidates: [ParsedCandidate] = [] + guard let candidatesValue = transport["candidates"] as? [[String: Any]] else { + return nil + } + for candidateValue in candidatesValue { + guard let port = candidateValue["port"] as? String else { + continue + } + guard let `protocol` = candidateValue["protocol"] as? String else { + continue + } + guard let network = candidateValue["network"] as? String else { + continue + } + guard let generation = candidateValue["generation"] as? String else { + continue + } + guard let id = candidateValue["id"] as? String else { + continue + } + guard let component = candidateValue["component"] as? String else { + continue + } + guard let foundation = candidateValue["foundation"] as? String else { + continue + } + guard let priority = candidateValue["priority"] as? String else { + continue + } + guard let ip = candidateValue["ip"] as? String else { + continue + } + guard let type = candidateValue["type"] as? String else { + continue } - var name: String - var channels: [Channel] + let tcpType = candidateValue["tcptype"] as? String + + let relAddr = candidateValue["rel-addr"] as? String + let relPort = candidateValue["rel-port"] as? String + + candidates.append(ParsedCandidate( + port: port, + protocol: `protocol`, + network: network, + generation: generation, + id: id, + component: component, + foundation: foundation, + priority: priority, + ip: ip, + type: type, + tcpType: tcpType, + relAddr: relAddr, + relPort: relPort + )) } - 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] + struct StreamSpec { + var isMain: Bool + var audioSsrc: Int + var isRemoved: Bool } - 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) } - 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(" ") + 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 + if stream.isMain { + audioMid = "0" + } else { + audioMid = "audio\(stream.audioSsrc)" } - 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("m=audio \(stream.isMain ? "1" : "0") RTP/SAVPF 111 126") + if stream.isMain { + appendSdp("c=IN IP4 0.0.0.0") } - appendSdp(bundleString) - - appendSdp("a=ice-lite") - - for stream in bundleStreams { - let audioMid: String - let videoMid: String? + appendSdp("a=mid:\(audioMid)") + if stream.isRemoved { + appendSdp("a=inactive") + } else { if stream.isMain { - audioMid = "0" - if let videoSsrc = stream.videoSsrc { - videoMid = "1" - } else { - videoMid = nil + appendSdp("a=ice-ufrag:\(ufrag)") + appendSdp("a=ice-pwd:\(pwd)") + + for fingerprint in fingerprints { + appendSdp("a=fingerprint:\(fingerprint.hashValue) \(fingerprint.fingerprint)") + appendSdp("a=setup:passive") } - } else { - audioMid = "audio\(stream.audioSsrc)" - if let videoSsrc = stream.videoSsrc { - videoMid = "video\(stream.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 candidate in 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) ") - 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") + let ip = candidate.ip + 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 } - 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 { + if protocolValue == "tcp" { + guard let tcpType = candidate.tcpType 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") + candidateString.append("tcptype \(tcpType) ") } - 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)") + candidateString.append("generation \(candidate.generation)") + + appendSdp(candidateString) } } - 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 - )) - } - } + 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 { - precondition(audioChannel.ssrcs.count <= 1) - if audioChannel.ssrcs.count == 1 { - streams.append(StreamSpec( - isMain: false, - audioSsrc: audioChannel.ssrcs[0], - videoSsrc: nil, - isRemoved: false - )) + 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)") } - - /*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() - } + appendSdp("") - 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 - ) - } - ) - ) + return sdp } - 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 + var bundleStreams: [StreamSpec] = [] + bundleStreams.append(StreamSpec( + isMain: true, + audioSsrc: Int(mainStreamAudioSsrc), + isRemoved: false + )) + + for ssrc in otherSsrcs { + bundleStreams.append(StreamSpec( + isMain: false, + audioSsrc: Int(ssrc), + isRemoved: false + )) + } + + /*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) + }*/ + + return createSdp(sessionId: sessionId, bundleStreams: bundleStreams) } -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 OngoingGroupCallContext { + public enum NetworkState { + case connecting + case connected } -} - -public final class GroupCallContext { + + public struct MemberState: Equatable { + public var isSpeaking: Bool + } + private final class Impl { - private let queue: Queue - private let context: GroupCallThreadLocalContext - private let disposable = MetaDisposable() + let queue: Queue + let context: GroupCallThreadLocalContext - private let colibriHost: String - private let sessionId: UInt32 + let sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) + var mainStreamAudioSsrc: UInt32? + var initialAnswerPayload: String? + var otherSsrcs: [UInt32] = [] - private var audioSessionDisposable: Disposable? - private let pollDisposable = MetaDisposable() + let joinPayload = Promise() + let networkState = ValuePromise(.connecting, ignoreRepeated: true) + let isMuted = ValuePromise(true, ignoreRepeated: true) + let memberStates = ValuePromise<[UInt32: MemberState]>([:], ignoreRepeated: true) - 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?) { + init(queue: Queue) { self.queue = queue - self.sessionId = UInt32.random(in: 0 ..< UInt32(Int32.max)) - self.colibriHost = "192.168.93.24" + var networkStateUpdatedImpl: ((GroupCallNetworkState) -> Void)? - 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() + self.context = GroupCallThreadLocalContext(queue: ContextQueueImpl(queue: queue), relaySdpAnswer: { _ in + }, incomingVideoStreamListUpdated: { _ in + }, videoCapturer: nil, + networkStateUpdated: { state in + networkStateUpdatedImpl?(state) }) - } - - 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: [] - )*/ + let queue = self.queue - 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) - } - }) - - /*guard let offer = conference.offerSdp(sessionId: strongSelf.sessionId, bundleId: bundleId, bridgeHost: strongSelf.colibriHost, transport: transport, currentState: strongSelf.currentOfferState) else { - return - } - 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) - }*/ - })) - } - - 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 + networkStateUpdatedImpl = { [weak self] state in + queue.async { guard let strongSelf = self else { return } - - guard let conference = ConferenceDescription(json: result) else { - return + let mappedState: NetworkState + switch state { + case .connecting: + mappedState = .connecting + case .connected: + mappedState = .connected + @unknown default: + mappedState = .connecting } - - 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 + strongSelf.networkState.set(mappedState) + } } - 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 - let queue = strongSelf.queue - - strongSelf.memberCount.set(offer.state.items.filter({ !$0.isRemoved }).count) - - for sdp in offer.sdpList { - strongSelf.context.setOfferSdp(sdp, isPartial: false) + + self.context.emitOffer(adjustSdp: { sdp in + return sdp + }, completion: { [weak self] offerSdp in + queue.async { + guard let strongSelf = self else { + return + } + if let payload = parseSdpIntoJoinPayload(sdp: offerSdp) { + strongSelf.mainStreamAudioSsrc = payload.audioSsrc + strongSelf.joinPayload.set(.single(payload.payload)) } - - 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 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) } }) } + + func setJoinResponse(payload: String, ssrcs: [UInt32]) { + guard let mainStreamAudioSsrc = self.mainStreamAudioSsrc else { + return + } + if let sdp = parseJoinResponseIntoSdp(sessionId: self.sessionId, mainStreamAudioSsrc: mainStreamAudioSsrc, payload: payload, isAnswer: true, otherSsrcs: []) { + self.initialAnswerPayload = payload + self.context.setOfferSdp(sdp, isPartial: true) + self.addSsrcs(ssrcs: ssrcs) + } + } + + func addSsrcs(ssrcs: [UInt32]) { + if ssrcs.isEmpty { + return + } + guard let mainStreamAudioSsrc = self.mainStreamAudioSsrc else { + return + } + guard let initialAnswerPayload = self.initialAnswerPayload else { + return + } + let mappedSsrcs = ssrcs + var otherSsrcs = self.otherSsrcs + for ssrc in mappedSsrcs { + if ssrc == mainStreamAudioSsrc { + continue + } + if !otherSsrcs.contains(ssrc) { + otherSsrcs.append(ssrc) + } + } + 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) + } + } + } + + func setIsMuted(_ isMuted: Bool) { + self.isMuted.set(isMuted) + self.context.setIsMuted(isMuted) + } } 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 { + public var joinPayload: Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in - disposable.set(impl.memberCount.get().start(next: { value in + disposable.set(impl.joinPayload.get().start(next: { value in subscriber.putNext(value) })) } @@ -1899,11 +568,23 @@ public final class GroupCallContext { } } - public var videoStreamList: Signal<[String], NoError> { + public var networkState: Signal { return Signal { subscriber in let disposable = MetaDisposable() self.impl.with { impl in - disposable.set(impl.videoStreamList.get().start(next: { value 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) })) } @@ -1923,16 +604,28 @@ public final class GroupCallContext { } } - public func toggleIsMuted() { + public init() { + let queue = self.queue + self.impl = QueueLocalObject(queue: queue, generate: { + return Impl(queue: queue) + }) + } + + public func setIsMuted(_ isMuted: Bool) { self.impl.with { impl in - impl.toggleIsMuted() + impl.setIsMuted(isMuted) } } - public func makeIncomingVideoView(id: String, completion: @escaping (OngoingCallContextPresentationCallVideoView?) -> Void) { + public func setJoinResponse(payload: String, ssrcs: [UInt32]) { self.impl.with { impl in - impl.makeIncomingVideoView(id: id, completion: completion) + impl.setJoinResponse(payload: payload, ssrcs: ssrcs) + } + } + + 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/TgVoipWebrtc/tgcalls b/submodules/TgVoipWebrtc/tgcalls index 5b60ad7708..9da28c0473 160000 --- a/submodules/TgVoipWebrtc/tgcalls +++ b/submodules/TgVoipWebrtc/tgcalls @@ -1 +1 @@ -Subproject commit 5b60ad7708d0a5d29bc0857fbabc91514a07f252 +Subproject commit 9da28c047317708366da3af6c657cf514ef53593 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) } } }