diff --git a/build-system/Make/BuildEnvironment.py b/build-system/Make/BuildEnvironment.py index 3f7311aa73..2cb64aae7b 100644 --- a/build-system/Make/BuildEnvironment.py +++ b/build-system/Make/BuildEnvironment.py @@ -11,25 +11,26 @@ def is_apple_silicon(): return False -def get_clean_env(): +def get_clean_env(use_clean_env=True): clean_env = os.environ.copy() - clean_env['PATH'] = '/usr/bin:/bin:/usr/sbin:/sbin' + if use_clean_env: + clean_env['PATH'] = '/usr/bin:/bin:/usr/sbin:/sbin' return clean_env -def resolve_executable(program): +def resolve_executable(program, use_clean_env=True): def is_executable(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - for path in get_clean_env()["PATH"].split(os.pathsep): + for path in get_clean_env(use_clean_env=use_clean_env)["PATH"].split(os.pathsep): executable_file = os.path.join(path, program) if is_executable(executable_file): return executable_file return None -def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False): - executable_path = resolve_executable(path) +def run_executable_with_output(path, arguments, use_clean_env=True, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False): + executable_path = resolve_executable(path, use_clean_env=use_clean_env) if executable_path is None: raise Exception('Could not resolve {} to a valid executable file'.format(path)) @@ -45,7 +46,7 @@ def run_executable_with_output(path, arguments, decode=True, input=None, stderr_ stdout=subprocess.PIPE, stderr=stderr_assignment, stdin=subprocess.PIPE, - env=get_clean_env() + env=get_clean_env(use_clean_env=use_clean_env) ) if input is not None: output_data, _ = process.communicate(input=input) diff --git a/build-system/Make/DeployToFirebase.py b/build-system/Make/DeployToFirebase.py index 32c12bf018..e0dcc994a8 100644 --- a/build-system/Make/DeployToFirebase.py +++ b/build-system/Make/DeployToFirebase.py @@ -2,8 +2,9 @@ import os import sys import argparse import json +import re -from BuildEnvironment import check_run_system +from BuildEnvironment import run_executable_with_output def deploy_to_firebase(args): if not os.path.exists(args.configuration): @@ -26,18 +27,26 @@ def deploy_to_firebase(args): if key not in configuration_dict: print('Configuration at {} does not contain {}'.format(args.configuration, key)) sys.exit(1) - - debug_flag = "--debug" if args.debug else "" - command = 'firebase appdistribution:distribute --app {app_id} --groups "{group}" {debug_flag}'.format( - app_id=configuration_dict['app_id'], - group=configuration_dict['group'], - debug_flag=debug_flag - ) - - command += ' "{ipa_path}"'.format(ipa_path=args.ipa) - check_run_system(command) - + firebase_arguments = [ + 'appdistribution:distribute', + '--app', configuration_dict['app_id'], + '--groups', configuration_dict['group'], + args.ipa + ] + + output = run_executable_with_output( + 'firebase', + firebase_arguments, + use_clean_env=False, + check_result=True + ) + + sharing_link_match = re.search(r'Share this release with testers who have access: (https://\S+)', output) + if sharing_link_match: + print(f"Sharing link: {sharing_link_match.group(1)}") + else: + print("No sharing link found in the output.") if __name__ == '__main__': parser = argparse.ArgumentParser(prog='deploy-firebase') 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 e4e0c66218..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 @@ -519,10 +519,7 @@ final class CallListControllerNode: ASDisplayNode { let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) |> map { configuration -> Bool in - var isConferencePossible = false - if context.sharedContext.immediateExperimentalUISettings.conferenceDebug { - isConferencePossible = true - } + var isConferencePossible = true if let data = configuration.data, let value = data["ios_enable_conference"] as? Double { isConferencePossible = value != 0.0 } diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index 6af1749af9..143f6d3439 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -100,7 +100,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { case enableReactionOverrides(Bool) case compressedEmojiCache(Bool) case storiesJpegExperiment(Bool) - case conferenceDebug(Bool) case checkSerializedData(Bool) case enableQuickReactionSwitch(Bool) case disableReloginTokens(Bool) @@ -134,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry { return DebugControllerSection.web.rawValue case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: return DebugControllerSection.experiments.rawValue - case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .conferenceDebug, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation: + case .clearTips, .resetNotifications, .crash, .fillLocalSavedMessageCache, .resetDatabase, .resetDatabaseAndCache, .resetHoles, .resetTagHoles, .reindexUnread, .resetCacheIndex, .reindexCache, .resetBiometricsData, .optimizeDatabase, .photoPreview, .knockoutWallpaper, .compressedEmojiCache, .storiesJpegExperiment, .checkSerializedData, .enableQuickReactionSwitch, .experimentalCompatibility, .enableDebugDataDisplay, .rippleEffect, .browserExperiment, .localTranscription, .enableReactionOverrides, .restorePurchases, .disableReloginTokens, .liveStreamV2, .experimentalCallMute, .playerV2, .devRequests, .fakeAds, .enableLocalTranslation: return DebugControllerSection.experiments.rawValue case .logTranslationRecognition, .resetTranslationStates: return DebugControllerSection.translation.rawValue @@ -243,8 +242,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { return 47 case .disableReloginTokens: return 48 - case .conferenceDebug: - return 49 case .checkSerializedData: return 50 case .enableQuickReactionSwitch: @@ -1311,16 +1308,6 @@ private enum DebugControllerEntry: ItemListNodeEntry { }) }).start() }) - case let .conferenceDebug(value): - return ItemListSwitchItem(presentationData: presentationData, title: "Conference Debug", value: value, sectionId: self.section, style: .blocks, updated: { value in - let _ = arguments.sharedContext.accountManager.transaction ({ transaction in - transaction.updateSharedData(ApplicationSpecificSharedDataKeys.experimentalUISettings, { settings in - var settings = settings?.get(ExperimentalUISettings.self) ?? ExperimentalUISettings.defaultSettings - settings.conferenceDebug = value - return PreferencesEntry(settings) - }) - }).start() - }) case let .checkSerializedData(value): return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in let _ = arguments.sharedContext.accountManager.transaction ({ transaction in @@ -1552,7 +1539,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment)) entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens)) - entries.append(.conferenceDebug(experimentalSettings.conferenceDebug)) entries.append(.checkSerializedData(experimentalSettings.checkSerializedData)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) entries.append(.liveStreamV2(experimentalSettings.liveStreamV2)) 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/CallControllerNodeV2.swift b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift index 5641bf8f89..9f5f9a24db 100644 --- a/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift +++ b/submodules/TelegramCallsUI/Sources/CallControllerNodeV2.swift @@ -167,10 +167,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP self.conferenceAddParticipant?() } - var isConferencePossible = false - if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug { - isConferencePossible = true - } + var isConferencePossible = true if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double { isConferencePossible = value != 0.0 } diff --git a/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift b/submodules/TelegramCallsUI/Sources/PresentationGroupCall.swift index e0047cd47b..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( @@ -2988,6 +2997,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { result.append(OngoingGroupCallContext.MediaChannelDescription( kind: .audio, + peerId: participant.peer.id.id._internalGetInt64Value(), audioSsrc: audioSsrc, videoDescription: nil )) @@ -2999,6 +3009,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall { result.append(OngoingGroupCallContext.MediaChannelDescription( kind: .audio, + peerId: participant.peer.id.id._internalGetInt64Value(), audioSsrc: screencastSsrc, videoDescription: nil )) diff --git a/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift b/submodules/TelegramCallsUI/Sources/VideoChatScreen.swift index cb5dd1da6d..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 @@ -705,7 +751,7 @@ final class VideoChatScreenComponent: Component { peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) ) ) - |> deliverOnMainQueue).start(next: { [weak self] peerList in + |> deliverOnMainQueue).start(next: { [weak self] peerList in guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else { return } 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( diff --git a/submodules/TelegramVoip/Sources/GroupCallContext.swift b/submodules/TelegramVoip/Sources/GroupCallContext.swift index 93fe446b53..8903a2d4c7 100644 --- a/submodules/TelegramVoip/Sources/GroupCallContext.swift +++ b/submodules/TelegramVoip/Sources/GroupCallContext.swift @@ -270,11 +270,13 @@ public final class OngoingGroupCallContext { } public var kind: Kind + public var peerId: Int64 public var audioSsrc: UInt32 public var videoDescription: String? - public init(kind: Kind, audioSsrc: UInt32, videoDescription: String?) { + public init(kind: Kind, peerId: Int64, audioSsrc: UInt32, videoDescription: String?) { self.kind = kind + self.peerId = peerId self.audioSsrc = audioSsrc self.videoDescription = videoDescription } @@ -575,6 +577,7 @@ public final class OngoingGroupCallContext { } return OngoingGroupCallMediaChannelDescription( type: mappedType, + peerId: channel.peerId, audioSsrc: channel.audioSsrc, videoDescription: channel.videoDescription ) @@ -688,6 +691,7 @@ public final class OngoingGroupCallContext { } return OngoingGroupCallMediaChannelDescription( type: mappedType, + peerId: channel.peerId, audioSsrc: channel.audioSsrc, videoDescription: channel.videoDescription ) diff --git a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h index 25ec5e072c..bc2349f7b4 100644 --- a/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h +++ b/submodules/TgVoipWebrtc/PublicHeaders/TgVoipWebrtc/OngoingCallThreadLocalContext.h @@ -332,10 +332,12 @@ typedef NS_ENUM(int32_t, OngoingGroupCallMediaChannelType) { @interface OngoingGroupCallMediaChannelDescription : NSObject @property (nonatomic, readonly) OngoingGroupCallMediaChannelType type; +@property (nonatomic, readonly) uint64_t peerId; @property (nonatomic, readonly) uint32_t audioSsrc; @property (nonatomic, strong, readonly) NSString * _Nullable videoDescription; - (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type + peerId:(int64_t)peerId audioSsrc:(uint32_t)audioSsrc videoDescription:(NSString * _Nullable)videoDescription; diff --git a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm index abcfdbd27c..aade0011a9 100644 --- a/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm +++ b/submodules/TgVoipWebrtc/Sources/OngoingCallThreadLocalContext.mm @@ -3029,11 +3029,13 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp @implementation OngoingGroupCallMediaChannelDescription - (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type - audioSsrc:(uint32_t)audioSsrc - videoDescription:(NSString * _Nullable)videoDescription { + peerId:(int64_t)peerId + audioSsrc:(uint32_t)audioSsrc + videoDescription:(NSString * _Nullable)videoDescription { self = [super init]; if (self != nil) { _type = type; + _peerId = peerId; _audioSsrc = audioSsrc; _videoDescription = videoDescription; }