diff --git a/submodules/AccountContext/Sources/PresentationCallManager.swift b/submodules/AccountContext/Sources/PresentationCallManager.swift index 88fb9bfc5f..1939801d42 100644 --- a/submodules/AccountContext/Sources/PresentationCallManager.swift +++ b/submodules/AccountContext/Sources/PresentationCallManager.swift @@ -423,6 +423,7 @@ public protocol PresentationGroupCall: AnyObject { var internalId: CallSessionInternalId { get } var peerId: EnginePeer.Id? { get } var callId: Int64? { get } + var currentReference: InternalGroupCallReference? { get } var hasVideo: Bool { get } var hasScreencast: Bool { get } diff --git a/submodules/CallListUI/Sources/CallListCallItem.swift b/submodules/CallListUI/Sources/CallListCallItem.swift index dd8bd7bea0..e32ac96f5d 100644 --- a/submodules/CallListUI/Sources/CallListCallItem.swift +++ b/submodules/CallListUI/Sources/CallListCallItem.swift @@ -138,16 +138,7 @@ class CallListCallItem: ListViewItem { func selected(listView: ListView) { listView.clearHighlightAnimated(true) - var isVideo = false - for media in self.topMessage.media { - if let action = media as? TelegramMediaAction { - if case let .phoneCall(_, _, _, isVideoValue) = action.action { - isVideo = isVideoValue - break - } - } - } - self.interaction.call(self.topMessage.id.peerId, isVideo) + self.interaction.call(self.topMessage) } static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) { @@ -262,15 +253,7 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { guard let item = self?.layoutParams?.0 else { return false } - var isVideo = false - for media in item.topMessage.media { - if let action = media as? TelegramMediaAction { - if case let .phoneCall(_, _, _, isVideoValue) = action.action { - isVideo = isVideoValue - } - } - } - item.interaction.call(item.topMessage.id.peerId, isVideo) + item.interaction.call(item.topMessage) return true } } @@ -390,6 +373,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { var hadDuration = false var callDuration: Int32? + var isConference = false + var conferenceIsDeclined = false + for message in item.messages { inner: for media in message.media { if let action = media as? TelegramMediaAction { @@ -411,6 +397,36 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { } else { callDuration = nil } + } else if case let .conferenceCall(conferenceCall) = action.action { + isConference = true + + isVideo = conferenceCall.flags.contains(.isVideo) + if message.flags.contains(.Incoming) { + hasIncoming = true + //TODO:localize + let missedTimeout: Int32 + #if DEBUG + missedTimeout = 5 + #else + missedTimeout = 30 + #endif + let currentTime = Int32(Date().timeIntervalSince1970) + if conferenceCall.flags.contains(.isMissed) { + titleColor = item.presentationData.theme.list.itemDestructiveColor + conferenceIsDeclined = true + } else if message.timestamp < currentTime - missedTimeout { + titleColor = item.presentationData.theme.list.itemDestructiveColor + hasMissed = true + } + } else { + hasOutgoing = true + } + if callDuration == nil && !hadDuration { + hadDuration = true + callDuration = conferenceCall.duration + } else { + callDuration = nil + } } break inner } @@ -441,7 +457,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode { titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) } - if hasMissed { + if isConference { + //TODO:localize + if conferenceIsDeclined { + statusAttributedString = NSAttributedString(string: "Declined Group Call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + } else if hasMissed { + statusAttributedString = NSAttributedString(string: "Missed Group Call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + } else { + statusAttributedString = NSAttributedString(string: "Group call", font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) + } + + statusAccessibilityString = statusAttributedString?.string ?? "" + } else if hasMissed { statusAttributedString = NSAttributedString(string: item.presentationData.strings.Notification_CallMissedShort, font: statusFont, textColor: item.presentationData.theme.list.itemSecondaryTextColor) statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed } else if hasIncoming && hasOutgoing { diff --git a/submodules/CallListUI/Sources/CallListController.swift b/submodules/CallListUI/Sources/CallListController.swift index 4abc9c7ad7..ad70beba8c 100644 --- a/submodules/CallListUI/Sources/CallListController.swift +++ b/submodules/CallListUI/Sources/CallListController.swift @@ -92,6 +92,7 @@ public final class CallListController: TelegramBaseController { private let createActionDisposable = MetaDisposable() private let clearDisposable = MetaDisposable() + private var createConferenceCallDisposable: Disposable? public init(context: AccountContext, mode: CallListControllerMode) { self.context = context @@ -163,6 +164,7 @@ public final class CallListController: TelegramBaseController { self.presentationDataDisposable?.dispose() self.peerViewDisposable.dispose() self.clearDisposable.dispose() + self.createConferenceCallDisposable?.dispose() } private func updateThemeAndStrings() { @@ -210,11 +212,16 @@ public final class CallListController: TelegramBaseController { guard !self.presentAccountFrozenInfoIfNeeded() else { return } - let _ = (self.context.engine.calls.createConferenceCall() - |> deliverOnMainQueue).startStandalone(next: { [weak self] call in + if self.createConferenceCallDisposable != nil { + return + } + self.createConferenceCallDisposable = (self.context.engine.calls.createConferenceCall() + |> deliverOnMainQueue).startStrict(next: { [weak self] call in guard let self else { return } + self.createConferenceCallDisposable?.dispose() + self.createConferenceCallDisposable = nil let openCall: () -> Void = { [weak self] in guard let self else { @@ -235,34 +242,51 @@ public final class CallListController: TelegramBaseController { ) } - let controller = InviteLinkInviteController(context: self.context, updatedPresentationData: nil, mode: .groupCall(link: call.link, isRecentlyCreated: true), parentNavigationController: self.navigationController as? NavigationController, completed: { [weak self] result in - guard let self else { - return - } - if let result { - switch result { - case .linkCopied: - //TODO:localize - let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } - self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - if case .undo = action { - openCall() - } - return false - }), in: .window(.root)) - case .openCall: - openCall() + let controller = InviteLinkInviteController( + context: self.context, + updatedPresentationData: nil, + mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)), + initialInvite: .link(link: call.link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil), + parentNavigationController: self.navigationController as? NavigationController, + completed: { [weak self] result in + guard let self else { + return + } + if let result { + switch result { + case .linkCopied: + //TODO:localize + let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } + self.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: "View Call", timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in + if case .undo = action { + openCall() + } + return false + }), in: .window(.root)) + case .openCall: + openCall() + } } } - }) + ) self.present(controller, in: .window(.root), with: nil) }) } override public func loadDisplayNode() { - self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId, isVideo in - if let strongSelf = self { - strongSelf.call(peerId, isVideo: isVideo) + self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] message in + guard let self else { + return + } + + for media in message.media { + if let action = media as? TelegramMediaAction { + if case let .phoneCall(_, _, _, isVideo) = action.action { + self.call(message.id.peerId, isVideo: isVideo) + } else if case .conferenceCall = action.action { + self.openGroupCall(message: message) + } + } } }, joinGroupCall: { [weak self] peerId, activeCall in if let self { @@ -573,6 +597,48 @@ public final class CallListController: TelegramBaseController { })) } + private func openGroupCall(message: EngineMessage) { + var action: TelegramMediaAction? + for media in message.media { + if let media = media as? TelegramMediaAction { + action = media + break + } + } + guard case let .conferenceCall(conferenceCall) = action?.action else { + return + } + if conferenceCall.duration != nil { + return + } + + if let currentGroupCallController = self.context.sharedContext as? VoiceChatController, case let .group(groupCall) = currentGroupCallController.call, let currentCallId = groupCall.callId, currentCallId == conferenceCall.callId { + self.context.sharedContext.navigateToCurrentCall() + return + } + + let signal = self.context.engine.peers.joinCallInvitationInformation(messageId: message.id) + let _ = (signal + |> deliverOnMainQueue).startStandalone(next: { [weak self] resolvedCallLink in + guard let self else { + return + } + self.context.sharedContext.callManager?.joinConferenceCall( + accountContext: self.context, + initialCall: EngineGroupCallDescription( + id: resolvedCallLink.id, + accessHash: resolvedCallLink.accessHash, + title: nil, + scheduleTimestamp: nil, + subscribedToScheduled: false, + isStream: false + ), + reference: .message(id: message.id), + beginWithVideo: conferenceCall.flags.contains(.isVideo) + ) + }) + } + override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) { var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in diff --git a/submodules/CallListUI/Sources/CallListControllerNode.swift b/submodules/CallListUI/Sources/CallListControllerNode.swift index 3d3ffe05ca..867fc17494 100644 --- a/submodules/CallListUI/Sources/CallListControllerNode.swift +++ b/submodules/CallListUI/Sources/CallListControllerNode.swift @@ -62,14 +62,14 @@ private extension EngineCallList.Item { final class CallListNodeInteraction { let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void - let call: (EnginePeer.Id, Bool) -> Void + let call: (EngineMessage) -> Void let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void let delete: ([EngineMessage.Id]) -> Void let updateShowCallsTab: (Bool) -> Void let openGroupCall: (EnginePeer.Id) -> Void let createGroupCall: () -> Void - init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EnginePeer.Id, Bool) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) { + init(setMessageIdWithRevealedOptions: @escaping (EngineMessage.Id?, EngineMessage.Id?) -> Void, call: @escaping (EngineMessage) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, delete: @escaping ([EngineMessage.Id]) -> Void, updateShowCallsTab: @escaping (Bool) -> Void, openGroupCall: @escaping (EnginePeer.Id) -> Void, createGroupCall: @escaping () -> Void) { self.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions self.call = call self.openInfo = openInfo @@ -222,7 +222,7 @@ final class CallListControllerNode: ASDisplayNode { private let emptyButtonIconNode: ASImageNode private let emptyButtonTextNode: ImmediateTextNode - private let call: (EnginePeer.Id, Bool) -> Void + private let call: (EngineMessage) -> Void private let joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void private let createGroupCall: () -> Void private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void @@ -234,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode { private var previousContentOffset: ListViewVisibleContentOffset? - init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EnginePeer.Id, Bool) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) { + init(controller: CallListController, context: AccountContext, mode: CallListControllerMode, presentationData: PresentationData, call: @escaping (EngineMessage) -> Void, joinGroupCall: @escaping (EnginePeer.Id, EngineGroupCallDescription) -> Void, openInfo: @escaping (EnginePeer.Id, [EngineMessage]) -> Void, emptyStateUpdated: @escaping (Bool) -> Void, createGroupCall: @escaping () -> Void) { self.controller = controller self.context = context self.mode = mode @@ -333,8 +333,8 @@ final class CallListControllerNode: ASDisplayNode { } } } - }, call: { [weak self] peerId, isVideo in - self?.call(peerId, isVideo) + }, call: { [weak self] message in + self?.call(message) }, openInfo: { [weak self] peerId, messages in self?.openInfo(peerId, messages) }, delete: { [weak self] messageIds in diff --git a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift index d1ad615edc..b881c0d803 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkInviteController.swift @@ -154,14 +154,32 @@ private func preparedTransition(from fromEntries: [InviteLinkInviteEntry], to to return InviteLinkInviteTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading) } +private func getBackgroundColor(theme: PresentationTheme) -> UIColor { + return theme.actionSheet.opaqueItemBackgroundColor +} + public final class InviteLinkInviteController: ViewController { private var controllerNode: Node { return self.displayNode as! Node } public enum Mode { + public struct GroupCall { + public let callId: Int64 + public let accessHash: Int64 + public let isRecentlyCreated: Bool + public let canRevoke: Bool + + public init(callId: Int64, accessHash: Int64, isRecentlyCreated: Bool, canRevoke: Bool) { + self.callId = callId + self.accessHash = accessHash + self.isRecentlyCreated = isRecentlyCreated + self.canRevoke = canRevoke + } + } + case groupOrChannel(peerId: EnginePeer.Id) - case groupCall(link: String, isRecentlyCreated: Bool) + case groupCall(GroupCall) } public enum CompletionResult { @@ -173,6 +191,7 @@ public final class InviteLinkInviteController: ViewController { private let context: AccountContext private let mode: Mode + private let initialInvite: ExportedInvitation? private weak var parentNavigationController: NavigationController? private var presentationData: PresentationData @@ -180,9 +199,10 @@ public final class InviteLinkInviteController: ViewController { fileprivate let completed: ((CompletionResult?) -> Void)? - public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: Mode, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) { + public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil, mode: Mode, initialInvite: ExportedInvitation?, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) { self.context = context self.mode = mode + self.initialInvite = initialInvite self.parentNavigationController = parentNavigationController self.completed = completed @@ -215,7 +235,7 @@ public final class InviteLinkInviteController: ViewController { } override public func loadDisplayNode() { - self.displayNode = Node(context: self.context, presentationData: self.presentationData, mode: self.mode, controller: self) + self.displayNode = Node(context: self.context, presentationData: self.presentationData, mode: self.mode, controller: self, initialInvite: self.initialInvite) } private var didAppearOnce: Bool = false @@ -296,7 +316,7 @@ public final class InviteLinkInviteController: ViewController { private var revokeDisposable = MetaDisposable() - init(context: AccountContext, presentationData: PresentationData, mode: InviteLinkInviteController.Mode, controller: InviteLinkInviteController) { + init(context: AccountContext, presentationData: PresentationData, mode: InviteLinkInviteController.Mode, controller: InviteLinkInviteController, initialInvite: ExportedInvitation?) { self.context = context self.mode = mode @@ -319,7 +339,7 @@ public final class InviteLinkInviteController: ViewController { self.headerNode.clipsToBounds = false self.headerBackgroundNode = ASDisplayNode() - self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.headerBackgroundNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme) self.headerBackgroundNode.cornerRadius = 16.0 self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] @@ -338,7 +358,7 @@ public final class InviteLinkInviteController: ViewController { self.historyBackgroundContentNode = ASDisplayNode() self.historyBackgroundContentNode.isLayerBacked = true - self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme) self.historyBackgroundNode.addSubnode(self.historyBackgroundContentNode) @@ -354,7 +374,7 @@ public final class InviteLinkInviteController: ViewController { self.backgroundColor = nil self.isOpaque = false - let mainInvitePromise = ValuePromise(nil) + let mainInvitePromise = ValuePromise(initialInvite) self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in guard let self else { @@ -363,7 +383,6 @@ public final class InviteLinkInviteController: ViewController { guard let node = node as? ContextReferenceContentNode else { return } - let presentationData = context.sharedContext.currentPresentationData.with { $0 } var items: [ContextMenuItem] = [] items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in @@ -381,17 +400,17 @@ public final class InviteLinkInviteController: ViewController { } }))) - if case let .groupOrChannel(peerId) = self.mode { - items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in - return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) - }, action: { [weak self] _, f in - f(.dismissWithoutContent) - - guard let self else { - return - } - - if let invite = invite { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) + }, action: { [weak self] _, f in + f(.dismissWithoutContent) + + guard let self else { + return + } + + if let invite { + if case let .groupOrChannel(peerId) = self.mode { let _ = (context.account.postbox.loadedPeerWithId(peerId) |> deliverOnMainQueue).start(next: { [weak self] peer in guard let strongSelf = self else { @@ -404,12 +423,17 @@ public final class InviteLinkInviteController: ViewController { isGroup = true } let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get()) - let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)) + let controller = QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)) strongSelf.controller?.present(controller, in: .window(.root)) }) + } else if case .groupCall = self.mode { + let controller = QrCodeScreen(context: context, updatedPresentationData: (self.presentationData, self.presentationDataPromise.get()), subject: .invite(invite: invite, type: .channel)) + self.controller?.present(controller, in: .window(.root)) } - }))) - + } + }))) + + if case let .groupOrChannel(peerId) = self.mode { items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) }, action: { [ weak self] _, f in @@ -454,7 +478,45 @@ public final class InviteLinkInviteController: ViewController { self?.controller?.present(controller, in: .window(.root)) }) }))) + } else if case let .groupCall(groupCall) = self.mode, groupCall.canRevoke { + items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextRevoke, textColor: .destructive, icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor) + }, action: { [ weak self] _, f in + f(.dismissWithoutContent) + + guard let self else { + return + } + + let controller = ActionSheetController(presentationData: presentationData) + let dismissAction: () -> Void = { [weak controller] in + controller?.dismissAnimated() + } + //TODO:localize + controller.setItemGroups([ + ActionSheetItemGroup(items: [ + ActionSheetTextItem(title: "Revoke Link"), + ActionSheetButtonItem(title: presentationData.strings.GroupInfo_InviteLink_RevokeLink, color: .destructive, action: { [weak self] in + dismissAction() + guard let self else { + return + } + + if let inviteLink = invite?.link { + let _ = (context.engine.calls.revokeConferenceInviteLink(reference: .id(id: groupCall.callId, accessHash: groupCall.accessHash), link: inviteLink) |> deliverOnMainQueue).start(next: { result in + mainInvitePromise.set(.link(link: result.listenerLink, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil)) + }) + + let presentationData = context.sharedContext.currentPresentationData.with { $0 } + self.controller?.present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: presentationData.strings.InviteLink_InviteLinkRevoked), elevatedLayout: false, animateInAsReplacement: false, action: { _ in return false }), in: .window(.root)) + } + }) + ]), + ActionSheetItemGroup(items: [ActionSheetButtonItem(title: presentationData.strings.Common_Cancel, action: { dismissAction() })]) + ]) + self.controller?.present(controller, in: .window(.root)) + }))) } let contextController = ContextController(presentationData: presentationData, source: .reference(InviteLinkContextReferenceContentSource(controller: controller, sourceNode: node)), items: .single(ContextController.Items(content: .list(items))), gesture: gesture) @@ -597,15 +659,16 @@ public final class InviteLinkInviteController: ViewController { strongSelf.enqueueTransition(transition) } }) - case let .groupCall(link, isRecentlyCreated): - //TODO:release - let tempInfo: Signal = .single(Void()) |> delay(0.0, queue: .mainQueue()) - + case let .groupCall(groupCall): + // A workaround to skip the first run of the event cycle + let delayOfZero = Signal.single(()) |> delay(0.0, queue: .mainQueue()) + self.disposable = (combineLatest(queue: .mainQueue(), self.presentationDataPromise.get(), - tempInfo + mainInvitePromise.get(), + delayOfZero ) - |> deliverOnMainQueue).start(next: { [weak self] presentationData, _ in + |> deliverOnMainQueue).start(next: { [weak self] presentationData, mainInvite, _ in guard let self else { return } @@ -615,9 +678,9 @@ public final class InviteLinkInviteController: ViewController { let helpText: String = "Anyone on Telegram can join your call by following the link below." entries.append(.header(title: "Call Link", text: helpText)) - let mainInvite: ExportedInvitation = .link(link: link, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil) + let mainInvite: ExportedInvitation = .link(link: mainInvite?.link ?? "", title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: self.context.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil) - entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: isRecentlyCreated)) + entries.append(.mainLink(invitation: mainInvite, isCall: true, isRecentlyCreated: groupCall.isRecentlyCreated)) let previousEntries = previousEntries.swap(entries) @@ -675,8 +738,8 @@ public final class InviteLinkInviteController: ViewController { self.presentationData = presentationData self.presentationDataPromise.set(.single(presentationData)) - self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor - self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor + self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme) + self.headerBackgroundNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme) self.titleNode.attributedText = NSAttributedString(string: self.presentationData.strings.InviteLink_InviteLink, font: Font.bold(17.0), textColor: self.presentationData.theme.actionSheet.primaryTextColor) self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))! diff --git a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift index cd3dcb291a..cf7cdf5aeb 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkListController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkListController.swift @@ -530,7 +530,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio } else { isGroup = true } - presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) }) }))) @@ -719,7 +719,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio isGroup = true } Queue.mainQueue().after(0.2) { - presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) } }) }))) diff --git a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift index b87a65d5e0..bc5f73a1e7 100644 --- a/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift +++ b/submodules/InviteLinksUI/Sources/InviteLinkViewController.swift @@ -755,7 +755,7 @@ public final class InviteLinkViewController: ViewController { isGroup = true } let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get()) - strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), in: .window(.root)) + strongSelf.controller?.present(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), in: .window(.root)) }) }))) } diff --git a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift index 854b42cfc0..783f04db24 100644 --- a/submodules/PeerInfoUI/Sources/ChannelMembersController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelMembersController.swift @@ -655,7 +655,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio }, inviteViaLink: { if let controller = getControllerImpl?() { dismissInputImpl?() - presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: peerId), parentNavigationController: controller.navigationController as? NavigationController), nil) + presentControllerImpl?(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: peerId), initialInvite: nil, parentNavigationController: controller.navigationController as? NavigationController), nil) } }, updateHideMembers: { value in let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start() diff --git a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift index be840d4941..3f27534b60 100644 --- a/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift +++ b/submodules/PeerInfoUI/Sources/ChannelVisibilityController.swift @@ -1609,7 +1609,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta } else { isGroup = true } - presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, isGroup: isGroup)), nil) + presentControllerImpl?(QrCodeScreen(context: context, updatedPresentationData: updatedPresentationData, subject: .invite(invite: invite, type: isGroup ? .group : .channel)), nil) }) } }) diff --git a/submodules/QrCodeUI/Sources/QrCodeScreen.swift b/submodules/QrCodeUI/Sources/QrCodeScreen.swift index d4568f4d8b..69e11b3c2e 100644 --- a/submodules/QrCodeUI/Sources/QrCodeScreen.swift +++ b/submodules/QrCodeUI/Sources/QrCodeScreen.swift @@ -35,9 +35,15 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie } public final class QrCodeScreen: ViewController { + public enum SubjectType { + case group + case channel + case groupCall + } + public enum Subject { case peer(peer: EnginePeer) - case invite(invite: ExportedInvitation, isGroup: Bool) + case invite(invite: ExportedInvitation, type: SubjectType) case chatFolder(slug: String) var link: String { @@ -239,9 +245,17 @@ public final class QrCodeScreen: ViewController { let title: String let text: String switch subject { - case let .invite(_, isGroup): + case let .invite(_, type): title = self.presentationData.strings.InviteLink_QRCode_Title - text = isGroup ? self.presentationData.strings.InviteLink_QRCode_Info : self.presentationData.strings.InviteLink_QRCode_InfoChannel + switch type { + case .group: + text = self.presentationData.strings.InviteLink_QRCode_Info + case .channel: + text = self.presentationData.strings.InviteLink_QRCode_InfoChannel + case .groupCall: + //TODO:localize + text = "Everyone on Telegram can scan this code to join your group call." + } case .chatFolder: title = self.presentationData.strings.InviteLink_QRCodeFolder_Title text = self.presentationData.strings.InviteLink_QRCodeFolder_Text diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index 766e55d009..b3d7a70f3b 100644 --- a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift +++ b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift @@ -161,7 +161,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext { defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, - isStream: state.isStream + isStream: state.isStream, + isCreator: state.isCreator ), topParticipants: topParticipants, participantCount: state.totalCount, @@ -888,6 +889,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void) private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)? + public var currentReference: InternalGroupCallReference? public let internalId: CallSessionInternalId public let peerId: EnginePeer.Id? private let isChannel: Bool @@ -1208,6 +1210,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { self.getDeviceAccessData = getDeviceAccessData self.initialCall = initialCall + self.currentReference = initialCall?.reference self.callId = initialCall?.description.id self.internalId = internalId @@ -1963,7 +1966,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, isVideoEnabled: callInfo.isVideoEnabled, unmutedVideoLimit: callInfo.unmutedVideoLimit, - isStream: callInfo.isStream + isStream: callInfo.isStream, + isCreator: callInfo.isCreator )), audioSessionControl: self.audioSessionControl) } else { self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo( @@ -1979,7 +1983,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, - isStream: callInfo.isStream + isStream: callInfo.isStream, + isCreator: callInfo.isCreator )))) self.summaryParticipantsState.set(.single(SummaryParticipantsState( @@ -2301,6 +2306,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { guard let self else { return } + + self.currentReference = .id(id: joinCallResult.callInfo.id, accessHash: joinCallResult.callInfo.accessHash) + let clientParams = joinCallResult.jsonParams if let data = clientParams.data(using: .utf8), let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] { if let video = dict["video"] as? [String: Any] { @@ -2910,7 +2918,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, isVideoEnabled: state.isVideoEnabled, unmutedVideoLimit: state.unmutedVideoLimit, - isStream: callInfo.isStream + isStream: callInfo.isStream, + isCreator: callInfo.isCreator )))) self.summaryParticipantsState.set(.single(SummaryParticipantsState( diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index bd5a193cc0..6e89966067 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift @@ -25,6 +25,7 @@ import LegacyComponents import TooltipUI import BlurredBackgroundComponent import CallsEmoji +import InviteLinksUI extension VideoChatCall { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { @@ -652,6 +653,51 @@ final class VideoChatScreenComponent: Component { guard case let .group(groupCall) = self.currentCall else { return } + + if groupCall.isConference { + guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { + return + } + guard let currentReference = groupCall.currentReference, case let .id(callId, accessHash) = currentReference else { + return + } + guard let callState = self.callState else { + return + } + var presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 } + presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) + let controller = InviteLinkInviteController( + context: groupCall.accountContext, + updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), + mode: .groupCall(InviteLinkInviteController.Mode.GroupCall( + callId: callId, + accessHash: accessHash, + isRecentlyCreated: false, + canRevoke: callState.canManageCall + )), + initialInvite: .link(link: inviteLinks.listenerLink, title: nil, isPermanent: true, requestApproval: false, isRevoked: false, adminId: groupCall.accountContext.account.peerId, date: 0, startDate: nil, expireDate: nil, usageLimit: nil, count: nil, requestedCount: nil, pricing: nil), + parentNavigationController: navigationController, + completed: { [weak self] result in + guard let self, case let .group(groupCall) = self.currentCall else { + return + } + if let result { + switch result { + case .linkCopied: + //TODO:localize + let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 } + self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in + return false + }), in: .current) + case .openCall: + break + } + } + } + ) + self.environment?.controller()?.present(controller, in: .window(.root), with: nil) + return + } let formatSendTitle: (String) -> String = { string in var string = string diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift index 259ec6a932..1fd32412b3 100644 --- a/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift +++ b/submodules/TelegramCallsUI/Sources/VideoChatScreenInviteMembers.swift @@ -16,31 +16,6 @@ extension VideoChatScreenComponent.View { return } - /*if groupCall.accountContext.sharedContext.immediateExperimentalUISettings.conferenceDebug { - guard let navigationController = self.environment?.controller()?.navigationController as? NavigationController else { - return - } - var presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 } - presentationData = presentationData.withUpdated(theme: defaultDarkColorPresentationTheme) - let controller = InviteLinkInviteController(context: groupCall.accountContext, updatedPresentationData: (initial: presentationData, signal: .single(presentationData)), mode: .groupCall(link: "https://t.me/call/+abbfbffll123", isRecentlyCreated: false), parentNavigationController: navigationController, completed: { [weak self] result in - guard let self, case let .group(groupCall) = self.currentCall else { - return - } - if let result { - switch result { - case .linkCopied: - //TODO:localize - let presentationData = groupCall.accountContext.sharedContext.currentPresentationData.with { $0 } - self.environment?.controller()?.present(UndoOverlayController(presentationData: presentationData, content: .universal(animation: "anim_linkcopied", scale: 0.08, colors: ["info1.info1.stroke": UIColor.clear, "info2.info2.Fill": UIColor.clear], title: nil, text: "Call link copied.", customUndoText: nil, timeout: nil), elevatedLayout: false, animateInAsReplacement: false, action: { action in - return false - }), in: .current) - } - } - }) - self.environment?.controller()?.present(controller, in: .window(.root), with: nil) - return - }*/ - if groupCall.isConference { var disablePeerIds: [EnginePeer.Id] = [] disablePeerIds.append(groupCall.accountContext.account.peerId) diff --git a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift index 9feaa78847..27ee6e65f7 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/StoreMessage_Telegram.swift @@ -93,13 +93,18 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], tags.insert(.webPage) } else if let action = attachment as? TelegramMediaAction { switch action.action { - case let .phoneCall(_, discardReason, _, _): - globalTags.insert(.Calls) - if incoming, let discardReason = discardReason, case .missed = discardReason { - globalTags.insert(.MissedCalls) - } - default: - break + case let .phoneCall(_, discardReason, _, _): + globalTags.insert(.Calls) + if incoming, let discardReason = discardReason, case .missed = discardReason { + globalTags.insert(.MissedCalls) + } + case let .conferenceCall(conferenceCall): + globalTags.insert(.Calls) + if incoming, conferenceCall.flags.contains(.isMissed) { + globalTags.insert(.MissedCalls) + } + default: + break } } else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil { tags.insert(.liveLocation) @@ -118,9 +123,6 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute], } } - if !incoming { - assert(true) - } return (tags, globalTags) } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift index 650be6ac5c..eaa73c54d8 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/GroupCalls.swift @@ -96,6 +96,7 @@ public struct GroupCallInfo: Equatable { public var isVideoEnabled: Bool public var unmutedVideoLimit: Int public var isStream: Bool + public var isCreator: Bool public init( id: Int64, @@ -110,7 +111,8 @@ public struct GroupCallInfo: Equatable { defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, isVideoEnabled: Bool, unmutedVideoLimit: Int, - isStream: Bool + isStream: Bool, + isCreator: Bool ) { self.id = id self.accessHash = accessHash @@ -125,6 +127,7 @@ public struct GroupCallInfo: Equatable { self.isVideoEnabled = isVideoEnabled self.unmutedVideoLimit = unmutedVideoLimit self.isStream = isStream + self.isCreator = isCreator } } @@ -150,7 +153,8 @@ extension GroupCallInfo { defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0), isVideoEnabled: (flags & (1 << 9)) != 0, unmutedVideoLimit: Int(unmutedVideoLimit), - isStream: (flags & (1 << 12)) != 0 + isStream: (flags & (1 << 12)) != 0, + isCreator: (flags & (1 << 15)) != 0 ) case .groupCallDiscarded: return nil @@ -454,17 +458,17 @@ public enum GetGroupCallParticipantsError { func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal { let accountPeerId = account.peerId - let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError> + let sortAscendingValue: Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Bool), GetGroupCallParticipantsError> sortAscendingValue = _internal_getCurrentGroupCall(account: account, reference: reference) |> mapError { _ -> GetGroupCallParticipantsError in return .generic } - |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool), GetGroupCallParticipantsError> in + |> mapToSignal { result -> Signal<(Bool, Int32?, Bool, GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, Bool, Int, Bool, Bool), GetGroupCallParticipantsError> in guard let result = result else { return .fail(.generic) } - return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream)) + return .single((sortAscending ?? result.info.sortAscending, result.info.scheduleTimestamp, result.info.subscribedToScheduled, result.info.defaultParticipantsAreMuted, result.info.isVideoEnabled, result.info.unmutedVideoLimit, result.info.isStream, result.info.isCreator)) } return combineLatest( @@ -481,7 +485,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro let version: Int32 let nextParticipantsFetchOffset: String? - let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp + let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream, isCreator) = sortAscendingAndScheduleTimestamp switch result { case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion): @@ -506,7 +510,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro participants: parsedParticipants, nextParticipantsFetchOffset: nextParticipantsFetchOffset, adminIds: Set(), - isCreator: false, + isCreator: isCreator, defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false), sortAscending: sortAscendingValue, recordingStartTimestamp: nil, @@ -2959,6 +2963,29 @@ func _internal_createConferenceCall(postbox: Postbox, network: Network, accountP } } +public enum RevokeConferenceInviteLinkError { + case generic +} + +func _internal_revokeConferenceInviteLink(account: Account, reference: InternalGroupCallReference, link: String) -> Signal { + return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse)) + |> mapError { _ -> RevokeConferenceInviteLinkError in + return .generic + } + |> mapToSignal { result -> Signal in + account.stateManager.addUpdates(result) + + return _internal_groupCallInviteLinks(account: account, reference: reference, isConference: true) + |> castError(RevokeConferenceInviteLinkError.self) + |> mapToSignal { result -> Signal in + guard let result = result else { + return .fail(.generic) + } + return .single(result) + } + } +} + public enum ConfirmAddConferenceParticipantError { case generic } diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift index f179efbca8..9ebc1e1fff 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Calls/TelegramEngineCalls.swift @@ -96,6 +96,10 @@ public extension TelegramEngine { public func createConferenceCall() -> Signal { return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId) } + + public func revokeConferenceInviteLink(reference: InternalGroupCallReference, link: String) -> Signal { + return _internal_revokeConferenceInviteLink(account: self.account, reference: reference, link: link) + } public func pollConferenceCallBlockchain(reference: InternalGroupCallReference, subChainId: Int, offset: Int, limit: Int) -> Signal<(blocks: [Data], nextOffset: Int)?, NoError> { return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit) diff --git a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift index 6ccacd8c1f..7bed9b2257 100644 --- a/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Components/PeerInfo/PeerInfoScreen/Sources/PeerInfoScreen.swift @@ -14124,7 +14124,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa createInviteLinkImpl = { [weak contactsController] in contactsController?.view.window?.endEditing(true) - contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root)) + contactsController?.present(InviteLinkInviteController(context: context, updatedPresentationData: updatedPresentationData, mode: .groupOrChannel(peerId: groupPeer.id), initialInvite: nil, parentNavigationController: contactsController?.navigationController as? NavigationController), in: .window(.root)) } parentController?.push(contactsController) diff --git a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift index 2900d2d7ef..bcb6f012d2 100644 --- a/submodules/TelegramUI/Sources/OpenResolvedUrl.swift +++ b/submodules/TelegramUI/Sources/OpenResolvedUrl.swift @@ -402,6 +402,15 @@ func openResolvedUrlImpl( members: resolvedCallLink.members, totalMemberCount: resolvedCallLink.totalMemberCount )))) + }, error: { _ in + var elevatedLayout = true + if case .chat = urlContext { + elevatedLayout = false + } + //TODO:localize + present(UndoOverlayController(presentationData: presentationData, content: .linkRevoked(text: "This link is no longer active"), elevatedLayout: elevatedLayout, animateInAsReplacement: false, action: { _ in + return true + }), nil) }) case let .localization(identifier): dismissInput() @@ -788,6 +797,7 @@ func openResolvedUrlImpl( } if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) { let presentationData = context.sharedContext.currentPresentationData.with { $0 } + //TODO:localize let controller = UndoOverlayController( presentationData: presentationData, content: .universal(