Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios

This commit is contained in:
Mikhail Filimonov 2025-04-03 08:52:07 +04:00
commit b78817526e
25 changed files with 426 additions and 182 deletions

View File

@ -11,25 +11,26 @@ def is_apple_silicon():
return False return False
def get_clean_env(): def get_clean_env(use_clean_env=True):
clean_env = os.environ.copy() 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 return clean_env
def resolve_executable(program): def resolve_executable(program, use_clean_env=True):
def is_executable(fpath): def is_executable(fpath):
return os.path.isfile(fpath) and os.access(fpath, os.X_OK) 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) executable_file = os.path.join(path, program)
if is_executable(executable_file): if is_executable(executable_file):
return executable_file return executable_file
return None return None
def run_executable_with_output(path, arguments, decode=True, input=None, stderr_to_stdout=True, print_command=False, check_result=False): 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) executable_path = resolve_executable(path, use_clean_env=use_clean_env)
if executable_path is None: if executable_path is None:
raise Exception('Could not resolve {} to a valid executable file'.format(path)) 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, stdout=subprocess.PIPE,
stderr=stderr_assignment, stderr=stderr_assignment,
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
env=get_clean_env() env=get_clean_env(use_clean_env=use_clean_env)
) )
if input is not None: if input is not None:
output_data, _ = process.communicate(input=input) output_data, _ = process.communicate(input=input)

View File

@ -2,8 +2,9 @@ import os
import sys import sys
import argparse import argparse
import json import json
import re
from BuildEnvironment import check_run_system from BuildEnvironment import run_executable_with_output
def deploy_to_firebase(args): def deploy_to_firebase(args):
if not os.path.exists(args.configuration): if not os.path.exists(args.configuration):
@ -26,18 +27,26 @@ def deploy_to_firebase(args):
if key not in configuration_dict: if key not in configuration_dict:
print('Configuration at {} does not contain {}'.format(args.configuration, key)) print('Configuration at {} does not contain {}'.format(args.configuration, key))
sys.exit(1) 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__': if __name__ == '__main__':
parser = argparse.ArgumentParser(prog='deploy-firebase') parser = argparse.ArgumentParser(prog='deploy-firebase')

View File

