diff --git a/submodules/ContextUI/Sources/ContextController.swift b/submodules/ContextUI/Sources/ContextController.swift index 888bf26b9e..8a3c61a691 100644 --- a/submodules/ContextUI/Sources/ContextController.swift +++ b/submodules/ContextUI/Sources/ContextController.swift @@ -1247,7 +1247,7 @@ private final class ContextControllerNode: ViewControllerTracingNode, UIScrollVi if let contentNode = self.contentContainerNode.contentNode { switch contentNode { case let .reference(referenceNode): - let contentActionsSpacing: CGFloat = 16.0 + let contentActionsSpacing: CGFloat = 8.0 if let originalProjectedContentViewFrame = self.originalProjectedContentViewFrame { let isInitialLayout = self.actionsContainerNode.frame.size.width.isZero let previousContainerFrame = self.view.convert(self.contentContainerNode.frame, from: self.scrollNode.view) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index ead43415ef..79d71c3a64 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -355,17 +355,20 @@ private func inviteLinkListControllerEntries(presentationData: PresentationData, entries.append(.linksInfo(presentationData.theme, presentationData.strings.InviteLink_CreateInfo)) } - if let revokedInvites = revokedInvites, !revokedInvites.isEmpty { - entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased())) - if admin == nil { - entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks)) - } - var index: Int32 = 0 - for invite in revokedInvites { - entries.append(.revokedLink(index, presentationData.theme, invite)) - index += 1 + if let revokedInvites = revokedInvites { + if !revokedInvites.isEmpty { + entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased())) + if admin == nil { + entries.append(.revokedLinksDeleteAll(presentationData.theme, presentationData.strings.InviteLink_DeleteAllRevokedLinks)) + } + var index: Int32 = 0 + for invite in revokedInvites { + entries.append(.revokedLink(index, presentationData.theme, invite)) + index += 1 + } } } else if let admin = admin, admin.revokedCount > 0 { + entries.append(.revokedLinksHeader(presentationData.theme, presentationData.strings.InviteLink_RevokedLinks.uppercased())) var index: Int32 = 0 for _ in 0 ..< admin.revokedCount { entries.append(.revokedLink(index, presentationData.theme, nil)) @@ -817,7 +820,7 @@ public func inviteLinkListController(context: AccountContext, peerId: PeerId, ad } let controllerState = ItemListControllerState(presentationData: ItemListPresentationData(presentationData), title: title, leftNavigationButton: nil, rightNavigationButton: nil, backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), animateChanges: true) - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.invitations, importers: importers, creators: creators, admin: admin, tick: tick), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: inviteLinkListControllerEntries(presentationData: presentationData, view: view, invites: invites.hasLoadedOnce ? invites.invitations : nil, revokedInvites: revokedInvites.hasLoadedOnce ? revokedInvites.invitations : nil, importers: importers, creators: creators, admin: admin, tick: tick), style: .blocks, emptyStateItem: nil, crossfadeState: crossfade, animateChanges: animateChanges) return (controllerState, (listState, arguments)) } diff --git a/submodules/PeerInfoUI/Sources/PeerReportController.swift b/submodules/PeerInfoUI/Sources/PeerReportController.swift index 5dc1c16f77..90b30059e2 100644 --- a/submodules/PeerInfoUI/Sources/PeerReportController.swift +++ b/submodules/PeerInfoUI/Sources/PeerReportController.swift @@ -56,7 +56,7 @@ public func presentPeerReportOptions(context: AccountContext, parent: ViewContro icon = UIImage(bundleImageName: "Chat/Context Menu/ReportXxx") case .childAbuse: title = presentationData.strings.ReportPeer_ReasonChildAbuse - icon = UIImage(bundleImageName: "Chat/Context Menu/Block") + icon = UIImage(bundleImageName: "Chat/Context Menu/Restrict") case .copyright: title = presentationData.strings.ReportPeer_ReasonCopyright icon = UIImage(bundleImageName: "Chat/Context Menu/ReportCopyright") diff --git a/submodules/PeerInfoUI/Sources/ReportPeerHeaderActionSheetItem.swift b/submodules/PeerInfoUI/Sources/ReportPeerHeaderActionSheetItem.swift index bc66fad77c..22b2888e0a 100644 --- a/submodules/PeerInfoUI/Sources/ReportPeerHeaderActionSheetItem.swift +++ b/submodules/PeerInfoUI/Sources/ReportPeerHeaderActionSheetItem.swift @@ -42,7 +42,7 @@ private final class ReportPeerHeaderActionSheetItemNode: ActionSheetItemNode { self.animationNode = AnimatedStickerNode() if let path = getAppBundle().path(forResource: "Cop", ofType: "tgs") { - self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .once, mode: .direct(cachePathPrefix: nil)) + self.animationNode.setup(source: AnimatedStickerNodeLocalFileSource(path: path), width: 192, height: 192, playbackMode: .count(2), mode: .direct(cachePathPrefix: nil)) self.animationNode.visibility = true } diff --git a/submodules/ShareController/Sources/SharePeersContainerNode.swift b/submodules/ShareController/Sources/SharePeersContainerNode.swift index 6fc7f407e0..8b05d91369 100644 --- a/submodules/ShareController/Sources/SharePeersContainerNode.swift +++ b/submodules/ShareController/Sources/SharePeersContainerNode.swift @@ -178,7 +178,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { self.searchButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/SearchIcon"), color: self.theme.actionSheet.controlAccentColor), for: []) self.shareButtonNode = HighlightableButtonNode() - self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: [])  + self.shareButtonNode.setImage(generateTintedImage(image: UIImage(bundleImageName: "Share/ShareIcon"), color: self.theme.actionSheet.controlAccentColor), for: []) let segmentedItems: [SegmentedControlItem] if let segmentedValues = segmentedValues { @@ -187,6 +187,7 @@ final class SharePeersContainerNode: ASDisplayNode, ShareContentContainerNode { segmentedItems = [] } self.segmentedNode = SegmentedControlNode(theme: SegmentedControlTheme(theme: theme), items: segmentedItems, selectedIndex: 0) + self.segmentedNode.isHidden = segmentedValues == nil self.contentTitleNode.isHidden = self.segmentedValues != nil self.contentSubtitleNode.isHidden = self.segmentedValues != nil diff --git a/submodules/SyncCore/Sources/Namespaces.swift b/submodules/SyncCore/Sources/Namespaces.swift index 93effd92e9..bfccdc26ec 100644 --- a/submodules/SyncCore/Sources/Namespaces.swift +++ b/submodules/SyncCore/Sources/Namespaces.swift @@ -75,6 +75,7 @@ public struct Namespaces { public static let proximityNotificationStoredState: Int8 = 11 public static let cachedPeerInvitationImporters: Int8 = 12 public static let cachedPeerExportedInvitations: Int8 = 13 + public static let cachedGroupCallDisplayAsPeers: Int8 = 14 } public struct UnorderedItemList { diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift index 29d4e68a49..e39977d1e0 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatController.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatController.swift @@ -1444,13 +1444,14 @@ public final class VoiceChatController: ViewController { let displayAsItems: () -> Signal<[ContextMenuItem], NoError> = { return displayAsPeersPromise.get() + |> take(1) |> map { peers -> [ContextMenuItem] in var items: [ContextMenuItem] = [] items.append(.custom(VoiceChatInfoContextItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: theme.actionSheet.primaryTextColor) }), true)) - for peer in peers.prefix(5) { + for peer in peers { var subtitle: String? if peer.peer.id.namespace == Namespaces.Peer.CloudUser { subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount @@ -1486,6 +1487,10 @@ public final class VoiceChatController: ViewController { strongSelf.presentUndoOverlay(content: .invitedToVoiceChat(context: strongSelf.context, peer: peer.peer, text: strongSelf.presentationData.strings.VoiceChat_DisplayAsSuccess(peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder)).0), action: { _ in return false }) }))) + + if peer.peer.id.namespace == Namespaces.Peer.CloudUser { + items.append(.separator) + } } items.append(.separator) items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in @@ -1546,6 +1551,7 @@ public final class VoiceChatController: ViewController { mainItemsImpl = { return displayAsPeersPromise.get() + |> take(1) |> map { peers -> [ContextMenuItem] in var items: [ContextMenuItem] = [] @@ -1693,7 +1699,7 @@ public final class VoiceChatController: ViewController { } let optionsButton: VoiceChatHeaderButton = !strongSelf.recButton.isHidden ? strongSelf.recButton : strongSelf.optionsButton - let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .extracted(VoiceChatContextExtractedContentSource(controller: controller, sourceNode: optionsButton.extractedContainerNode, keepInPlace: true, blurBackground: false)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: gesture) + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData.withUpdated(theme: strongSelf.darkTheme), source: .reference(VoiceChatContextReferenceContentSource(controller: controller, sourceNode: optionsButton.referenceNode)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: gesture) strongSelf.controller?.presentInGlobalOverlay(contextController) } @@ -3223,3 +3229,17 @@ private final class VoiceChatContextExtractedContentSource: ContextExtractedCont return ContextControllerPutBackViewInfo(contentAreaInScreenSpace: UIScreen.main.bounds) } } + +private final class VoiceChatContextReferenceContentSource: ContextReferenceContentSource { + private let controller: ViewController + private let sourceNode: ContextReferenceContentNode + + init(controller: ViewController, sourceNode: ContextReferenceContentNode) { + self.controller = controller + self.sourceNode = sourceNode + } + + func transitionInfo() -> ContextControllerReferenceViewInfo? { + return ContextControllerReferenceViewInfo(referenceNode: self.sourceNode, contentAreaInScreenSpace: UIScreen.main.bounds) + } +} diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatInfoContextItem.swift b/submodules/TelegramCallsUI/Sources/VoiceChatInfoContextItem.swift index 34be8861f1..577f352ffb 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatInfoContextItem.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatInfoContextItem.swift @@ -8,16 +8,16 @@ import AppBundle import ContextUI import TelegramStringFormatting -final class VoiceChatInfoContextItem: ContextMenuCustomItem { +public final class VoiceChatInfoContextItem: ContextMenuCustomItem { let text: String let icon: (PresentationTheme) -> UIImage? - init(text: String, icon: @escaping (PresentationTheme) -> UIImage?) { + public init(text: String, icon: @escaping (PresentationTheme) -> UIImage?) { self.text = text self.icon = icon } - func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { + public func node(presentationData: PresentationData, getController: @escaping () -> ContextController?, actionSelected: @escaping (ContextMenuActionResult) -> Void) -> ContextMenuCustomNode { return VoiceChatInfoContextItemNode(presentationData: presentationData, item: self, getController: getController, actionSelected: actionSelected) } } diff --git a/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift b/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift index 6e20a8dada..1ad9cfaedb 100644 --- a/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift +++ b/submodules/TelegramCallsUI/Sources/VoiceChatOptionsButton.swift @@ -45,7 +45,7 @@ func closeButtonImage(dark: Bool) -> UIImage? { } final class VoiceChatHeaderButton: HighlightableButtonNode { - let extractedContainerNode: ContextExtractedContentContainingNode + let referenceNode: ContextReferenceContentNode let containerNode: ContextControllerSourceNode private let iconNode: ASImageNode @@ -55,7 +55,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { var dotNode: ASImageNode? init(rec: Bool = false) { - self.extractedContainerNode = ContextExtractedContentContainingNode() + self.referenceNode = ContextReferenceContentNode() self.containerNode = ContextControllerSourceNode() self.containerNode.isGestureEnabled = false self.iconNode = ASImageNode() @@ -78,14 +78,13 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { super.init() - self.containerNode.addSubnode(self.extractedContainerNode) - self.extractedContainerNode.contentNode.addSubnode(self.iconNode) - self.containerNode.targetNodeForActivationProgress = self.extractedContainerNode.contentNode + self.containerNode.addSubnode(self.referenceNode) + self.referenceNode.addSubnode(self.iconNode) self.addSubnode(self.containerNode) if rec, let textNode = self.textNode, let dotNode = self.dotNode { - self.extractedContainerNode.contentNode.addSubnode(textNode) - self.extractedContainerNode.contentNode.addSubnode(dotNode) + self.referenceNode.addSubnode(textNode) + self.referenceNode.addSubnode(dotNode) } self.containerNode.shouldBegin = { [weak self] location in @@ -104,8 +103,7 @@ final class VoiceChatHeaderButton: HighlightableButtonNode { self.iconNode.image = optionsButtonImage(dark: false) self.containerNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: rec ? 58.0 : 28.0, height: 28.0)) - self.extractedContainerNode.frame = self.containerNode.bounds - self.extractedContainerNode.contentRect = self.containerNode.bounds + self.referenceNode.frame = self.containerNode.bounds self.iconNode.frame = self.containerNode.bounds } diff --git a/submodules/TelegramCore/Sources/AccountManager.swift b/submodules/TelegramCore/Sources/AccountManager.swift index 63bd006448..69ed985493 100644 --- a/submodules/TelegramCore/Sources/AccountManager.swift +++ b/submodules/TelegramCore/Sources/AccountManager.swift @@ -173,6 +173,7 @@ private var declaredEncodables: Void = { declareEncodable(CachedPeerInvitationImporters.self, f: { CachedPeerInvitationImporters(decoder: $0) }) declareEncodable(CachedPeerExportedInvitations.self, f: { CachedPeerExportedInvitations(decoder: $0) }) declareEncodable(ExportedInvitation.self, f: { ExportedInvitation(decoder: $0) }) + declareEncodable(CachedDisplayAsPeers.self, f: { CachedDisplayAsPeers(decoder: $0) }) return }() diff --git a/submodules/TelegramCore/Sources/GroupCalls.swift b/submodules/TelegramCore/Sources/GroupCalls.swift index 002dd1271c..2b4113df3f 100644 --- a/submodules/TelegramCore/Sources/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/GroupCalls.swift @@ -1732,6 +1732,60 @@ public func groupCallDisplayAsAvailablePeers(network: Network, postbox: Postbox) } } +public final class CachedDisplayAsPeers: PostboxCoding { + public let peerIds: [PeerId] + public let timestamp: Int32 + + public init(peerIds: [PeerId], timestamp: Int32) { + self.peerIds = peerIds + self.timestamp = timestamp + } + + public init(decoder: PostboxDecoder) { + self.peerIds = decoder.decodeInt64ArrayForKey("peerIds").map { PeerId($0) } + self.timestamp = decoder.decodeInt32ForKey("timestamp", orElse: 0) + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeInt64Array(self.peerIds.map { $0.toInt64() }, forKey: "peerIds") + encoder.encodeInt32(self.timestamp, forKey: "timestamp") + } +} + +public func cachedGroupCallDisplayAsAvailablePeers(account: Account) -> Signal<[FoundPeer], NoError> { + let key = ValueBoxKey(length: 0) + return account.postbox.transaction { transaction -> ([FoundPeer], Int32)? in + let cached = transaction.retrieveItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers, key: key)) as? CachedDisplayAsPeers + if let cached = cached { + var peers: [FoundPeer] = [] + for peerId in cached.peerIds { + if let peer = transaction.getPeer(peerId) { + peers.append(FoundPeer(peer: peer, subscribers: nil)) + } + } + return (peers, cached.timestamp) + } else { + return nil + } + } + |> mapToSignal { cachedPeersAndTimestamp -> Signal<[FoundPeer], NoError> in + let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + if let (cachedPeers, timestamp) = cachedPeersAndTimestamp, currentTimestamp - timestamp < 60 * 5 { + return .single(cachedPeers) + } else { + return groupCallDisplayAsAvailablePeers(network: account.network, postbox: account.postbox) + |> mapToSignal { peers -> Signal<[FoundPeer], NoError> in + return account.postbox.transaction { transaction -> [FoundPeer] in + let currentTimestamp = Int32(CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970) + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedGroupCallDisplayAsPeers, key: key), entry: CachedDisplayAsPeers(peerIds: peers.map { $0.peer.id }, timestamp: currentTimestamp), collectionSpec: ItemCacheCollectionSpec(lowWaterItemCount: 1, highWaterItemCount: 1)) + return peers + } + } + } + } +} + + public func updatedCurrentPeerGroupCall(account: Account, peerId: PeerId) -> Signal { return fetchAndUpdateCachedPeerData(accountPeerId: account.peerId, peerId: peerId, network: account.network, postbox: account.postbox) |> mapToSignal { _ -> Signal in diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 169cc3a322..365e274b49 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -58,6 +58,7 @@ import UndoUI import MediaResources import HashtagSearchUI import ActionSheetPeerItem +import TelegramCallsUI protocol PeerInfoScreenItem: class { var id: AnyHashable { get } @@ -1529,6 +1530,8 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD private let preloadedSticker = Promise(nil) private let preloadStickerDisposable = MetaDisposable() + private let displayAsPeersPromise = Promise<[FoundPeer]>([]) + fileprivate let accountsAndPeers = Promise<[(Account, Peer, Int32)]>() fileprivate let activeSessionsContextAndCount = Promise<(ActiveSessionsContext, Int, WebSessionsContext)?>() private let notificationExceptions = Promise() @@ -2735,6 +2738,10 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .controller(ContextControllerContentSourceImpl(controller: galleryController, sourceNode: node)), items: .single(items), reactionItems: [], gesture: gesture) strongSelf.controller?.presentInGlobalOverlay(contextController) } + + if [Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel].contains(peerId.namespace) { + self.displayAsPeersPromise.set(cachedGroupCallDisplayAsAvailablePeers(account: context.account)) + } } self.headerNode.avatarListNode.listContainerNode.currentIndexUpdated = { [weak self] in @@ -3257,80 +3264,17 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD case .videoCall: self.requestCall(isVideo: true) case .voiceChat: - if let cachedData = self.data?.cachedData as? CachedGroupData, let activeCall = cachedData.activeCall { - self.context.joinGroupCall(peerId: self.peerId, joinAsPeerId: nil, activeCall: activeCall) - } else if let cachedData = self.data?.cachedData as? CachedChannelData, let activeCall = cachedData.activeCall { - let accountPeerId = self.context.account.peerId - let _ = (adminedPublicChannels(account: self.context.account, scope: .forVoiceChat) - |> deliverOnMainQueue).start(next: { [weak self] channelPeers in - guard let strongSelf = self else { - return - } - let _ = (strongSelf.context.account.postbox.transaction { transaction -> Peer? in - return transaction.getPeer(accountPeerId) - } - |> deliverOnMainQueue).start(next: { accountPeer in - guard let strongSelf = self else { - return - } - - if channelPeers.isEmpty { - strongSelf.context.joinGroupCall(peerId: strongSelf.peerId, joinAsPeerId: nil, activeCall: activeCall) - } else { - let actionSheet = ActionSheetController(presentationData: strongSelf.presentationData) - let dismissAction: () -> Void = { [weak actionSheet] in - actionSheet?.dismissAnimated() - } - let selectAction: (PeerId) -> Void = { joinAsPeerId in - dismissAction() - - guard let strongSelf = self else { - return - } - strongSelf.context.joinGroupCall(peerId: strongSelf.peerId, joinAsPeerId: joinAsPeerId == strongSelf.context.account.peerId ? nil : joinAsPeerId, activeCall: activeCall) - } - var items: [ActionSheetItem] = [] - - var allPeers: [Peer] = [] - if let accountPeer = accountPeer { - allPeers.append(accountPeer) - } - var channelPeers = channelPeers - for i in 0 ..< channelPeers.count { - if channelPeers[i].id == strongSelf.peerId { - let peer = channelPeers[i] - channelPeers.remove(at: i) - channelPeers.insert(peer, at: 0) - break - } - } - allPeers.append(contentsOf: channelPeers) - - for peer in allPeers { - items.append(ActionSheetPeerItem(context: strongSelf.context, peer: peer, title: peer.debugDisplayTitle, isSelected: false, strings: strongSelf.presentationData.strings, theme: strongSelf.presentationData.theme, action: { - selectAction(peer.id) - })) - } - - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: strongSelf.presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) - strongSelf.view.endEditing(true) - controller.present(actionSheet, in: .window(.root)) - } - }) - }) - } + self.openVoiceChatDisplayAsPeerSelection(contextController: nil, result: nil, backAction: nil) case .mute: if let notificationSettings = self.data?.notificationSettings, case .muted = notificationSettings.muteState { let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: nil).start() } else { - let actionSheet = ActionSheetController(presentationData: self.presentationData) - let dismissAction: () -> Void = { [weak actionSheet] in - actionSheet?.dismissAnimated() + self.state = self.state.withHighlightedButton(.mute) + if let (layout, navigationHeight) = self.validLayout { + self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) } - var items: [ActionSheetItem] = [] + + var items: [ContextMenuItem] = [] let muteValues: [Int32] = [ 1 * 60 * 60, 2 * 24 * 60 * 60, @@ -3343,20 +3287,28 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } else { title = muteForIntervalString(strings: self.presentationData.strings, value: delay) } - items.append(ActionSheetButtonItem(title: title, action: { - dismissAction() + + items.append(.action(ContextMenuActionItem(text: title, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Muted"), color: theme.contextMenu.primaryColor) + }, action: { _, f in + f(.dismissWithoutContent) let _ = updatePeerMuteSetting(account: self.context.account, peerId: self.peerId, muteInterval: delay).start() - })) + }))) } - items.insert(ActionSheetButtonItem(title: self.presentationData.strings.PeerInfo_CustomizeNotifications, action: { - guard let peer = self.data?.peer else { + items.append(.separator) + + items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.PeerInfo_CustomizeNotifications, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Edit"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + guard let strongSelf = self, let peer = strongSelf.data?.peer else { return } - dismissAction() - let context = self.context + let context = strongSelf.context let updatePeerSound: (PeerId, PeerMessageSound) -> Signal = { peerId, sound in return updatePeerNotificationSoundInteractive(account: context.account, peerId: peerId, sound: sound) |> deliverOnMainQueue } @@ -3391,32 +3343,34 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD }) exceptionController.navigationPresentation = .modal controller.push(exceptionController) - }), at: items.count - 1) + }))) - actionSheet.setItemGroups([ - ActionSheetItemGroup(items: items), - ActionSheetItemGroup(items: [ActionSheetButtonItem(title: self.presentationData.strings.Common_Cancel, action: { dismissAction() })]) - ]) self.view.endEditing(true) - controller.present(actionSheet, in: .window(.root)) + + if let sourceNode = self.headerNode.buttonNodes[.mute]?.referenceNode { + let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(items), reactionItems: [], gesture: nil) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } } case .more: guard let data = self.data, let peer = data.peer else { return } let presentationData = self.presentationData - self.state = self.state.withHighlightedButton(.more) if let (layout, navigationHeight) = self.validLayout { self.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) } var mainItemsImpl: (() -> Signal<[ContextMenuItem], NoError>)? - - let displayAsItems: () -> Signal<[ContextMenuItem], NoError> = { - return .single([]) - } - mainItemsImpl = { var items: [ContextMenuItem] = [] @@ -3630,10 +3584,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD if canManageGroupCalls, !group.flags.contains(.hasVoiceChat) { items.append(.action(ContextMenuActionItem(text: presentationData.strings.ChannelInfo_CreateVoiceChat, icon: { theme in generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/VoiceChat"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - self?.requestCall(isVideo: false) + }, action: { [weak self] c, f in + self?.openVoiceChatDisplayAsPeerSelection(contextController: c, result: f, backAction: { c in + if let mainItemsImpl = mainItemsImpl { + c.setItems(mainItemsImpl()) + } + }) }))) } @@ -3655,7 +3611,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.view.endEditing(true) if let sourceNode = self.headerNode.buttonNodes[.more]?.referenceNode { - let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoButtonReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: nil) + let contextController = ContextController(account: self.context.account, presentationData: self.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: mainItemsImpl?() ?? .single([]), reactionItems: [], gesture: nil) contextController.dismissed = { [weak self] in if let strongSelf = self { strongSelf.state = strongSelf.state.withHighlightedButton(nil) @@ -3808,16 +3764,16 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.controller?.present(shareController, in: .window(.root)) } - private func requestCall(isVideo: Bool) { + private func requestCall(isVideo: Bool, joinAsPeerId: PeerId? = nil) { if let peer = self.data?.peer as? TelegramChannel { guard let cachedChannelData = self.data?.cachedData as? CachedChannelData else { return } if let activeCall = cachedChannelData.activeCall { - self.context.joinGroupCall(peerId: peer.id, joinAsPeerId: nil, activeCall: activeCall) + self.context.joinGroupCall(peerId: peer.id, joinAsPeerId: joinAsPeerId, activeCall: activeCall) } else { - self.createAndJoinGroupCall(peerId: peer.id) + self.createAndJoinGroupCall(peerId: peer.id, joinAsPeerId: joinAsPeerId) } return } else if let peer = self.data?.peer as? TelegramGroup { @@ -3826,9 +3782,9 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD } if let activeCall = cachedGroupData.activeCall { - self.context.joinGroupCall(peerId: peer.id, joinAsPeerId: nil, activeCall: activeCall) + self.context.joinGroupCall(peerId: peer.id, joinAsPeerId: joinAsPeerId, activeCall: activeCall) } else { - self.createAndJoinGroupCall(peerId: peer.id) + self.createAndJoinGroupCall(peerId: peer.id, joinAsPeerId: joinAsPeerId) } return } @@ -3844,7 +3800,7 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD self.context.requestCall(peerId: peer.id, isVideo: isVideo, completion: {}) } - private func createAndJoinGroupCall(peerId: PeerId) { + private func createAndJoinGroupCall(peerId: PeerId, joinAsPeerId: PeerId?) { if let _ = self.context.sharedContext.callManager { let startCall: (Bool) -> Void = { [weak self] endCurrentIfAny in guard let strongSelf = self else { @@ -3860,12 +3816,12 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD statusController?.dismiss() } strongSelf.controller?.present(statusController, in: .window(.root)) - strongSelf.activeActionDisposable.set((createGroupCall(account: strongSelf.context.account, peerId: peerId, joinAs: nil) + strongSelf.activeActionDisposable.set((createGroupCall(account: strongSelf.context.account, peerId: peerId, joinAs: joinAsPeerId) |> deliverOnMainQueue).start(next: { [weak self] info in guard let strongSelf = self else { return } - strongSelf.context.joinGroupCall(peerId: peerId, joinAsPeerId: nil, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash)) + strongSelf.context.joinGroupCall(peerId: peerId, joinAsPeerId: joinAsPeerId, activeCall: CachedChannelData.ActiveCall(id: info.id, accessHash: info.accessHash)) }, error: { [weak self] error in dismissStatus?() @@ -4184,6 +4140,86 @@ private final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewD controller.push(statsController) } + private func openVoiceChatDisplayAsPeerSelection(contextController: ContextController?, result: ((ContextMenuActionResult) -> Void)?, backAction: ((ContextController) -> Void)?) { + let currentAccountPeer = self.context.account.postbox.loadedPeerWithId(context.account.peerId) + |> map { peer in + return [FoundPeer(peer: peer, subscribers: nil)] + } + let _ = (combineLatest(queue: Queue.mainQueue(), currentAccountPeer, self.displayAsPeersPromise.get() |> take(1)) + |> map { currentAccountPeer, availablePeers -> [FoundPeer] in + var result = currentAccountPeer + result.append(contentsOf: availablePeers) + return result + }).start(next: { [weak self] peers in + guard let strongSelf = self else { + return + } + if peers.count == 1 { + result?(.dismissWithoutContent) + strongSelf.requestCall(isVideo: false, joinAsPeerId: nil) + } else { + var items: [ContextMenuItem] = [] + + items.append(.custom(VoiceChatInfoContextItem(text: strongSelf.presentationData.strings.VoiceChat_DisplayAsInfo, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Media Gallery/Stickers"), color: theme.actionSheet.primaryTextColor) + }), true)) + + for peer in peers { + var subtitle: String? + if peer.peer.id.namespace == Namespaces.Peer.CloudUser { + subtitle = strongSelf.presentationData.strings.VoiceChat_PersonalAccount + } else if let subscribers = peer.subscribers { + subtitle = strongSelf.presentationData.strings.Conversation_StatusSubscribers(subscribers) + } + + let avatarSize = CGSize(width: 28.0, height: 28.0) + let avatarSignal = peerAvatarCompleteImage(account: strongSelf.context.account, peer: peer.peer, size: avatarSize) + items.append(.action(ContextMenuActionItem(text: peer.peer.displayTitle(strings: strongSelf.presentationData.strings, displayOrder: strongSelf.presentationData.nameDisplayOrder), textLayout: subtitle.flatMap { .secondLineWithValue($0) } ?? .singleLine, icon: { _ in nil }, iconSource: ContextMenuActionItemIconSource(size: avatarSize, signal: avatarSignal), action: { _, f in + f(.dismissWithoutContent) + + strongSelf.requestCall(isVideo: false, joinAsPeerId: peer.peer.id == strongSelf.context.account.peerId ? nil : peer.peer.id) + }))) + + if peer.peer.id.namespace == Namespaces.Peer.CloudUser { + items.append(.separator) + } + } + if backAction != nil { + items.append(.separator) + items.append(.action(ContextMenuActionItem(text: strongSelf.presentationData.strings.Common_Back, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Back"), color: theme.actionSheet.primaryTextColor) + }, action: { (c, _) in + if let backAction = backAction { + backAction(c) + } + }))) + } + + if let contextController = contextController { + contextController.setItems(.single(items)) + } else { + strongSelf.state = strongSelf.state.withHighlightedButton(.voiceChat) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + + if let sourceNode = strongSelf.headerNode.buttonNodes[.voiceChat]?.referenceNode, let controller = strongSelf.controller { + let contextController = ContextController(account: strongSelf.context.account, presentationData: strongSelf.presentationData, source: .reference(PeerInfoContextReferenceContentSource(controller: controller, sourceNode: sourceNode)), items: .single(items), reactionItems: [], gesture: nil) + contextController.dismissed = { [weak self] in + if let strongSelf = self { + strongSelf.state = strongSelf.state.withHighlightedButton(nil) + if let (layout, navigationHeight) = strongSelf.validLayout { + strongSelf.containerLayoutUpdated(layout: layout, navigationHeight: navigationHeight, transition: .immediate, additive: false) + } + } + } + controller.presentInGlobalOverlay(contextController) + } + } + } + }) + } + private func openReport(user: Bool, contextController: ContextController?, backAction: ((ContextController) -> Void)?) { guard let controller = self.controller else { return @@ -6574,7 +6610,7 @@ private final class MessageContextExtractedContentSource: ContextExtractedConten } } -private final class PeerInfoButtonReferenceContentSource: ContextReferenceContentSource { +private final class PeerInfoContextReferenceContentSource: ContextReferenceContentSource { private let controller: ViewController private let sourceNode: ContextReferenceContentNode