mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 13:35:19 +00:00
Conference improvements
This commit is contained in:
parent
0d4b8ee2c0
commit
4bbea1f0d0
@ -14188,3 +14188,8 @@ Sorry for the inconvenience.";
|
||||
"SendInviteLink.TextCallsRestrictedMultipleUsers_1" = "{user_list}, and **%d** more person do not accept calls.";
|
||||
"SendInviteLink.TextCallsRestrictedMultipleUsers_any" = "{user_list}, and **%d** more people do not accept calls.";
|
||||
"SendInviteLink.TextCallsRestrictedSendInviteLink" = "You can try to send an invite link instead.";
|
||||
|
||||
"Call.VoiceChatInProgressConferenceMessage" = "Leave voice chat in %1$@ and start a conference?";
|
||||
"Call.LiveStreamInProgressConferenceMessage" = "Leave live stream in %1$@ and start a conference?";
|
||||
|
||||
"VoiceChat.RemoveConferencePeerConfirmation" = "Are you sure you want to remove %@ from this call?";
|
||||
|
@ -964,14 +964,16 @@ public enum JoinSubjectScreenMode {
|
||||
public let inviter: EnginePeer?
|
||||
public let members: [EnginePeer]
|
||||
public let totalMemberCount: Int
|
||||
public let info: JoinCallLinkInformation
|
||||
|
||||
public init(id: Int64, accessHash: Int64, slug: String, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
|
||||
public init(id: Int64, accessHash: Int64, slug: String, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int, info: JoinCallLinkInformation) {
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.slug = slug
|
||||
self.inviter = inviter
|
||||
self.members = members
|
||||
self.totalMemberCount = totalMemberCount
|
||||
self.info = info
|
||||
}
|
||||
}
|
||||
|
||||
@ -1325,6 +1327,7 @@ public protocol AccountContext: AnyObject {
|
||||
|
||||
func scheduleGroupCall(peerId: PeerId, parentController: ViewController)
|
||||
func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription)
|
||||
func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool)
|
||||
func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void)
|
||||
}
|
||||
|
||||
|
@ -579,6 +579,7 @@ public protocol PresentationCallManager: AnyObject {
|
||||
initialCall: EngineGroupCallDescription,
|
||||
reference: InternalGroupCallReference,
|
||||
beginWithVideo: Bool,
|
||||
invitePeerIds: [EnginePeer.Id]
|
||||
)
|
||||
invitePeerIds: [EnginePeer.Id],
|
||||
endCurrentIfAny: Bool
|
||||
) -> JoinGroupCallManagerResult
|
||||
}
|
||||
|
@ -210,7 +210,7 @@ public final class CallListController: TelegramBaseController {
|
||||
}
|
||||
|
||||
private func createGroupCall(peerIds: [EnginePeer.Id], isVideo: Bool, completion: (() -> Void)? = nil) {
|
||||
self.view.endEditing(true)
|
||||
self.view.window?.endEditing(true)
|
||||
|
||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||
return
|
||||
@ -264,7 +264,7 @@ public final class CallListController: TelegramBaseController {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
self.context.sharedContext.callManager?.joinConferenceCall(
|
||||
let _ = self.context.sharedContext.callManager?.joinConferenceCall(
|
||||
accountContext: self.context,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.callInfo.id,
|
||||
@ -276,7 +276,8 @@ public final class CallListController: TelegramBaseController {
|
||||
),
|
||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: peerIds
|
||||
invitePeerIds: peerIds,
|
||||
endCurrentIfAny: true
|
||||
)
|
||||
completion?()
|
||||
}
|
||||
@ -712,20 +713,7 @@ public final class CallListController: TelegramBaseController {
|
||||
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),
|
||||
invitePeerIds: []
|
||||
)
|
||||
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo))
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
|
@ -799,7 +799,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
||||
return entries
|
||||
}
|
||||
|
||||
private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool) -> ContactsListNodeTransition {
|
||||
private func preparedContactListNodeTransition(context: AccountContext, presentationData: PresentationData, from fromEntries: [ContactListNodeEntry], to toEntries: [ContactListNodeEntry], interaction: ContactListNodeInteraction, firstTime: Bool, isEmpty: Bool, hasOptions: Bool, generateIndexSections: Bool, animation: ContactListAnimation, isSearch: Bool) -> ContactsListNodeTransition {
|
||||
let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||
|
||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
||||
@ -848,7 +848,7 @@ private func preparedContactListNodeTransition(context: AccountContext, presenta
|
||||
scrollToItem = ListViewScrollToItem(index: 0, position: .top(-50.0), animated: false, curve: .Default(duration: 0.0), directionHint: .Up)
|
||||
}
|
||||
|
||||
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, indexSections: indexSections, firstTime: firstTime, isEmpty: isEmpty, scrollToItem: scrollToItem, animation: animation)
|
||||
return ContactsListNodeTransition(deletions: deletions, insertions: insertions, updates: updates, indexSections: indexSections, firstTime: firstTime, isEmpty: isEmpty, hasOptions: hasOptions, scrollToItem: scrollToItem, animation: animation)
|
||||
}
|
||||
|
||||
private struct ContactsListNodeTransition {
|
||||
@ -858,6 +858,7 @@ private struct ContactsListNodeTransition {
|
||||
let indexSections: [String]
|
||||
let firstTime: Bool
|
||||
let isEmpty: Bool
|
||||
let hasOptions: Bool
|
||||
let scrollToItem: ListViewScrollToItem?
|
||||
let animation: ContactListAnimation
|
||||
}
|
||||
@ -1184,7 +1185,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
authorizeImpl?()
|
||||
}, openPrivacyPolicy: {
|
||||
openPrivacyPolicyImpl?()
|
||||
})
|
||||
}, filterHitTest: true)
|
||||
self.authorizationNode.isHidden = true
|
||||
|
||||
super.init()
|
||||
@ -1644,7 +1645,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
|
||||
let entries = contactListNodeEntries(accountPeer: nil, peers: peers, presences: localPeersAndStatuses.1, presentation: presentation, selectionState: selectionState, theme: presentationData.theme, strings: presentationData.strings, dateTimeFormat: presentationData.dateTimeFormat, sortOrder: presentationData.nameSortOrder, displayOrder: presentationData.nameDisplayOrder, disabledPeerIds: disabledPeerIds, peerRequiresPremiumForMessaging: peerRequiresPremiumForMessaging, peersWithStories: [:], authorizationStatus: .allowed, warningSuppressed: (true, true), displaySortOptions: false, displayCallIcons: displayCallIcons, storySubscriptions: nil, topPeers: [], topPeersPresentation: .none, isPeerEnabled: isPeerEnabled, interaction: interaction)
|
||||
let previous = previousEntries.swap(entries)
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch))
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: false, hasOptions: false, generateIndexSections: generateSections, animation: .none, isSearch: isSearch))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
@ -1885,7 +1886,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
animation = .none
|
||||
}
|
||||
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, generateIndexSections: generateSections, animation: animation, isSearch: isSearch))
|
||||
return .single(preparedContactListNodeTransition(context: context, presentationData: presentationData, from: previous ?? [], to: entries, interaction: interaction, firstTime: previous == nil, isEmpty: isEmpty, hasOptions: optionsCount != 0, generateIndexSections: generateSections, animation: animation, isSearch: isSearch))
|
||||
}
|
||||
|
||||
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||
@ -1921,7 +1922,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
authorizeImpl?()
|
||||
}, openPrivacyPolicy: {
|
||||
openPrivacyPolicyImpl?()
|
||||
})
|
||||
}, filterHitTest: true)
|
||||
strongSelf.authorizationNode.isHidden = authorizationPreviousHidden
|
||||
strongSelf.addSubnode(strongSelf.authorizationNode)
|
||||
|
||||
@ -2125,7 +2126,7 @@ public final class ContactListNode: ASDisplayNode {
|
||||
}
|
||||
})
|
||||
|
||||
self.listNode.isHidden = self.displayPermissionPlaceholder && transition.isEmpty
|
||||
self.listNode.isHidden = self.displayPermissionPlaceholder && (transition.isEmpty && !transition.hasOptions)
|
||||
self.authorizationNode.isHidden = !transition.isEmpty || !self.displayPermissionPlaceholder
|
||||
}
|
||||
}
|
||||
|
@ -589,6 +589,8 @@ public final class InviteLinkInviteController: ViewController {
|
||||
}
|
||||
return false
|
||||
}), in: .window(.root))
|
||||
|
||||
strongSelf.controller?.dismiss()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -149,7 +149,7 @@ struct SqlitePreparedStatement {
|
||||
}
|
||||
}
|
||||
|
||||
private let dabaseFileNames: [String] = [
|
||||
private let databaseFileNames: [String] = [
|
||||
"db_sqlite",
|
||||
"db_sqlite-shm",
|
||||
"db_sqlite-wal"
|
||||
@ -194,7 +194,6 @@ public final class SqliteValueBox: ValueBox {
|
||||
private var insertOrIgnorePrimaryKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
private var insertOrIgnoreIndexKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
private var moveStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
private var copyStatements: [TablePairKey : SqlitePreparedStatement] = [:]
|
||||
private var fullTextInsertStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
private var fullTextDeleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||
@ -347,7 +346,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
return nil
|
||||
}
|
||||
|
||||
for fileName in dabaseFileNames {
|
||||
for fileName in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||
}
|
||||
database = Database(path, readOnly: false)!
|
||||
@ -367,7 +366,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
}
|
||||
|
||||
assert(false)
|
||||
for fileName in dabaseFileNames {
|
||||
for fileName in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||
}
|
||||
|
||||
@ -400,7 +399,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
if self.isEncrypted(database) {
|
||||
postboxLog("Reencryption failed")
|
||||
|
||||
for fileName in dabaseFileNames {
|
||||
for fileName in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||
}
|
||||
database = Database(path, readOnly: false)!
|
||||
@ -427,7 +426,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
return nil
|
||||
}
|
||||
|
||||
for fileName in dabaseFileNames {
|
||||
for fileName in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||
}
|
||||
database = Database(path, readOnly: false)!
|
||||
@ -1224,7 +1223,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
preconditionFailure(errorText)
|
||||
}
|
||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||
self.insertOrReplacePrimaryKeyStatements[table.table.id] = preparedStatement
|
||||
self.insertOrReplaceIndexKeyStatements[table.table.id] = preparedStatement
|
||||
resultStatement = preparedStatement
|
||||
}
|
||||
}
|
||||
@ -1279,7 +1278,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
preconditionFailure(errorText)
|
||||
}
|
||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||
self.insertOrIgnorePrimaryKeyStatements[table.table.id] = preparedStatement
|
||||
self.insertOrIgnoreIndexKeyStatements[table.table.id] = preparedStatement
|
||||
resultStatement = preparedStatement
|
||||
}
|
||||
}
|
||||
@ -1330,38 +1329,6 @@ public final class SqliteValueBox: ValueBox {
|
||||
return resultStatement
|
||||
}
|
||||
|
||||
private func moveStatement(_ table: ValueBoxTable, from previousKey: ValueBoxKey, to updatedKey: ValueBoxKey) -> SqlitePreparedStatement {
|
||||
precondition(self.queue.isCurrent())
|
||||
checkTableKey(table, previousKey)
|
||||
checkTableKey(table, updatedKey)
|
||||
|
||||
let resultStatement: SqlitePreparedStatement
|
||||
|
||||
if let statement = self.moveStatements[table.id] {
|
||||
resultStatement = statement
|
||||
} else {
|
||||
var statement: OpaquePointer? = nil
|
||||
let status = sqlite3_prepare_v3(self.database.handle, "UPDATE t\(table.id) SET key=? WHERE key=?", -1, SQLITE_PREPARE_PERSISTENT, &statement, nil)
|
||||
precondition(status == SQLITE_OK)
|
||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||
self.moveStatements[table.id] = preparedStatement
|
||||
resultStatement = preparedStatement
|
||||
}
|
||||
|
||||
resultStatement.reset()
|
||||
|
||||
switch table.keyType {
|
||||
case .binary:
|
||||
resultStatement.bind(1, data: previousKey.memory, length: previousKey.length)
|
||||
resultStatement.bind(2, data: updatedKey.memory, length: updatedKey.length)
|
||||
case .int64:
|
||||
resultStatement.bind(1, number: previousKey.getInt64(0))
|
||||
resultStatement.bind(2, number: updatedKey.getInt64(0))
|
||||
}
|
||||
|
||||
return resultStatement
|
||||
}
|
||||
|
||||
private func copyStatement(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) -> SqlitePreparedStatement {
|
||||
precondition(self.queue.isCurrent())
|
||||
let _ = checkTable(fromTable)
|
||||
@ -1443,6 +1410,8 @@ public final class SqliteValueBox: ValueBox {
|
||||
}
|
||||
|
||||
private func fullTextDeleteStatement(_ table: ValueBoxFullTextTable, itemId: Data) -> SqlitePreparedStatement {
|
||||
precondition(self.queue.isCurrent())
|
||||
|
||||
let resultStatement: SqlitePreparedStatement
|
||||
|
||||
if let statement = self.fullTextDeleteStatements[table.id] {
|
||||
@ -2004,16 +1973,6 @@ public final class SqliteValueBox: ValueBox {
|
||||
}
|
||||
}
|
||||
|
||||
public func move(_ table: ValueBoxTable, from previousKey: ValueBoxKey, to updatedKey: ValueBoxKey) {
|
||||
precondition(self.queue.isCurrent())
|
||||
if let _ = self.tables[table.id] {
|
||||
let statement = self.moveStatement(table, from: previousKey, to: updatedKey)
|
||||
while statement.step(handle: self.database.handle, pathToRemoveOnError: self.removeDatabaseOnError ? self.databasePath : nil) {
|
||||
}
|
||||
statement.reset()
|
||||
}
|
||||
}
|
||||
|
||||
public func copy(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) {
|
||||
precondition(self.queue.isCurrent())
|
||||
if let _ = self.tables[fromTable.id] {
|
||||
@ -2254,11 +2213,6 @@ public final class SqliteValueBox: ValueBox {
|
||||
}
|
||||
self.deleteStatements.removeAll()
|
||||
|
||||
for (_, statement) in self.moveStatements {
|
||||
statement.destroy()
|
||||
}
|
||||
self.moveStatements.removeAll()
|
||||
|
||||
for (_, statement) in self.copyStatements {
|
||||
statement.destroy()
|
||||
}
|
||||
@ -2319,7 +2273,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
|
||||
postboxLog("dropping DB")
|
||||
|
||||
for fileName in dabaseFileNames {
|
||||
for fileName in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(fileName)")
|
||||
}
|
||||
|
||||
@ -2368,7 +2322,7 @@ public final class SqliteValueBox: ValueBox {
|
||||
|
||||
self.exportEncrypted(database: database, to: targetPath, encryptionParameters: encryptionParameters)
|
||||
|
||||
for name in dabaseFileNames {
|
||||
for name in databaseFileNames {
|
||||
let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(name)")
|
||||
let _ = try? FileManager.default.moveItem(atPath: targetPath + "/\(name)", toPath: self.basePath + "/\(name)")
|
||||
}
|
||||
|
@ -85,7 +85,6 @@ public protocol ValueBox {
|
||||
func exists(_ table: ValueBoxTable, key: ValueBoxKey) -> Bool
|
||||
func set(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer)
|
||||
func remove(_ table: ValueBoxTable, key: ValueBoxKey, secure: Bool)
|
||||
func move(_ table: ValueBoxTable, from previousKey: ValueBoxKey, to updatedKey: ValueBoxKey)
|
||||
func copy(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey)
|
||||
func removeRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey)
|
||||
func fullTextSet(_ table: ValueBoxFullTextTable, collectionId: String, itemId: String, contents: String, tags: String)
|
||||
|
@ -1065,4 +1065,42 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
||||
})
|
||||
}, activeCall: activeCall)
|
||||
}
|
||||
|
||||
open func joinConferenceCall(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 let currentGroupCallController = self.context.sharedContext.currentGroupCallController 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.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo))
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch error {
|
||||
case .doesNotExist:
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
default:
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1084,35 +1084,83 @@ public final class PresentationCallManagerImpl: PresentationCallManager {
|
||||
initialCall: EngineGroupCallDescription,
|
||||
reference: InternalGroupCallReference,
|
||||
beginWithVideo: Bool,
|
||||
invitePeerIds: [EnginePeer.Id]
|
||||
) {
|
||||
let keyPair: TelegramKeyPair
|
||||
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
|
||||
return
|
||||
invitePeerIds: [EnginePeer.Id],
|
||||
endCurrentIfAny: Bool
|
||||
) -> JoinGroupCallManagerResult {
|
||||
let begin: () -> Void = { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
let keyPair: TelegramKeyPair
|
||||
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
|
||||
return
|
||||
}
|
||||
keyPair = keyPairValue
|
||||
|
||||
let call = PresentationGroupCallImpl(
|
||||
accountContext: accountContext,
|
||||
audioSession: self.audioSession,
|
||||
callKitIntegration: nil,
|
||||
getDeviceAccessData: self.getDeviceAccessData,
|
||||
initialCall: (initialCall, reference),
|
||||
internalId: CallSessionInternalId(),
|
||||
peerId: nil,
|
||||
isChannel: false,
|
||||
invite: nil,
|
||||
joinAsPeerId: nil,
|
||||
isStream: false,
|
||||
keyPair: keyPair,
|
||||
conferenceSourceId: nil,
|
||||
isConference: true,
|
||||
beginWithVideo: beginWithVideo,
|
||||
sharedAudioContext: nil
|
||||
)
|
||||
for peerId in invitePeerIds {
|
||||
let _ = call.invitePeer(peerId, isVideo: beginWithVideo)
|
||||
}
|
||||
self.updateCurrentGroupCall(.group(call))
|
||||
}
|
||||
keyPair = keyPairValue
|
||||
|
||||
let call = PresentationGroupCallImpl(
|
||||
accountContext: accountContext,
|
||||
audioSession: self.audioSession,
|
||||
callKitIntegration: nil,
|
||||
getDeviceAccessData: self.getDeviceAccessData,
|
||||
initialCall: (initialCall, reference),
|
||||
internalId: CallSessionInternalId(),
|
||||
peerId: nil,
|
||||
isChannel: false,
|
||||
invite: nil,
|
||||
joinAsPeerId: nil,
|
||||
isStream: false,
|
||||
keyPair: keyPair,
|
||||
conferenceSourceId: nil,
|
||||
isConference: true,
|
||||
beginWithVideo: beginWithVideo,
|
||||
sharedAudioContext: nil
|
||||
)
|
||||
for peerId in invitePeerIds {
|
||||
let _ = call.invitePeer(peerId, isVideo: beginWithVideo)
|
||||
if let currentGroupCall = self.currentGroupCallValue {
|
||||
if endCurrentIfAny {
|
||||
switch currentGroupCall {
|
||||
case let .conferenceSource(conferenceSource):
|
||||
self.startCallDisposable.set((conferenceSource.hangUp()
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
case let .group(groupCall):
|
||||
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|
||||
|> filter { $0 }
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
}
|
||||
} else {
|
||||
switch currentGroupCall {
|
||||
case let .conferenceSource(conferenceSource):
|
||||
return .alreadyInProgress(conferenceSource.peerId)
|
||||
case let .group(groupCall):
|
||||
return .alreadyInProgress(groupCall.peerId)
|
||||
}
|
||||
}
|
||||
} else if let currentCall = self.currentCall {
|
||||
if endCurrentIfAny {
|
||||
self.callKitIntegration?.dropCall(uuid: currentCall.internalId)
|
||||
self.startCallDisposable.set((currentCall.hangUp()
|
||||
|> deliverOnMainQueue).start(next: { _ in
|
||||
begin()
|
||||
}))
|
||||
} else {
|
||||
return .alreadyInProgress(currentCall.peerId)
|
||||
}
|
||||
} else {
|
||||
begin()
|
||||
}
|
||||
self.updateCurrentGroupCall(.group(call))
|
||||
return .joined
|
||||
}
|
||||
}
|
||||
|
@ -3394,6 +3394,10 @@ public final class PresentationGroupCallImpl: PresentationGroupCall {
|
||||
}
|
||||
|
||||
private func updateProximityMonitoring() {
|
||||
if self.sharedAudioContext != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var shouldMonitorProximity = false
|
||||
switch self.currentSelectedAudioOutputValue {
|
||||
case .builtin:
|
||||
|
@ -2,6 +2,7 @@ import Foundation
|
||||
import SwiftSignalKit
|
||||
import TelegramVoip
|
||||
import TelegramAudio
|
||||
import DeviceProximity
|
||||
|
||||
public final class SharedCallAudioContext {
|
||||
private static weak var current: SharedCallAudioContext?
|
||||
@ -33,6 +34,8 @@ public final class SharedCallAudioContext {
|
||||
|
||||
private let audioSessionShouldBeActive = Promise<Bool>(true)
|
||||
private var initialSetupTimer: Foundation.Timer?
|
||||
|
||||
private var proximityManagerIndex: Int?
|
||||
|
||||
static func get(audioSession: ManagedAudioSession, callKitIntegration: CallKitIntegration?, defaultToSpeaker: Bool = false, reuseCurrent: Bool = false) -> SharedCallAudioContext {
|
||||
if let current = self.current, reuseCurrent {
|
||||
@ -105,6 +108,8 @@ public final class SharedCallAudioContext {
|
||||
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
|
||||
audioSessionControl.setup(synchronous: true)
|
||||
}
|
||||
|
||||
self.updateProximityMonitoring()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -129,6 +134,7 @@ public final class SharedCallAudioContext {
|
||||
if let currentOutput = currentOutput {
|
||||
self.currentAudioOutputValue = currentOutput
|
||||
self.didSetCurrentAudioOutputValue = true
|
||||
self.updateProximityMonitoring()
|
||||
}
|
||||
|
||||
var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput))
|
||||
@ -186,6 +192,7 @@ public final class SharedCallAudioContext {
|
||||
self.audioOutputStateValue = value
|
||||
if let currentOutput = value.1 {
|
||||
self.currentAudioOutputValue = currentOutput
|
||||
self.updateProximityMonitoring()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -196,6 +203,10 @@ public final class SharedCallAudioContext {
|
||||
self.isAudioSessionActiveDisposable?.dispose()
|
||||
self.audioOutputStateDisposable?.dispose()
|
||||
self.initialSetupTimer?.invalidate()
|
||||
|
||||
if let proximityManagerIndex = self.proximityManagerIndex {
|
||||
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
||||
}
|
||||
}
|
||||
|
||||
func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
||||
@ -228,4 +239,26 @@ public final class SharedCallAudioContext {
|
||||
self.setCurrentAudioOutput(.speaker)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateProximityMonitoring() {
|
||||
var shouldMonitorProximity = false
|
||||
switch self.currentAudioOutputValue {
|
||||
case .builtin:
|
||||
shouldMonitorProximity = true
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if shouldMonitorProximity {
|
||||
if self.proximityManagerIndex == nil {
|
||||
self.proximityManagerIndex = DeviceProximityManager.shared().add { _ in
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if let proximityManagerIndex = self.proximityManagerIndex {
|
||||
self.proximityManagerIndex = nil
|
||||
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -566,7 +566,7 @@ final class VideoChatMicButtonComponent: Component {
|
||||
|
||||
transition.setPosition(view: self.icon.view, position: iconFrame.center)
|
||||
transition.setBounds(view: self.icon.view, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
||||
transition.setScale(view: self.icon.view, scale: component.isCollapsed ? ((iconSize.width - 24.0) / iconSize.width) : 1.0)
|
||||
transition.setScale(view: self.icon.view, scale: component.isCollapsed ? ((iconSize.width - 32.0) / iconSize.width) : 1.0)
|
||||
|
||||
switch component.content {
|
||||
case .connecting:
|
||||
|
@ -25,12 +25,16 @@ final class VideoChatParticipantsComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
var leftInset: CGFloat
|
||||
var rightInset: CGFloat
|
||||
var videoColumn: Column?
|
||||
var mainColumn: Column
|
||||
var columnSpacing: CGFloat
|
||||
var isMainColumnHidden: Bool
|
||||
|
||||
init(videoColumn: Column?, mainColumn: Column, columnSpacing: CGFloat, isMainColumnHidden: Bool) {
|
||||
init(leftInset: CGFloat, rightInset: CGFloat, videoColumn: Column?, mainColumn: Column, columnSpacing: CGFloat, isMainColumnHidden: Bool) {
|
||||
self.leftInset = leftInset
|
||||
self.rightInset = rightInset
|
||||
self.videoColumn = videoColumn
|
||||
self.mainColumn = mainColumn
|
||||
self.columnSpacing = columnSpacing
|
||||
@ -509,13 +513,13 @@ final class VideoChatParticipantsComponent: Component {
|
||||
|
||||
if let videoColumn = layout.videoColumn {
|
||||
let columnsWidth: CGFloat = videoColumn.width + layout.columnSpacing + layout.mainColumn.width
|
||||
let columnsSideInset: CGFloat = floorToScreenPixels((containerSize.width - columnsWidth) * 0.5)
|
||||
let columnsLeftInset: CGFloat = layout.leftInset
|
||||
|
||||
var separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), y: 0.0), size: CGSize(width: gridWidth, height: containerSize.height))
|
||||
var separateVideoGridFrame = CGRect(origin: CGPoint(x: columnsLeftInset, y: 0.0), size: CGSize(width: gridWidth, height: containerSize.height))
|
||||
|
||||
var listFrame = CGRect(origin: CGPoint(x: separateVideoGridFrame.maxX + layout.columnSpacing, y: 0.0), size: CGSize(width: listWidth, height: containerSize.height))
|
||||
if isUIHidden || layout.isMainColumnHidden {
|
||||
listFrame.origin.x = containerSize.width + columnsSideInset
|
||||
listFrame.origin.x = containerSize.width + columnsLeftInset
|
||||
separateVideoGridFrame = CGRect(origin: CGPoint(x: floor((containerSize.width - columnsWidth) * 0.5), y: 0.0), size: CGSize(width: columnsWidth, height: containerSize.height))
|
||||
}
|
||||
|
||||
|
@ -387,7 +387,6 @@ final class VideoChatScreenComponent: Component {
|
||||
|
||||
let targetContainer = SimpleLayer()
|
||||
targetContainer.masksToBounds = true
|
||||
targetContainer.backgroundColor = UIColor.blue.cgColor
|
||||
targetContainer.cornerRadius = 10.0
|
||||
|
||||
self.containerView.layer.insertSublayer(targetContainer, above: participantsView.layer)
|
||||
@ -1276,11 +1275,61 @@ final class VideoChatScreenComponent: Component {
|
||||
)
|
||||
}
|
||||
|
||||
var firstMemberWithPresentation: VideoChatParticipantsComponent.VideoParticipantKey?
|
||||
if let members {
|
||||
for participant in members.participants {
|
||||
if participant.presentationDescription != nil {
|
||||
firstMemberWithPresentation = VideoChatParticipantsComponent.VideoParticipantKey(id: participant.id, isPresentation: true)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if let previousMembers = self.members, let firstMemberWithPresentationValue = firstMemberWithPresentation {
|
||||
for participant in previousMembers.participants {
|
||||
if participant.id == firstMemberWithPresentationValue.id && participant.presentationDescription != nil {
|
||||
firstMemberWithPresentation = nil
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
firstMemberWithPresentation = nil
|
||||
}
|
||||
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState {
|
||||
if expandedParticipantsVideoState.isMainParticipantPinned {
|
||||
firstMemberWithPresentation = nil
|
||||
}
|
||||
} else {
|
||||
firstMemberWithPresentation = nil
|
||||
}
|
||||
|
||||
if let members, firstMemberWithPresentation != nil {
|
||||
var videoCount = 0
|
||||
for participant in members.participants {
|
||||
if participant.presentationDescription != nil {
|
||||
videoCount += 1
|
||||
}
|
||||
if participant.videoDescription != nil {
|
||||
videoCount += 1
|
||||
}
|
||||
}
|
||||
if videoCount <= 1 {
|
||||
firstMemberWithPresentation = nil
|
||||
}
|
||||
} else {
|
||||
firstMemberWithPresentation = nil
|
||||
}
|
||||
|
||||
self.members = members
|
||||
|
||||
if let members {
|
||||
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.id == .peer(invitedPeer.peer.id) }) })
|
||||
}
|
||||
|
||||
if let firstMemberWithPresentation {
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: firstMemberWithPresentation, isMainParticipantPinned: true, isUIHidden: self.expandedParticipantsVideoState?.isUIHidden ?? false)
|
||||
}
|
||||
|
||||
if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
||||
var videoCount = 0
|
||||
for participant in members.participants {
|
||||
@ -1886,7 +1935,18 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
})
|
||||
|
||||
let sideInset: CGFloat = max(environment.safeInsets.left, 14.0)
|
||||
let landscapeControlsWidth: CGFloat = 88.0
|
||||
let landscapeControlsOffsetX: CGFloat = 18.0
|
||||
let landscapeControlsSpacing: CGFloat = 30.0
|
||||
|
||||
let leftInset: CGFloat = max(environment.safeInsets.left, 14.0)
|
||||
|
||||
var rightInset: CGFloat = max(environment.safeInsets.right, 14.0)
|
||||
var buttonsOnTheSide = false
|
||||
if availableSize.width > maxSingleColumnWidth && !environment.metrics.isTablet {
|
||||
buttonsOnTheSide = true
|
||||
rightInset += landscapeControlsWidth
|
||||
}
|
||||
|
||||
let topInset: CGFloat = environment.statusBarHeight + 2.0
|
||||
let navigationBarHeight: CGFloat = 61.0
|
||||
@ -1944,7 +2004,7 @@ final class VideoChatScreenComponent: Component {
|
||||
containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter)
|
||||
)
|
||||
|
||||
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: sideInset + floor((navigationButtonAreaWidth - navigationLeftButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize)
|
||||
let navigationLeftButtonFrame = CGRect(origin: CGPoint(x: leftInset + floor((navigationButtonAreaWidth - navigationLeftButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationLeftButtonSize.height) * 0.5)), size: navigationLeftButtonSize)
|
||||
if let navigationLeftButtonView = self.navigationLeftButton.view {
|
||||
if navigationLeftButtonView.superview == nil {
|
||||
self.containerView.addSubview(navigationLeftButtonView)
|
||||
@ -1953,7 +2013,7 @@ final class VideoChatScreenComponent: Component {
|
||||
alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
||||
}
|
||||
|
||||
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - sideInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
||||
let navigationRightButtonFrame = CGRect(origin: CGPoint(x: availableSize.width - rightInset - navigationButtonAreaWidth + floor((navigationButtonAreaWidth - navigationRightButtonSize.width) * 0.5), y: topInset + floor((navigationBarHeight - navigationRightButtonSize.height) * 0.5)), size: navigationRightButtonSize)
|
||||
if let navigationRightButtonView = self.navigationRightButton.view {
|
||||
if navigationRightButtonView.superview == nil {
|
||||
self.containerView.addSubview(navigationRightButtonView)
|
||||
@ -2037,7 +2097,7 @@ final class VideoChatScreenComponent: Component {
|
||||
|
||||
let canManageCall = self.callState?.canManageCall ?? false
|
||||
|
||||
var maxTitleWidth: CGFloat = availableSize.width - sideInset * 2.0 - navigationButtonAreaWidth * 2.0 - 4.0 * 2.0
|
||||
var maxTitleWidth: CGFloat = availableSize.width - leftInset - rightInset - navigationButtonAreaWidth * 2.0 - 4.0 * 2.0
|
||||
if isTwoColumnLayout {
|
||||
maxTitleWidth -= 110.0
|
||||
}
|
||||
@ -2086,7 +2146,7 @@ final class VideoChatScreenComponent: Component {
|
||||
environment: {},
|
||||
containerSize: CGSize(width: maxTitleWidth, height: 100.0)
|
||||
)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: floor((availableSize.width - titleSize.width) * 0.5), y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize)
|
||||
let titleFrame = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - titleSize.width) * 0.5), y: topInset + floor((navigationBarHeight - titleSize.height) * 0.5)), size: titleSize)
|
||||
if let titleView = self.title.view {
|
||||
if titleView.superview == nil {
|
||||
self.containerView.addSubview(titleView)
|
||||
@ -2132,9 +2192,9 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: min(400.0, availableSize.width - sideInset * 2.0 - 16.0 * 2.0), height: 10000.0)
|
||||
containerSize: CGSize(width: min(400.0, availableSize.width - leftInset - rightInset - 16.0 * 2.0), height: 10000.0)
|
||||
)
|
||||
let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: floor((availableSize.width - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize)
|
||||
let encryptionKeyFrameValue = CGRect(origin: CGPoint(x: leftInset + floor((availableSize.width - leftInset - rightInset - encryptionKeySize.width) * 0.5), y: navigationHeight), size: encryptionKeySize)
|
||||
encryptionKeyFrame = encryptionKeyFrameValue
|
||||
|
||||
navigationHeight += encryptionKeySize.height
|
||||
@ -2164,7 +2224,7 @@ final class VideoChatScreenComponent: Component {
|
||||
mainColumnSideInset = 0.0
|
||||
} else {
|
||||
mainColumnWidth = availableSize.width
|
||||
mainColumnSideInset = sideInset
|
||||
mainColumnSideInset = max(leftInset, rightInset)
|
||||
}
|
||||
}
|
||||
|
||||
@ -2181,7 +2241,9 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
let microphoneButtonDiameter: CGFloat
|
||||
if isTwoColumnLayout {
|
||||
if buttonsOnTheSide {
|
||||
microphoneButtonDiameter = actionButtonDiameter
|
||||
} else if isTwoColumnLayout {
|
||||
microphoneButtonDiameter = collapsedMicrophoneButtonDiameter
|
||||
} else {
|
||||
if areButtonsCollapsed {
|
||||
@ -2221,11 +2283,21 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
}
|
||||
|
||||
if isTwoColumnLayout {
|
||||
if buttonsOnTheSide {
|
||||
collapsedMicrophoneButtonFrame.origin.y = floor((availableSize.height - actionButtonDiameter) * 0.5)
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - environment.safeInsets.right - landscapeControlsWidth + landscapeControlsOffsetX
|
||||
|
||||
if isMainColumnHidden {
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - sideInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5) + sideInset + mainColumnWidth
|
||||
collapsedMicrophoneButtonFrame.origin.x += mainColumnWidth + landscapeControlsWidth
|
||||
}
|
||||
|
||||
collapsedMicrophoneButtonFrame.size = CGSize(width: actionButtonDiameter, height: actionButtonDiameter)
|
||||
expandedMicrophoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||
} else if isTwoColumnLayout {
|
||||
if isMainColumnHidden {
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - rightInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5) + leftInset + mainColumnWidth
|
||||
} else {
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - sideInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5)
|
||||
collapsedMicrophoneButtonFrame.origin.x = availableSize.width - rightInset - mainColumnWidth + floor((mainColumnWidth - collapsedMicrophoneButtonDiameter) * 0.5)
|
||||
}
|
||||
expandedMicrophoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||
} else {
|
||||
@ -2242,7 +2314,11 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
let collapsedParticipantsClippingY: CGFloat
|
||||
collapsedParticipantsClippingY = collapsedMicrophoneButtonFrame.minY - 16.0
|
||||
if buttonsOnTheSide {
|
||||
collapsedParticipantsClippingY = availableSize.height
|
||||
} else {
|
||||
collapsedParticipantsClippingY = collapsedMicrophoneButtonFrame.minY - 16.0
|
||||
}
|
||||
|
||||
let expandedParticipantsClippingY: CGFloat
|
||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
||||
@ -2255,8 +2331,16 @@ final class VideoChatScreenComponent: Component {
|
||||
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
||||
}
|
||||
|
||||
let leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
let rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
var leftActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.minX - actionMicrophoneButtonSpacing - actionButtonDiameter, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
var rightActionButtonFrame = CGRect(origin: CGPoint(x: microphoneButtonFrame.maxX + actionMicrophoneButtonSpacing, y: microphoneButtonFrame.minY + floor((microphoneButtonFrame.height - actionButtonDiameter) * 0.5)), size: CGSize(width: actionButtonDiameter, height: actionButtonDiameter))
|
||||
|
||||
if buttonsOnTheSide {
|
||||
leftActionButtonFrame.origin.x = microphoneButtonFrame.minX
|
||||
leftActionButtonFrame.origin.y = microphoneButtonFrame.minY - landscapeControlsSpacing - actionButtonDiameter
|
||||
|
||||
rightActionButtonFrame.origin.x = microphoneButtonFrame.minX
|
||||
rightActionButtonFrame.origin.y = microphoneButtonFrame.maxY + landscapeControlsSpacing
|
||||
}
|
||||
|
||||
let participantsSize = availableSize
|
||||
|
||||
@ -2264,8 +2348,10 @@ final class VideoChatScreenComponent: Component {
|
||||
let participantsLayout: VideoChatParticipantsComponent.Layout
|
||||
if isTwoColumnLayout {
|
||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
||||
let videoColumnWidth: CGFloat = max(10.0, availableSize.width - sideInset * 2.0 - mainColumnWidth - columnSpacing)
|
||||
let videoColumnWidth: CGFloat = max(10.0, availableSize.width - leftInset - rightInset - mainColumnWidth - columnSpacing)
|
||||
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||
leftInset: leftInset,
|
||||
rightInset: rightInset,
|
||||
videoColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||
width: videoColumnWidth,
|
||||
insets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: max(14.0, environment.safeInsets.bottom), right: 0.0)
|
||||
@ -2280,6 +2366,8 @@ final class VideoChatScreenComponent: Component {
|
||||
} else {
|
||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
||||
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||
leftInset: leftInset,
|
||||
rightInset: rightInset,
|
||||
videoColumn: nil,
|
||||
mainColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||
width: mainColumnWidth,
|
||||
@ -2354,7 +2442,7 @@ final class VideoChatScreenComponent: Component {
|
||||
isUIHidden = alsoSetIsUIHidden
|
||||
}
|
||||
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: false, isUIHidden: isUIHidden)
|
||||
self.expandedParticipantsVideoState = VideoChatParticipantsComponent.ExpandedVideoState(mainParticipant: key, isMainParticipantPinned: self.expandedParticipantsVideoState == nil && key.isPresentation, isUIHidden: isUIHidden)
|
||||
self.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 3.0
|
||||
self.state?.updated(transition: .spring(duration: 0.4))
|
||||
} else if self.expandedParticipantsVideoState != nil {
|
||||
@ -2585,7 +2673,7 @@ final class VideoChatScreenComponent: Component {
|
||||
call: call,
|
||||
strings: environment.strings,
|
||||
content: micButtonContent,
|
||||
isCollapsed: areButtonsCollapsed,
|
||||
isCollapsed: areButtonsCollapsed || buttonsOnTheSide,
|
||||
updateUnmutedStateIsPushToTalk: { [weak self] unmutedStateIsPushToTalk in
|
||||
guard let self, let currentCall = self.currentCall else {
|
||||
return
|
||||
@ -2654,7 +2742,7 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
)),
|
||||
environment: {},
|
||||
containerSize: CGSize(width: microphoneButtonDiameter, height: microphoneButtonDiameter)
|
||||
containerSize: microphoneButtonFrame.size
|
||||
)
|
||||
if let microphoneButtonView = self.microphoneButton.view {
|
||||
if microphoneButtonView.superview == nil {
|
||||
@ -2709,14 +2797,14 @@ final class VideoChatScreenComponent: Component {
|
||||
}
|
||||
|
||||
var displayVideoControlButton = true
|
||||
if areButtonsCollapsed {
|
||||
if areButtonsCollapsed || buttonsOnTheSide {
|
||||
displayVideoControlButton = false
|
||||
} else if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
||||
displayVideoControlButton = false
|
||||
}
|
||||
if case .audio = videoControlButtonContent {
|
||||
if let (availableOutputs, _) = self.audioOutputState {
|
||||
if availableOutputs.count <= 0 {
|
||||
if availableOutputs.count <= 1 {
|
||||
displayVideoControlButton = false
|
||||
}
|
||||
} else {
|
||||
@ -2762,7 +2850,7 @@ final class VideoChatScreenComponent: Component {
|
||||
strings: environment.strings,
|
||||
content: videoButtonContent,
|
||||
microphoneState: actionButtonMicrophoneState,
|
||||
isCollapsed: areButtonsCollapsed
|
||||
isCollapsed: areButtonsCollapsed || buttonsOnTheSide
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
@ -2816,7 +2904,7 @@ final class VideoChatScreenComponent: Component {
|
||||
strings: environment.strings,
|
||||
content: .leave,
|
||||
microphoneState: actionButtonMicrophoneState,
|
||||
isCollapsed: areButtonsCollapsed
|
||||
isCollapsed: areButtonsCollapsed || buttonsOnTheSide
|
||||
)),
|
||||
effectAlignment: .center,
|
||||
action: { [weak self] in
|
||||
|
@ -298,6 +298,8 @@ extension VideoChatScreenComponent.View {
|
||||
let nameDisplayOrder = presentationData.nameDisplayOrder
|
||||
if let chatPeer {
|
||||
items.append(DeleteChatPeerActionSheetItem(context: groupCall.accountContext, peer: peer, chatPeer: chatPeer, action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder))
|
||||
} else {
|
||||
items.append(ActionSheetTextItem(title: environment.strings.VoiceChat_RemoveConferencePeerConfirmation(peer.displayTitle(strings: environment.strings, displayOrder: nameDisplayOrder)).string, parseMarkdown: true))
|
||||
}
|
||||
|
||||
items.append(ActionSheetButtonItem(title: environment.strings.VoiceChat_RemovePeerRemove, color: .destructive, action: { [weak self, weak actionSheet] in
|
||||
|
@ -143,13 +143,15 @@ func _internal_joinLinkInformation(_ hash: String, account: Account) -> Signal<E
|
||||
}
|
||||
|
||||
public final class JoinCallLinkInformation {
|
||||
public let reference: InternalGroupCallReference
|
||||
public let id: Int64
|
||||
public let accessHash: Int64
|
||||
public let inviter: EnginePeer?
|
||||
public let members: [EnginePeer]
|
||||
public let totalMemberCount: Int
|
||||
|
||||
public init(id: Int64, accessHash: Int64, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
|
||||
public init(reference: InternalGroupCallReference, id: Int64, accessHash: Int64, inviter: EnginePeer?, members: [EnginePeer], totalMemberCount: Int) {
|
||||
self.reference = reference
|
||||
self.id = id
|
||||
self.accessHash = accessHash
|
||||
self.inviter = inviter
|
||||
@ -176,7 +178,7 @@ func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Sign
|
||||
members.append(peer)
|
||||
}
|
||||
}
|
||||
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
|
||||
return .single(JoinCallLinkInformation(reference: .link(slug: hash), id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
|
||||
}
|
||||
}
|
||||
|
||||
@ -204,6 +206,6 @@ func _internal_joinCallInvitationInformation(account: Account, messageId: Messag
|
||||
members.append(peer)
|
||||
}
|
||||
}
|
||||
return .single(JoinCallLinkInformation(id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
|
||||
return .single(JoinCallLinkInformation(reference: .message(id: messageId), id: call.info.id, accessHash: call.info.accessHash, inviter: nil, members: members, totalMemberCount: call.info.participantCount))
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ public enum PermissionContentIcon: Equatable {
|
||||
public final class PermissionContentNode: ASDisplayNode {
|
||||
private var theme: PresentationTheme
|
||||
public let kind: Int32
|
||||
private let filterHitTest: Bool
|
||||
|
||||
private let iconNode: ASImageNode
|
||||
private let nearbyIconNode: PeersNearbyIconNode?
|
||||
@ -55,12 +56,13 @@ public final class PermissionContentNode: ASDisplayNode {
|
||||
|
||||
public var validLayout: (CGSize, UIEdgeInsets)?
|
||||
|
||||
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: PermissionContentIcon, title: String, subtitle: String? = nil, text: String, buttonTitle: String, secondaryButtonTitle: String? = nil, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?) {
|
||||
public init(context: AccountContext, theme: PresentationTheme, strings: PresentationStrings, kind: Int32, icon: PermissionContentIcon, title: String, subtitle: String? = nil, text: String, buttonTitle: String, secondaryButtonTitle: String? = nil, footerText: String? = nil, buttonAction: @escaping () -> Void, openPrivacyPolicy: (() -> Void)?, filterHitTest: Bool = false) {
|
||||
self.theme = theme
|
||||
self.kind = kind
|
||||
|
||||
self.buttonAction = buttonAction
|
||||
self.openPrivacyPolicy = openPrivacyPolicy
|
||||
self.filterHitTest = filterHitTest
|
||||
|
||||
self.icon = icon
|
||||
self.title = title
|
||||
@ -163,6 +165,18 @@ public final class PermissionContentNode: ASDisplayNode {
|
||||
self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
guard let result = super.hitTest(point, with: event) else {
|
||||
return nil
|
||||
}
|
||||
if self.filterHitTest {
|
||||
if result === self.view {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
public func updatePresentationData(_ presentationData: PresentationData) {
|
||||
let theme = presentationData.theme
|
||||
self.theme = theme
|
||||
|
@ -1392,7 +1392,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink
|
||||
))))
|
||||
})
|
||||
case let .localization(identifier):
|
||||
|
@ -394,20 +394,7 @@ private final class JoinSubjectScreenComponent: Component {
|
||||
self.environment?.controller()?.dismiss()
|
||||
})
|
||||
case let .groupCall(groupCall):
|
||||
component.context.sharedContext.callManager?.joinConferenceCall(
|
||||
accountContext: component.context,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: groupCall.id,
|
||||
accessHash: groupCall.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
reference: .link(slug: groupCall.slug),
|
||||
beginWithVideo: false,
|
||||
invitePeerIds: []
|
||||
)
|
||||
component.context.joinConferenceCall(call: groupCall.info, isVideo: false)
|
||||
|
||||
self.environment?.controller()?.dismiss()
|
||||
}
|
||||
|
@ -684,6 +684,106 @@ public final class AccountContextImpl: AccountContext {
|
||||
}
|
||||
}
|
||||
|
||||
public func joinConferenceCall(call: JoinCallLinkInformation, isVideo: Bool) {
|
||||
guard let callManager = self.sharedContext.callManager else {
|
||||
return
|
||||
}
|
||||
let result = callManager.joinConferenceCall(
|
||||
accountContext: self,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: false
|
||||
)
|
||||
if case let .alreadyInProgress(currentPeerId) = result {
|
||||
let dataInput: Signal<EnginePeer?, NoError>
|
||||
if let currentPeerId {
|
||||
dataInput = self.engine.data.get(
|
||||
TelegramEngine.EngineData.Item.Peer.Peer(id: currentPeerId)
|
||||
)
|
||||
} else {
|
||||
dataInput = .single(nil)
|
||||
}
|
||||
|
||||
let _ = (dataInput
|
||||
|> deliverOnMainQueue).start(next: { [weak self] current in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let presentationData = strongSelf.sharedContext.currentPresentationData.with { $0 }
|
||||
if let current = current {
|
||||
switch current {
|
||||
case .channel, .legacyGroup:
|
||||
let title: String
|
||||
let text: String
|
||||
if case let .channel(channel) = current, case .broadcast = channel.info {
|
||||
title = presentationData.strings.Call_LiveStreamInProgressTitle
|
||||
text = presentationData.strings.Call_LiveStreamInProgressConferenceMessage(current.compactDisplayTitle).string
|
||||
} else {
|
||||
title = presentationData.strings.Call_VoiceChatInProgressTitle
|
||||
text = presentationData.strings.Call_VoiceChatInProgressConferenceMessage(current.compactDisplayTitle).string
|
||||
}
|
||||
|
||||
strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: title, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = callManager.joinConferenceCall(
|
||||
accountContext: self,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: true
|
||||
)
|
||||
})]), on: .root)
|
||||
default:
|
||||
let text: String
|
||||
text = presentationData.strings.Call_VoiceChatInProgressConferenceMessage(current.compactDisplayTitle).string
|
||||
strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_CallInProgressTitle, text: text, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_Cancel, action: {}), TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
let _ = callManager.joinConferenceCall(
|
||||
accountContext: self,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.id,
|
||||
accessHash: call.accessHash,
|
||||
title: nil,
|
||||
scheduleTimestamp: nil,
|
||||
subscribedToScheduled: false,
|
||||
isStream: false
|
||||
),
|
||||
reference: call.reference,
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: [],
|
||||
endCurrentIfAny: true
|
||||
)
|
||||
})]), on: .root)
|
||||
}
|
||||
} else {
|
||||
strongSelf.sharedContext.mainWindow?.present(textAlertController(context: strongSelf, title: presentationData.strings.Call_CallInProgressTitle, text: presentationData.strings.Call_ExternalCallInProgressMessage, actions: [TextAlertAction(type: .genericAction, title: presentationData.strings.Common_OK, action: {
|
||||
})]), on: .root)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
public func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void) {
|
||||
guard let callResult = self.sharedContext.callManager?.requestCall(context: self, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false) else {
|
||||
return
|
||||
|
@ -2235,15 +2235,43 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
if context.account.id == accountId {
|
||||
context.account.callSessionManager.addConferenceInvitationMessages(ids: [(messageId, IncomingConferenceTermporaryExternalInfo(callId: groupCallId, isVideo: isVideo))])
|
||||
|
||||
/*disposable.set((context.account.callSessionManager.callState(internalId: internalId)
|
||||
|> deliverOnMainQueue).start(next: { state in
|
||||
switch state.state {
|
||||
case .terminated:
|
||||
callKitIntegration.dropCall(uuid: internalId)
|
||||
default:
|
||||
break
|
||||
let disposable = MetaDisposable()
|
||||
self.watchedCallsDisposables.add(disposable)
|
||||
|
||||
if let callManager = context.sharedContext.callManager {
|
||||
let signal = combineLatest(queue: .mainQueue(), context.account.callSessionManager.ringingStates()
|
||||
|> map { ringingStates -> Bool in
|
||||
for state in ringingStates {
|
||||
if state.id == internalId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
},
|
||||
callManager.currentGroupCallSignal
|
||||
|> map { currentGroupCall -> Bool in
|
||||
if case let .group(groupCall) = currentGroupCall {
|
||||
if groupCall.internalId == internalId {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
)
|
||||
|> mapToSignal { exists0, exists1 -> Signal<Void, NoError> in
|
||||
if !(exists0 || exists1) {
|
||||
return .single(Void())
|
||||
|> delay(1.0, queue: .mainQueue())
|
||||
}
|
||||
return .never()
|
||||
}
|
||||
}))*/
|
||||
|
||||
disposable.set((signal
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).startStrict(next: { _ in
|
||||
callKitIntegration.dropCall(uuid: internalId)
|
||||
}))
|
||||
}
|
||||
|
||||
processed = true
|
||||
|
||||
@ -2266,6 +2294,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
} else {
|
||||
guard var updateString = payloadJson["updates"] as? String else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "updates is nil")
|
||||
self.reportFailedIncomingCallKitCall()
|
||||
completion()
|
||||
return
|
||||
}
|
||||
@ -2277,11 +2306,13 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
}
|
||||
guard let updateData = Data(base64Encoded: updateString) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't decode updateData")
|
||||
self.reportFailedIncomingCallKitCall()
|
||||
completion()
|
||||
return
|
||||
}
|
||||
guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else {
|
||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't extract call update")
|
||||
self.reportFailedIncomingCallKitCall()
|
||||
completion()
|
||||
return
|
||||
}
|
||||
@ -2368,6 +2399,33 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)")
|
||||
}
|
||||
|
||||
private func reportFailedIncomingCallKitCall() {
|
||||
guard let callKitIntegration = CallKitIntegration.shared else {
|
||||
return
|
||||
}
|
||||
let uuid = CallSessionInternalId()
|
||||
callKitIntegration.reportIncomingCall(
|
||||
uuid: uuid,
|
||||
stableId: Int64.random(in: Int64.min ... Int64.max),
|
||||
handle: "Unknown",
|
||||
phoneNumber: nil,
|
||||
isVideo: false,
|
||||
displayTitle: "Unknown",
|
||||
completion: { error in
|
||||
if let error = error {
|
||||
if error.domain == "com.apple.CallKit.error.incomingcall" && (error.code == -3 || error.code == 3) {
|
||||
Logger.shared.log("PresentationCall", "reportFailedIncomingCallKitCall device in DND mode")
|
||||
} else {
|
||||
Logger.shared.log("PresentationCall", "reportFailedIncomingCallKitCall error \(error)")
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
Queue.mainQueue().after(1.0, {
|
||||
callKitIntegration.dropCall(uuid: uuid)
|
||||
})
|
||||
}
|
||||
|
||||
private func authorizedContext() -> Signal<AuthorizedApplicationContext, NoError> {
|
||||
return self.context.get()
|
||||
|> mapToSignal { context -> Signal<AuthorizedApplicationContext, NoError> in
|
||||
|
@ -2883,54 +2883,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
||||
return
|
||||
}
|
||||
|
||||
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 let currentGroupCallController = self.context.sharedContext.currentGroupCallController 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),
|
||||
invitePeerIds: []
|
||||
)
|
||||
}, error: { [weak self] error in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
switch error {
|
||||
case .doesNotExist:
|
||||
self.context.sharedContext.openCreateGroupCallUI(context: self.context, peerIds: conferenceCall.otherParticipants, parentController: self)
|
||||
default:
|
||||
let presentationData = self.context.sharedContext.currentPresentationData.with { $0 }
|
||||
self.present(textAlertController(context: self.context, title: nil, text: presentationData.strings.Login_UnknownError, actions: [TextAlertAction(type: .defaultAction, title: presentationData.strings.Common_OK, action: {})]), in: .window(.root))
|
||||
}
|
||||
})
|
||||
self.joinConferenceCall(message: EngineMessage(message))
|
||||
}, longTap: { [weak self] action, params in
|
||||
if let self {
|
||||
self.openLinkLongTap(action, params: params)
|
||||
|
@ -405,7 +405,8 @@ func openResolvedUrlImpl(
|
||||
slug: link,
|
||||
inviter: resolvedCallLink.inviter,
|
||||
members: resolvedCallLink.members,
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount
|
||||
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||
info: resolvedCallLink
|
||||
))))
|
||||
}, error: { _ in
|
||||
var elevatedLayout = true
|
||||
|
@ -2048,7 +2048,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
}
|
||||
|
||||
let openCall: () -> Void = {
|
||||
context.sharedContext.callManager?.joinConferenceCall(
|
||||
let _ = context.sharedContext.callManager?.joinConferenceCall(
|
||||
accountContext: context,
|
||||
initialCall: EngineGroupCallDescription(
|
||||
id: call.callInfo.id,
|
||||
@ -2060,7 +2060,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
),
|
||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||
beginWithVideo: isVideo,
|
||||
invitePeerIds: peerIds
|
||||
invitePeerIds: peerIds,
|
||||
endCurrentIfAny: true
|
||||
)
|
||||
completion?()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user