@ -423,6 +423,7 @@ public protocol PresentationGroupCall: AnyObject {
var internalId: CallSessionInternalId { get } var internalId: CallSessionInternalId { get }
var peerId: EnginePeer.Id? { get } var peerId: EnginePeer.Id? { get }
var callId: Int64? { get } var callId: Int64? { get }
var currentReference: InternalGroupCallReference? { get }
var hasVideo: Bool { get } var hasVideo: Bool { get }
var hasScreencast: Bool { get } var hasScreencast: Bool { get }

View File

@ -138,16 +138,7 @@ class CallListCallItem: ListViewItem {
func selected(listView: ListView) { func selected(listView: ListView) {
listView.clearHighlightAnimated(true) listView.clearHighlightAnimated(true)
var isVideo = false self.interaction.call(self.topMessage)
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)
} }
static func mergeType(item: CallListCallItem, previousItem: ListViewItem?, nextItem: ListViewItem?) -> (first: Bool, last: Bool, firstWithHeader: Bool) { 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 { guard let item = self?.layoutParams?.0 else {
return false return false
} }
var isVideo = false item.interaction.call(item.topMessage)
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)
return true return true
} }
} }
@ -390,6 +373,9 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
var hadDuration = false var hadDuration = false
var callDuration: Int32? var callDuration: Int32?
var isConference = false
var conferenceIsDeclined = false
for message in item.messages { for message in item.messages {
inner: for media in message.media { inner: for media in message.media {
if let action = media as? TelegramMediaAction { if let action = media as? TelegramMediaAction {
@ -411,6 +397,36 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
} else { } else {
callDuration = nil 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 break inner
} }
@ -441,7 +457,18 @@ class CallListCallItemNode: ItemListRevealOptionsItemNode {
titleAttributedString = NSAttributedString(string: channel.title, font: titleFont, textColor: titleColor) 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) 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 statusAccessibilityString = isVideo ? item.presentationData.strings.Call_VoiceOver_VideoCallMissed : item.presentationData.strings.Call_VoiceOver_VoiceCallMissed
} else if hasIncoming && hasOutgoing { } else if hasIncoming && hasOutgoing {

View File

@ -92,6 +92,7 @@ public final class CallListController: TelegramBaseController {
private let createActionDisposable = MetaDisposable() private let createActionDisposable = MetaDisposable()
private let clearDisposable = MetaDisposable() private let clearDisposable = MetaDisposable()
private var createConferenceCallDisposable: Disposable?
public init(context: AccountContext, mode: CallListControllerMode) { public init(context: AccountContext, mode: CallListControllerMode) {
self.context = context self.context = context
@ -163,6 +164,7 @@ public final class CallListController: TelegramBaseController {
self.presentationDataDisposable?.dispose() self.presentationDataDisposable?.dispose()
self.peerViewDisposable.dispose() self.peerViewDisposable.dispose()
self.clearDisposable.dispose() self.clearDisposable.dispose()
self.createConferenceCallDisposable?.dispose()
} }
private func updateThemeAndStrings() { private func updateThemeAndStrings() {
@ -210,11 +212,16 @@ public final class CallListController: TelegramBaseController {
guard !self.presentAccountFrozenInfoIfNeeded() else { guard !self.presentAccountFrozenInfoIfNeeded() else {
return return
} }
let _ = (self.context.engine.calls.createConferenceCall() if self.createConferenceCallDisposable != nil {
|> deliverOnMainQueue).startStandalone(next: { [weak self] call in return
}
self.createConferenceCallDisposable = (self.context.engine.calls.createConferenceCall()
|> deliverOnMainQueue).startStrict(next: { [weak self] call in
guard let self else { guard let self else {
return return
} }
self.createConferenceCallDisposable?.dispose()
self.createConferenceCallDisposable = nil
let openCall: () -> Void = { [weak self] in let openCall: () -> Void = { [weak self] in
guard let self else { 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 let controller = InviteLinkInviteController(
guard let self else { context: self.context,
return updatedPresentationData: nil,
} mode: .groupCall(InviteLinkInviteController.Mode.GroupCall(callId: call.callInfo.id, accessHash: call.callInfo.accessHash, isRecentlyCreated: true, canRevoke: true)),
if let result { 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),
switch result { parentNavigationController: self.navigationController as? NavigationController,
case .linkCopied: completed: { [weak self] result in
//TODO:localize guard let self else {
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 } return
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 { if let result {
openCall() switch result {
} case .linkCopied:
return false //TODO:localize
}), in: .window(.root)) let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
case .openCall: 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
openCall() if case .undo = action {
openCall()
}
return false
}), in: .window(.root))
case .openCall:
openCall()
}
} }
} }
}) )
self.present(controller, in: .window(.root), with: nil) self.present(controller, in: .window(.root), with: nil)
}) })
} }
override public func loadDisplayNode() { override public func loadDisplayNode() {
self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] peerId, isVideo in self.displayNode = CallListControllerNode(controller: self, context: self.context, mode: self.mode, presentationData: self.presentationData, call: { [weak self] message in
if let strongSelf = self { guard let self else {
strongSelf.call(peerId, isVideo: isVideo) 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 }, joinGroupCall: { [weak self] peerId, activeCall in
if let self { 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) { override public func tabBarItemContextAction(sourceNode: ContextExtractedContentContainingNode, gesture: ContextGesture) {
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in items.append(.action(ContextMenuActionItem(text: self.presentationData.strings.Calls_StartNewCall, icon: { theme in

View File

@ -62,14 +62,14 @@ private extension EngineCallList.Item {
final class CallListNodeInteraction { final class CallListNodeInteraction {
let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void let setMessageIdWithRevealedOptions: (EngineMessage.Id?, EngineMessage.Id?) -> Void
let call: (EnginePeer.Id, Bool) -> Void let call: (EngineMessage) -> Void
let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
let delete: ([EngineMessage.Id]) -> Void let delete: ([EngineMessage.Id]) -> Void
let updateShowCallsTab: (Bool) -> Void let updateShowCallsTab: (Bool) -> Void
let openGroupCall: (EnginePeer.Id) -> Void let openGroupCall: (EnginePeer.Id) -> Void
let createGroupCall: () -> 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.setMessageIdWithRevealedOptions = setMessageIdWithRevealedOptions
self.call = call self.call = call
self.openInfo = openInfo self.openInfo = openInfo
@ -222,7 +222,7 @@ final class CallListControllerNode: ASDisplayNode {
private let emptyButtonIconNode: ASImageNode private let emptyButtonIconNode: ASImageNode
private let emptyButtonTextNode: ImmediateTextNode 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 joinGroupCall: (EnginePeer.Id, EngineGroupCallDescription) -> Void
private let createGroupCall: () -> Void private let createGroupCall: () -> Void
private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void private let openInfo: (EnginePeer.Id, [EngineMessage]) -> Void
@ -234,7 +234,7 @@ final class CallListControllerNode: ASDisplayNode {
private var previousContentOffset: ListViewVisibleContentOffset? 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.controller = controller
self.context = context self.context = context
self.mode = mode self.mode = mode
@ -333,8 +333,8 @@ final class CallListControllerNode: ASDisplayNode {
} }
} }
} }
}, call: { [weak self] peerId, isVideo in }, call: { [weak self] message in
self?.call(peerId, isVideo) self?.call(message)
}, openInfo: { [weak self] peerId, messages in }, openInfo: { [weak self] peerId, messages in
self?.openInfo(peerId, messages) self?.openInfo(peerId, messages)
}, delete: { [weak self] messageIds in }, delete: { [weak self] messageIds in
@ -519,10 +519,7 @@ final class CallListControllerNode: ASDisplayNode {
let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App()) let canCreateGroupCall = context.engine.data.subscribe(TelegramEngine.EngineData.Item.Configuration.App())
|> map { configuration -> Bool in |> map { configuration -> Bool in
var isConferencePossible = false var isConferencePossible = true
if context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
isConferencePossible = true
}
if let data = configuration.data, let value = data["ios_enable_conference"] as? Double { if let data = configuration.data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0 isConferencePossible = value != 0.0
} }

View File

@ -100,7 +100,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
case enableReactionOverrides(Bool) case enableReactionOverrides(Bool)
case compressedEmojiCache(Bool) case compressedEmojiCache(Bool)
case storiesJpegExperiment(Bool) case storiesJpegExperiment(Bool)
case conferenceDebug(Bool)
case checkSerializedData(Bool) case checkSerializedData(Bool)
case enableQuickReactionSwitch(Bool) case enableQuickReactionSwitch(Bool)
case disableReloginTokens(Bool) case disableReloginTokens(Bool)
@ -134,7 +133,7 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return DebugControllerSection.web.rawValue return DebugControllerSection.web.rawValue
case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure: case .keepChatNavigationStack, .skipReadHistory, .dustEffect, .crashOnSlowQueries, .crashOnMemoryPressure:
return DebugControllerSection.experiments.rawValue 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 return DebugControllerSection.experiments.rawValue
case .logTranslationRecognition, .resetTranslationStates: case .logTranslationRecognition, .resetTranslationStates:
return DebugControllerSection.translation.rawValue return DebugControllerSection.translation.rawValue
@ -243,8 +242,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
return 47 return 47
case .disableReloginTokens: case .disableReloginTokens:
return 48 return 48
case .conferenceDebug:
return 49
case .checkSerializedData: case .checkSerializedData:
return 50 return 50
case .enableQuickReactionSwitch: case .enableQuickReactionSwitch:
@ -1311,16 +1308,6 @@ private enum DebugControllerEntry: ItemListNodeEntry {
}) })
}).start() }).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): case let .checkSerializedData(value):
return ItemListSwitchItem(presentationData: presentationData, title: "Check Serialized Data", value: value, sectionId: self.section, style: .blocks, updated: { value in 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 let _ = arguments.sharedContext.accountManager.transaction ({ transaction in
@ -1552,7 +1539,6 @@ private func debugControllerEntries(sharedContext: SharedAccountContext, present
entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment)) entries.append(.storiesJpegExperiment(experimentalSettings.storiesJpegExperiment))
entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens)) entries.append(.disableReloginTokens(experimentalSettings.disableReloginTokens))
entries.append(.conferenceDebug(experimentalSettings.conferenceDebug))
entries.append(.checkSerializedData(experimentalSettings.checkSerializedData)) entries.append(.checkSerializedData(experimentalSettings.checkSerializedData))
entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction)) entries.append(.enableQuickReactionSwitch(!experimentalSettings.disableQuickReaction))
entries.append(.liveStreamV2(experimentalSettings.liveStreamV2)) entries.append(.liveStreamV2(experimentalSettings.liveStreamV2))

View File

@ -154,14 +154,32 @@ private func preparedTransition(from fromEntries: [InviteLinkInviteEntry], to to
return InviteLinkInviteTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading) 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 { public final class InviteLinkInviteController: ViewController {
private var controllerNode: Node { private var controllerNode: Node {
return self.displayNode as! Node return self.displayNode as! Node
} }
public enum Mode { 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 groupOrChannel(peerId: EnginePeer.Id)
case groupCall(link: String, isRecentlyCreated: Bool) case groupCall(GroupCall)
} }
public enum CompletionResult { public enum CompletionResult {
@ -173,6 +191,7 @@ public final class InviteLinkInviteController: ViewController {
private let context: AccountContext private let context: AccountContext
private let mode: Mode private let mode: Mode
private let initialInvite: ExportedInvitation?
private weak var parentNavigationController: NavigationController? private weak var parentNavigationController: NavigationController?
private var presentationData: PresentationData private var presentationData: PresentationData
@ -180,9 +199,10 @@ public final class InviteLinkInviteController: ViewController {
fileprivate let completed: ((CompletionResult?) -> Void)? fileprivate let completed: ((CompletionResult?) -> Void)?
public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) { public init(context: AccountContext, updatedPresentationData: (initial: PresentationData, signal: Signal<PresentationData, NoError>)? = nil, mode: Mode, initialInvite: ExportedInvitation?, parentNavigationController: NavigationController?, completed: ((CompletionResult?) -> Void)? = nil) {
self.context = context self.context = context
self.mode = mode self.mode = mode
self.initialInvite = initialInvite
self.parentNavigationController = parentNavigationController self.parentNavigationController = parentNavigationController
self.completed = completed self.completed = completed
@ -215,7 +235,7 @@ public final class InviteLinkInviteController: ViewController {
} }
override public func loadDisplayNode() { 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 private var didAppearOnce: Bool = false
@ -296,7 +316,7 @@ public final class InviteLinkInviteController: ViewController {
private var revokeDisposable = MetaDisposable() 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.context = context
self.mode = mode self.mode = mode
@ -319,7 +339,7 @@ public final class InviteLinkInviteController: ViewController {
self.headerNode.clipsToBounds = false self.headerNode.clipsToBounds = false
self.headerBackgroundNode = ASDisplayNode() 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.cornerRadius = 16.0
self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner] self.headerBackgroundNode.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
@ -338,7 +358,7 @@ public final class InviteLinkInviteController: ViewController {
self.historyBackgroundContentNode = ASDisplayNode() self.historyBackgroundContentNode = ASDisplayNode()
self.historyBackgroundContentNode.isLayerBacked = true 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) self.historyBackgroundNode.addSubnode(self.historyBackgroundContentNode)
@ -354,7 +374,7 @@ public final class InviteLinkInviteController: ViewController {
self.backgroundColor = nil self.backgroundColor = nil
self.isOpaque = false self.isOpaque = false
let mainInvitePromise = ValuePromise<ExportedInvitation?>(nil) let mainInvitePromise = ValuePromise<ExportedInvitation?>(initialInvite)
self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in self.interaction = InviteLinkInviteInteraction(context: context, mainLinkContextAction: { [weak self] invite, node, gesture in
guard let self else { guard let self else {
@ -363,7 +383,6 @@ public final class InviteLinkInviteController: ViewController {
guard let node = node as? ContextReferenceContentNode else { guard let node = node as? ContextReferenceContentNode else {
return return
} }
let presentationData = context.sharedContext.currentPresentationData.with { $0 }
var items: [ContextMenuItem] = [] var items: [ContextMenuItem] = []
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextCopy, icon: { theme in 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
items.append(.action(ContextMenuActionItem(text: presentationData.strings.InviteLink_ContextGetQRCode, icon: { theme in return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor)
return generateTintedImage(image: UIImage(bundleImageName: "Settings/QrIcon"), color: theme.contextMenu.primaryColor) }, action: { [weak self] _, f in
}, action: { [weak self] _, f in f(.dismissWithoutContent)
f(.dismissWithoutContent)
guard let self else {
guard let self else { return
return }
}
if let invite {
if let invite = invite { if case let .groupOrChannel(peerId) = self.mode {
let _ = (context.account.postbox.loadedPeerWithId(peerId) let _ = (context.account.postbox.loadedPeerWithId(peerId)
|> deliverOnMainQueue).start(next: { [weak self] peer in |> deliverOnMainQueue).start(next: { [weak self] peer in
guard let strongSelf = self else { guard let strongSelf = self else {
@ -404,12 +423,17 @@ public final class InviteLinkInviteController: ViewController {
isGroup = true isGroup = true
} }
let updatedPresentationData = (strongSelf.presentationData, strongSelf.presentationDataPromise.get()) 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)) 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 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) return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Delete"), color: theme.actionSheet.destructiveActionTextColor)
}, action: { [ weak self] _, f in }, action: { [ weak self] _, f in
@ -454,7 +478,45 @@ public final class InviteLinkInviteController: ViewController {
self?.controller?.present(controller, in: .window(.root)) 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) 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) strongSelf.enqueueTransition(transition)
} }
}) })
case let .groupCall(link, isRecentlyCreated): case let .groupCall(groupCall):
//TODO:release // A workaround to skip the first run of the event cycle
let tempInfo: Signal<Void, NoError> = .single(Void()) |> delay(0.0, queue: .mainQueue()) let delayOfZero = Signal<Void, NoError>.single(()) |> delay(0.0, queue: .mainQueue())
self.disposable = (combineLatest(queue: .mainQueue(), self.disposable = (combineLatest(queue: .mainQueue(),
self.presentationDataPromise.get(), 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 { guard let self else {
return 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." let helpText: String = "Anyone on Telegram can join your call by following the link below."
entries.append(.header(title: "Call Link", text: helpText)) 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) let previousEntries = previousEntries.swap(entries)
@ -675,8 +738,8 @@ public final class InviteLinkInviteController: ViewController {
self.presentationData = presentationData self.presentationData = presentationData
self.presentationDataPromise.set(.single(presentationData)) self.presentationDataPromise.set(.single(presentationData))
self.historyBackgroundContentNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor self.historyBackgroundContentNode.backgroundColor = getBackgroundColor(theme: self.presentationData.theme)
self.headerBackgroundNode.backgroundColor = self.presentationData.theme.list.plainBackgroundColor 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.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))! self.doneButtonIconNode.image = generateCloseButtonImage(backgroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.05), foregroundColor: self.presentationData.theme.list.itemPrimaryTextColor.withMultipliedAlpha(0.4))!

View File

@ -530,7 +530,7 @@ public func inviteLinkListController(context: AccountContext, updatedPresentatio
} else { } else {
isGroup = true 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 isGroup = true
} }
Queue.mainQueue().after(0.2) { 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)
} }
}) })
}))) })))

View File

@ -755,7 +755,7 @@ public final class InviteLinkViewController: ViewController {
isGroup = true isGroup = true
} }
let updatedPresentationData = (strongSelf.presentationData, parentController.presentationDataPromise.get()) 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))
}) })
}))) })))
} }

View File

@ -655,7 +655,7 @@ public func channelMembersController(context: AccountContext, updatedPresentatio
}, inviteViaLink: { }, inviteViaLink: {
if let controller = getControllerImpl?() { if let controller = getControllerImpl?() {
dismissInputImpl?() 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 }, updateHideMembers: { value in
let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start() let _ = context.engine.peers.updateChannelMembersHidden(peerId: peerId, value: value).start()

View File

@ -1609,7 +1609,7 @@ public func channelVisibilityController(context: AccountContext, updatedPresenta
} else { } else {
isGroup = true 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)
}) })
} }
}) })

View File

@ -35,9 +35,15 @@ private func shareQrCode(context: AccountContext, link: String, ecl: String, vie
} }
public final class QrCodeScreen: ViewController { public final class QrCodeScreen: ViewController {
public enum SubjectType {
case group
case channel
case groupCall
}
public enum Subject { public enum Subject {
case peer(peer: EnginePeer) case peer(peer: EnginePeer)
case invite(invite: ExportedInvitation, isGroup: Bool) case invite(invite: ExportedInvitation, type: SubjectType)
case chatFolder(slug: String) case chatFolder(slug: String)
var link: String { var link: String {
@ -239,9 +245,17 @@ public final class QrCodeScreen: ViewController {
let title: String let title: String
let text: String let text: String
switch subject { switch subject {
case let .invite(_, isGroup): case let .invite(_, type):
title = self.presentationData.strings.InviteLink_QRCode_Title 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: case .chatFolder:
title = self.presentationData.strings.InviteLink_QRCodeFolder_Title title = self.presentationData.strings.InviteLink_QRCodeFolder_Title
text = self.presentationData.strings.InviteLink_QRCodeFolder_Text text = self.presentationData.strings.InviteLink_QRCodeFolder_Text

View File

@ -167,10 +167,7 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
self.conferenceAddParticipant?() self.conferenceAddParticipant?()
} }
var isConferencePossible = false var isConferencePossible = true
if self.call.context.sharedContext.immediateExperimentalUISettings.conferenceDebug {
isConferencePossible = true
}
if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double { if let data = self.call.context.currentAppConfiguration.with({ $0 }).data, let value = data["ios_enable_conference"] as? Double {
isConferencePossible = value != 0.0 isConferencePossible = value != 0.0
} }

View File

@ -161,7 +161,8 @@ public final class AccountGroupCallContextImpl: AccountGroupCallContext {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled, isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit, unmutedVideoLimit: state.unmutedVideoLimit,
isStream: state.isStream isStream: state.isStream,
isCreator: state.isCreator
), ),
topParticipants: topParticipants, topParticipants: topParticipants,
participantCount: state.totalCount, participantCount: state.totalCount,
@ -888,6 +889,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void) private let getDeviceAccessData: () -> (presentationData: PresentationData, present: (ViewController, Any?) -> Void, openSettings: () -> Void)
private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)? private(set) var initialCall: (description: EngineGroupCallDescription, reference: InternalGroupCallReference)?
public var currentReference: InternalGroupCallReference?
public let internalId: CallSessionInternalId public let internalId: CallSessionInternalId
public let peerId: EnginePeer.Id? public let peerId: EnginePeer.Id?
private let isChannel: Bool private let isChannel: Bool
@ -1208,6 +1210,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
self.getDeviceAccessData = getDeviceAccessData self.getDeviceAccessData = getDeviceAccessData
self.initialCall = initialCall self.initialCall = initialCall
self.currentReference = initialCall?.reference
self.callId = initialCall?.description.id self.callId = initialCall?.description.id
self.internalId = internalId self.internalId = internalId
@ -1963,7 +1966,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: callInfo.defaultParticipantsAreMuted ?? state.defaultParticipantsAreMuted,
isVideoEnabled: callInfo.isVideoEnabled, isVideoEnabled: callInfo.isVideoEnabled,
unmutedVideoLimit: callInfo.unmutedVideoLimit, unmutedVideoLimit: callInfo.unmutedVideoLimit,
isStream: callInfo.isStream isStream: callInfo.isStream,
isCreator: callInfo.isCreator
)), audioSessionControl: self.audioSessionControl) )), audioSessionControl: self.audioSessionControl)
} else { } else {
self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo( self.summaryInfoState.set(.single(SummaryInfoState(info: GroupCallInfo(
@ -1979,7 +1983,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled, isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit, unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream isStream: callInfo.isStream,
isCreator: callInfo.isCreator
)))) ))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState( self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -2301,6 +2306,9 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
guard let self else { guard let self else {
return return
} }
self.currentReference = .id(id: joinCallResult.callInfo.id, accessHash: joinCallResult.callInfo.accessHash)
let clientParams = joinCallResult.jsonParams let clientParams = joinCallResult.jsonParams
if let data = clientParams.data(using: .utf8), let dict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] { 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] { if let video = dict["video"] as? [String: Any] {
@ -2910,7 +2918,8 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
defaultParticipantsAreMuted: state.defaultParticipantsAreMuted, defaultParticipantsAreMuted: state.defaultParticipantsAreMuted,
isVideoEnabled: state.isVideoEnabled, isVideoEnabled: state.isVideoEnabled,
unmutedVideoLimit: state.unmutedVideoLimit, unmutedVideoLimit: state.unmutedVideoLimit,
isStream: callInfo.isStream isStream: callInfo.isStream,
isCreator: callInfo.isCreator
)))) ))))
self.summaryParticipantsState.set(.single(SummaryParticipantsState( self.summaryParticipantsState.set(.single(SummaryParticipantsState(
@ -2988,6 +2997,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
result.append(OngoingGroupCallContext.MediaChannelDescription( result.append(OngoingGroupCallContext.MediaChannelDescription(
kind: .audio, kind: .audio,
peerId: participant.peer.id.id._internalGetInt64Value(),
audioSsrc: audioSsrc, audioSsrc: audioSsrc,
videoDescription: nil videoDescription: nil
)) ))
@ -2999,6 +3009,7 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
result.append(OngoingGroupCallContext.MediaChannelDescription( result.append(OngoingGroupCallContext.MediaChannelDescription(
kind: .audio, kind: .audio,
peerId: participant.peer.id.id._internalGetInt64Value(),
audioSsrc: screencastSsrc, audioSsrc: screencastSsrc,
videoDescription: nil videoDescription: nil
)) ))

View File

@ -25,6 +25,7 @@ import LegacyComponents
import TooltipUI import TooltipUI
import BlurredBackgroundComponent import BlurredBackgroundComponent
import CallsEmoji import CallsEmoji
import InviteLinksUI
extension VideoChatCall { extension VideoChatCall {
var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> { var myAudioLevelAndSpeaking: Signal<(Float, Bool), NoError> {
@ -652,6 +653,51 @@ final class VideoChatScreenComponent: Component {
guard case let .group(groupCall) = self.currentCall else { guard case let .group(groupCall) = self.currentCall else {
return 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 let formatSendTitle: (String) -> String = { string in
var string = string var string = string
@ -705,7 +751,7 @@ final class VideoChatScreenComponent: Component {
peerIds.map(TelegramEngine.EngineData.Item.Peer.Peer.init) 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 { guard let self, let environment = self.environment, case let .group(groupCall) = self.currentCall else {
return return
} }

View File

@ -16,31 +16,6 @@ extension VideoChatScreenComponent.View {
return 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 { if groupCall.isConference {
var disablePeerIds: [EnginePeer.Id] = [] var disablePeerIds: [EnginePeer.Id] = []
disablePeerIds.append(groupCall.accountContext.account.peerId) disablePeerIds.append(groupCall.accountContext.account.peerId)

View File

@ -93,13 +93,18 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
tags.insert(.webPage) tags.insert(.webPage)
} else if let action = attachment as? TelegramMediaAction { } else if let action = attachment as? TelegramMediaAction {
switch action.action { switch action.action {
case let .phoneCall(_, discardReason, _, _): case let .phoneCall(_, discardReason, _, _):
globalTags.insert(.Calls) globalTags.insert(.Calls)
if incoming, let discardReason = discardReason, case .missed = discardReason { if incoming, let discardReason = discardReason, case .missed = discardReason {
globalTags.insert(.MissedCalls) globalTags.insert(.MissedCalls)
} }
default: case let .conferenceCall(conferenceCall):
break globalTags.insert(.Calls)
if incoming, conferenceCall.flags.contains(.isMissed) {
globalTags.insert(.MissedCalls)
}
default:
break
} }
} else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil { } else if let location = attachment as? TelegramMediaMap, location.liveBroadcastingTimeout != nil {
tags.insert(.liveLocation) tags.insert(.liveLocation)
@ -118,9 +123,6 @@ public func tagsForStoreMessage(incoming: Bool, attributes: [MessageAttribute],
} }
} }
if !incoming {
assert(true)
}
return (tags, globalTags) return (tags, globalTags)
} }

View File

@ -96,6 +96,7 @@ public struct GroupCallInfo: Equatable {
public var isVideoEnabled: Bool public var isVideoEnabled: Bool
public var unmutedVideoLimit: Int public var unmutedVideoLimit: Int
public var isStream: Bool public var isStream: Bool
public var isCreator: Bool
public init( public init(
id: Int64, id: Int64,
@ -110,7 +111,8 @@ public struct GroupCallInfo: Equatable {
defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?, defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted?,
isVideoEnabled: Bool, isVideoEnabled: Bool,
unmutedVideoLimit: Int, unmutedVideoLimit: Int,
isStream: Bool isStream: Bool,
isCreator: Bool
) { ) {
self.id = id self.id = id
self.accessHash = accessHash self.accessHash = accessHash
@ -125,6 +127,7 @@ public struct GroupCallInfo: Equatable {
self.isVideoEnabled = isVideoEnabled self.isVideoEnabled = isVideoEnabled
self.unmutedVideoLimit = unmutedVideoLimit self.unmutedVideoLimit = unmutedVideoLimit
self.isStream = isStream 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), defaultParticipantsAreMuted: GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: (flags & (1 << 1)) != 0, canChange: (flags & (1 << 2)) != 0),
isVideoEnabled: (flags & (1 << 9)) != 0, isVideoEnabled: (flags & (1 << 9)) != 0,
unmutedVideoLimit: Int(unmutedVideoLimit), unmutedVideoLimit: Int(unmutedVideoLimit),
isStream: (flags & (1 << 12)) != 0 isStream: (flags & (1 << 12)) != 0,
isCreator: (flags & (1 << 15)) != 0
) )
case .groupCallDiscarded: case .groupCallDiscarded:
return nil 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<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> { func _internal_getGroupCallParticipants(account: Account, reference: InternalGroupCallReference, offset: String, ssrcs: [UInt32], limit: Int32, sortAscending: Bool?) -> Signal<GroupCallParticipantsContext.State, GetGroupCallParticipantsError> {
let accountPeerId = account.peerId 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) sortAscendingValue = _internal_getCurrentGroupCall(account: account, reference: reference)
|> mapError { _ -> GetGroupCallParticipantsError in |> mapError { _ -> GetGroupCallParticipantsError in
return .generic 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 { guard let result = result else {
return .fail(.generic) 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( return combineLatest(
@ -481,7 +485,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
let version: Int32 let version: Int32
let nextParticipantsFetchOffset: String? let nextParticipantsFetchOffset: String?
let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream) = sortAscendingAndScheduleTimestamp let (sortAscendingValue, scheduleTimestamp, subscribedToScheduled, defaultParticipantsAreMuted, isVideoEnabled, unmutedVideoLimit, isStream, isCreator) = sortAscendingAndScheduleTimestamp
switch result { switch result {
case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion): case let .groupParticipants(count, participants, nextOffset, chats, users, apiVersion):
@ -506,7 +510,7 @@ func _internal_getGroupCallParticipants(account: Account, reference: InternalGro
participants: parsedParticipants, participants: parsedParticipants,
nextParticipantsFetchOffset: nextParticipantsFetchOffset, nextParticipantsFetchOffset: nextParticipantsFetchOffset,
adminIds: Set(), adminIds: Set(),
isCreator: false, isCreator: isCreator,
defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false), defaultParticipantsAreMuted: defaultParticipantsAreMuted ?? GroupCallParticipantsContext.State.DefaultParticipantsAreMuted(isMuted: false, canChange: false),
sortAscending: sortAscendingValue, sortAscending: sortAscendingValue,
recordingStartTimestamp: nil, 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<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
return account.network.request(Api.functions.phone.toggleGroupCallSettings(flags: 1 << 1, call: reference.apiInputGroupCall, joinMuted: .boolFalse))
|> mapError { _ -> RevokeConferenceInviteLinkError in
return .generic
}
|> mapToSignal { result -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> in
account.stateManager.addUpdates(result)
return _internal_groupCallInviteLinks(account: account, reference: reference, isConference: true)
|> castError(RevokeConferenceInviteLinkError.self)
|> mapToSignal { result -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> in
guard let result = result else {
return .fail(.generic)
}
return .single(result)
}
}
}
public enum ConfirmAddConferenceParticipantError { public enum ConfirmAddConferenceParticipantError {
case generic case generic
} }

View File

@ -96,6 +96,10 @@ public extension TelegramEngine {
public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> { public func createConferenceCall() -> Signal<EngineCreatedGroupCall, CreateConferenceCallError> {
return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId) return _internal_createConferenceCall(postbox: self.account.postbox, network: self.account.network, accountPeerId: self.account.peerId)
} }
public func revokeConferenceInviteLink(reference: InternalGroupCallReference, link: String) -> Signal<GroupCallInviteLinks, RevokeConferenceInviteLinkError> {
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> { 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) return _internal_pollConferenceCallBlockchain(network: self.account.network, reference: reference, subChainId: subChainId, offset: offset, limit: limit)

View File

@ -14124,7 +14124,7 @@ public func presentAddMembersImpl(context: AccountContext, updatedPresentationDa
createInviteLinkImpl = { [weak contactsController] in createInviteLinkImpl = { [weak contactsController] in
contactsController?.view.window?.endEditing(true) 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) parentController?.push(contactsController)

View File

@ -402,6 +402,15 @@ func openResolvedUrlImpl(
members: resolvedCallLink.members, members: resolvedCallLink.members,
totalMemberCount: resolvedCallLink.totalMemberCount 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): case let .localization(identifier):
dismissInput() dismissInput()
@ -788,6 +797,7 @@ func openResolvedUrlImpl(
} }
if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) { if let currentState = starsContext.currentState, currentState.balance >= StarsAmount(value: amount, nanos: 0) {
let presentationData = context.sharedContext.currentPresentationData.with { $0 } let presentationData = context.sharedContext.currentPresentationData.with { $0 }
//TODO:localize
let controller = UndoOverlayController( let controller = UndoOverlayController(
presentationData: presentationData, presentationData: presentationData,
content: .universal( content: .universal(

View File

@ -270,11 +270,13 @@ public final class OngoingGroupCallContext {
} }
public var kind: Kind public var kind: Kind
public var peerId: Int64
public var audioSsrc: UInt32 public var audioSsrc: UInt32
public var videoDescription: String? 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.kind = kind
self.peerId = peerId
self.audioSsrc = audioSsrc self.audioSsrc = audioSsrc
self.videoDescription = videoDescription self.videoDescription = videoDescription
} }
@ -575,6 +577,7 @@ public final class OngoingGroupCallContext {
} }
return OngoingGroupCallMediaChannelDescription( return OngoingGroupCallMediaChannelDescription(
type: mappedType, type: mappedType,
peerId: channel.peerId,
audioSsrc: channel.audioSsrc, audioSsrc: channel.audioSsrc,
videoDescription: channel.videoDescription videoDescription: channel.videoDescription
) )
@ -688,6 +691,7 @@ public final class OngoingGroupCallContext {
} }
return OngoingGroupCallMediaChannelDescription( return OngoingGroupCallMediaChannelDescription(
type: mappedType, type: mappedType,
peerId: channel.peerId,
audioSsrc: channel.audioSsrc, audioSsrc: channel.audioSsrc,
videoDescription: channel.videoDescription videoDescription: channel.videoDescription
) )

View File

@ -332,10 +332,12 @@ typedef NS_ENUM(int32_t, OngoingGroupCallMediaChannelType) {
@interface OngoingGroupCallMediaChannelDescription : NSObject @interface OngoingGroupCallMediaChannelDescription : NSObject
@property (nonatomic, readonly) OngoingGroupCallMediaChannelType type; @property (nonatomic, readonly) OngoingGroupCallMediaChannelType type;
@property (nonatomic, readonly) uint64_t peerId;
@property (nonatomic, readonly) uint32_t audioSsrc; @property (nonatomic, readonly) uint32_t audioSsrc;
@property (nonatomic, strong, readonly) NSString * _Nullable videoDescription; @property (nonatomic, strong, readonly) NSString * _Nullable videoDescription;
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type - (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
peerId:(int64_t)peerId
audioSsrc:(uint32_t)audioSsrc audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription; videoDescription:(NSString * _Nullable)videoDescription;

View File

@ -3029,11 +3029,13 @@ encryptDecrypt:(NSData * _Nullable (^ _Nullable)(NSData * _Nonnull, bool))encryp
@implementation OngoingGroupCallMediaChannelDescription @implementation OngoingGroupCallMediaChannelDescription
- (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type - (instancetype _Nonnull)initWithType:(OngoingGroupCallMediaChannelType)type
audioSsrc:(uint32_t)audioSsrc peerId:(int64_t)peerId
videoDescription:(NSString * _Nullable)videoDescription { audioSsrc:(uint32_t)audioSsrc
videoDescription:(NSString * _Nullable)videoDescription {
self = [super init]; self = [super init];
if (self != nil) { if (self != nil) {
_type = type; _type = type;
_peerId = peerId;
_audioSsrc = audioSsrc; _audioSsrc = audioSsrc;
_videoDescription = videoDescription; _videoDescription = videoDescription;
} }