mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-15 21:45: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_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.TextCallsRestrictedMultipleUsers_any" = "{user_list}, and **%d** more people do not accept calls.";
|
||||||
"SendInviteLink.TextCallsRestrictedSendInviteLink" = "You can try to send an invite link instead.";
|
"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 inviter: EnginePeer?
|
||||||
public let members: [EnginePeer]
|
public let members: [EnginePeer]
|
||||||
public let totalMemberCount: Int
|
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.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
self.slug = slug
|
self.slug = slug
|
||||||
self.inviter = inviter
|
self.inviter = inviter
|
||||||
self.members = members
|
self.members = members
|
||||||
self.totalMemberCount = totalMemberCount
|
self.totalMemberCount = totalMemberCount
|
||||||
|
self.info = info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1325,6 +1327,7 @@ public protocol AccountContext: AnyObject {
|
|||||||
|
|
||||||
func scheduleGroupCall(peerId: PeerId, parentController: ViewController)
|
func scheduleGroupCall(peerId: PeerId, parentController: ViewController)
|
||||||
func joinGroupCall(peerId: PeerId, invite: String?, requestJoinAsPeerId: ((@escaping (PeerId?) -> Void) -> Void)?, activeCall: EngineGroupCallDescription)
|
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)
|
func requestCall(peerId: PeerId, isVideo: Bool, completion: @escaping () -> Void)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -579,6 +579,7 @@ public protocol PresentationCallManager: AnyObject {
|
|||||||
initialCall: EngineGroupCallDescription,
|
initialCall: EngineGroupCallDescription,
|
||||||
reference: InternalGroupCallReference,
|
reference: InternalGroupCallReference,
|
||||||
beginWithVideo: Bool,
|
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) {
|
private func createGroupCall(peerIds: [EnginePeer.Id], isVideo: Bool, completion: (() -> Void)? = nil) {
|
||||||
self.view.endEditing(true)
|
self.view.window?.endEditing(true)
|
||||||
|
|
||||||
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
guard !self.presentAccountFrozenInfoIfNeeded() else {
|
||||||
return
|
return
|
||||||
@ -264,7 +264,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.context.sharedContext.callManager?.joinConferenceCall(
|
let _ = self.context.sharedContext.callManager?.joinConferenceCall(
|
||||||
accountContext: self.context,
|
accountContext: self.context,
|
||||||
initialCall: EngineGroupCallDescription(
|
initialCall: EngineGroupCallDescription(
|
||||||
id: call.callInfo.id,
|
id: call.callInfo.id,
|
||||||
@ -276,7 +276,8 @@ public final class CallListController: TelegramBaseController {
|
|||||||
),
|
),
|
||||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||||
beginWithVideo: isVideo,
|
beginWithVideo: isVideo,
|
||||||
invitePeerIds: peerIds
|
invitePeerIds: peerIds,
|
||||||
|
endCurrentIfAny: true
|
||||||
)
|
)
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
@ -712,20 +713,7 @@ public final class CallListController: TelegramBaseController {
|
|||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.context.sharedContext.callManager?.joinConferenceCall(
|
self.context.joinConferenceCall(call: resolvedCallLink, isVideo: conferenceCall.flags.contains(.isVideo))
|
||||||
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
|
}, error: { [weak self] error in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
|
@ -799,7 +799,7 @@ private func contactListNodeEntries(accountPeer: EnginePeer?, peers: [ContactLis
|
|||||||
return entries
|
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 (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries)
|
||||||
|
|
||||||
let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) }
|
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)
|
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 {
|
private struct ContactsListNodeTransition {
|
||||||
@ -858,6 +858,7 @@ private struct ContactsListNodeTransition {
|
|||||||
let indexSections: [String]
|
let indexSections: [String]
|
||||||
let firstTime: Bool
|
let firstTime: Bool
|
||||||
let isEmpty: Bool
|
let isEmpty: Bool
|
||||||
|
let hasOptions: Bool
|
||||||
let scrollToItem: ListViewScrollToItem?
|
let scrollToItem: ListViewScrollToItem?
|
||||||
let animation: ContactListAnimation
|
let animation: ContactListAnimation
|
||||||
}
|
}
|
||||||
@ -1184,7 +1185,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||||||
authorizeImpl?()
|
authorizeImpl?()
|
||||||
}, openPrivacyPolicy: {
|
}, openPrivacyPolicy: {
|
||||||
openPrivacyPolicyImpl?()
|
openPrivacyPolicyImpl?()
|
||||||
})
|
}, filterHitTest: true)
|
||||||
self.authorizationNode.isHidden = true
|
self.authorizationNode.isHidden = true
|
||||||
|
|
||||||
super.init()
|
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 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)
|
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) {
|
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||||
@ -1885,7 +1886,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||||||
animation = .none
|
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) {
|
if OSAtomicCompareAndSwap32(1, 0, &firstTime) {
|
||||||
@ -1921,7 +1922,7 @@ public final class ContactListNode: ASDisplayNode {
|
|||||||
authorizeImpl?()
|
authorizeImpl?()
|
||||||
}, openPrivacyPolicy: {
|
}, openPrivacyPolicy: {
|
||||||
openPrivacyPolicyImpl?()
|
openPrivacyPolicyImpl?()
|
||||||
})
|
}, filterHitTest: true)
|
||||||
strongSelf.authorizationNode.isHidden = authorizationPreviousHidden
|
strongSelf.authorizationNode.isHidden = authorizationPreviousHidden
|
||||||
strongSelf.addSubnode(strongSelf.authorizationNode)
|
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
|
self.authorizationNode.isHidden = !transition.isEmpty || !self.displayPermissionPlaceholder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -589,6 +589,8 @@ public final class InviteLinkInviteController: ViewController {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}), in: .window(.root))
|
}), in: .window(.root))
|
||||||
|
|
||||||
|
strongSelf.controller?.dismiss()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -149,7 +149,7 @@ struct SqlitePreparedStatement {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private let dabaseFileNames: [String] = [
|
private let databaseFileNames: [String] = [
|
||||||
"db_sqlite",
|
"db_sqlite",
|
||||||
"db_sqlite-shm",
|
"db_sqlite-shm",
|
||||||
"db_sqlite-wal"
|
"db_sqlite-wal"
|
||||||
@ -194,7 +194,6 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
private var insertOrIgnorePrimaryKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
private var insertOrIgnorePrimaryKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||||
private var insertOrIgnoreIndexKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
private var insertOrIgnoreIndexKeyStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||||
private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
private var deleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||||
private var moveStatements: [Int32 : SqlitePreparedStatement] = [:]
|
|
||||||
private var copyStatements: [TablePairKey : SqlitePreparedStatement] = [:]
|
private var copyStatements: [TablePairKey : SqlitePreparedStatement] = [:]
|
||||||
private var fullTextInsertStatements: [Int32 : SqlitePreparedStatement] = [:]
|
private var fullTextInsertStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||||
private var fullTextDeleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
private var fullTextDeleteStatements: [Int32 : SqlitePreparedStatement] = [:]
|
||||||
@ -347,7 +346,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in databaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||||
}
|
}
|
||||||
database = Database(path, readOnly: false)!
|
database = Database(path, readOnly: false)!
|
||||||
@ -367,7 +366,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
assert(false)
|
assert(false)
|
||||||
for fileName in dabaseFileNames {
|
for fileName in databaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +399,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
if self.isEncrypted(database) {
|
if self.isEncrypted(database) {
|
||||||
postboxLog("Reencryption failed")
|
postboxLog("Reencryption failed")
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in databaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||||
}
|
}
|
||||||
database = Database(path, readOnly: false)!
|
database = Database(path, readOnly: false)!
|
||||||
@ -427,7 +426,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in databaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/\(fileName)")
|
||||||
}
|
}
|
||||||
database = Database(path, readOnly: false)!
|
database = Database(path, readOnly: false)!
|
||||||
@ -1224,7 +1223,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
}
|
}
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.insertOrReplacePrimaryKeyStatements[table.table.id] = preparedStatement
|
self.insertOrReplaceIndexKeyStatements[table.table.id] = preparedStatement
|
||||||
resultStatement = preparedStatement
|
resultStatement = preparedStatement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1279,7 +1278,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
preconditionFailure(errorText)
|
preconditionFailure(errorText)
|
||||||
}
|
}
|
||||||
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
let preparedStatement = SqlitePreparedStatement(statement: statement)
|
||||||
self.insertOrIgnorePrimaryKeyStatements[table.table.id] = preparedStatement
|
self.insertOrIgnoreIndexKeyStatements[table.table.id] = preparedStatement
|
||||||
resultStatement = preparedStatement
|
resultStatement = preparedStatement
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1330,38 +1329,6 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
return resultStatement
|
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 {
|
private func copyStatement(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) -> SqlitePreparedStatement {
|
||||||
precondition(self.queue.isCurrent())
|
precondition(self.queue.isCurrent())
|
||||||
let _ = checkTable(fromTable)
|
let _ = checkTable(fromTable)
|
||||||
@ -1443,6 +1410,8 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func fullTextDeleteStatement(_ table: ValueBoxFullTextTable, itemId: Data) -> SqlitePreparedStatement {
|
private func fullTextDeleteStatement(_ table: ValueBoxFullTextTable, itemId: Data) -> SqlitePreparedStatement {
|
||||||
|
precondition(self.queue.isCurrent())
|
||||||
|
|
||||||
let resultStatement: SqlitePreparedStatement
|
let resultStatement: SqlitePreparedStatement
|
||||||
|
|
||||||
if let statement = self.fullTextDeleteStatements[table.id] {
|
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) {
|
public func copy(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey) {
|
||||||
precondition(self.queue.isCurrent())
|
precondition(self.queue.isCurrent())
|
||||||
if let _ = self.tables[fromTable.id] {
|
if let _ = self.tables[fromTable.id] {
|
||||||
@ -2254,11 +2213,6 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
}
|
}
|
||||||
self.deleteStatements.removeAll()
|
self.deleteStatements.removeAll()
|
||||||
|
|
||||||
for (_, statement) in self.moveStatements {
|
|
||||||
statement.destroy()
|
|
||||||
}
|
|
||||||
self.moveStatements.removeAll()
|
|
||||||
|
|
||||||
for (_, statement) in self.copyStatements {
|
for (_, statement) in self.copyStatements {
|
||||||
statement.destroy()
|
statement.destroy()
|
||||||
}
|
}
|
||||||
@ -2319,7 +2273,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
|
|
||||||
postboxLog("dropping DB")
|
postboxLog("dropping DB")
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in databaseFileNames {
|
||||||
let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(fileName)")
|
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)
|
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.removeItem(atPath: self.basePath + "/\(name)")
|
||||||
let _ = try? FileManager.default.moveItem(atPath: targetPath + "/\(name)", toPath: 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 exists(_ table: ValueBoxTable, key: ValueBoxKey) -> Bool
|
||||||
func set(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer)
|
func set(_ table: ValueBoxTable, key: ValueBoxKey, value: MemoryBuffer)
|
||||||
func remove(_ table: ValueBoxTable, key: ValueBoxKey, secure: Bool)
|
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 copy(fromTable: ValueBoxTable, fromKey: ValueBoxKey, toTable: ValueBoxTable, toKey: ValueBoxKey)
|
||||||
func removeRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey)
|
func removeRange(_ table: ValueBoxTable, start: ValueBoxKey, end: ValueBoxKey)
|
||||||
func fullTextSet(_ table: ValueBoxFullTextTable, collectionId: String, itemId: String, contents: String, tags: String)
|
func fullTextSet(_ table: ValueBoxFullTextTable, collectionId: String, itemId: String, contents: String, tags: String)
|
||||||
|
@ -1065,4 +1065,42 @@ open class TelegramBaseController: ViewController, KeyShortcutResponder {
|
|||||||
})
|
})
|
||||||
}, activeCall: activeCall)
|
}, 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,
|
initialCall: EngineGroupCallDescription,
|
||||||
reference: InternalGroupCallReference,
|
reference: InternalGroupCallReference,
|
||||||
beginWithVideo: Bool,
|
beginWithVideo: Bool,
|
||||||
invitePeerIds: [EnginePeer.Id]
|
invitePeerIds: [EnginePeer.Id],
|
||||||
) {
|
endCurrentIfAny: Bool
|
||||||
let keyPair: TelegramKeyPair
|
) -> JoinGroupCallManagerResult {
|
||||||
guard let keyPairValue = TelegramE2EEncryptionProviderImpl.shared.generateKeyPair() else {
|
let begin: () -> Void = { [weak self] in
|
||||||
return
|
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(
|
if let currentGroupCall = self.currentGroupCallValue {
|
||||||
accountContext: accountContext,
|
if endCurrentIfAny {
|
||||||
audioSession: self.audioSession,
|
switch currentGroupCall {
|
||||||
callKitIntegration: nil,
|
case let .conferenceSource(conferenceSource):
|
||||||
getDeviceAccessData: self.getDeviceAccessData,
|
self.startCallDisposable.set((conferenceSource.hangUp()
|
||||||
initialCall: (initialCall, reference),
|
|> filter { $0 }
|
||||||
internalId: CallSessionInternalId(),
|
|> take(1)
|
||||||
peerId: nil,
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
isChannel: false,
|
begin()
|
||||||
invite: nil,
|
}))
|
||||||
joinAsPeerId: nil,
|
case let .group(groupCall):
|
||||||
isStream: false,
|
self.startCallDisposable.set((groupCall.leave(terminateIfPossible: false)
|
||||||
keyPair: keyPair,
|
|> filter { $0 }
|
||||||
conferenceSourceId: nil,
|
|> take(1)
|
||||||
isConference: true,
|
|> deliverOnMainQueue).start(next: { _ in
|
||||||
beginWithVideo: beginWithVideo,
|
begin()
|
||||||
sharedAudioContext: nil
|
}))
|
||||||
)
|
}
|
||||||
for peerId in invitePeerIds {
|
} else {
|
||||||
let _ = call.invitePeer(peerId, isVideo: beginWithVideo)
|
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() {
|
private func updateProximityMonitoring() {
|
||||||
|
if self.sharedAudioContext != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var shouldMonitorProximity = false
|
var shouldMonitorProximity = false
|
||||||
switch self.currentSelectedAudioOutputValue {
|
switch self.currentSelectedAudioOutputValue {
|
||||||
case .builtin:
|
case .builtin:
|
||||||
|
@ -2,6 +2,7 @@ import Foundation
|
|||||||
import SwiftSignalKit
|
import SwiftSignalKit
|
||||||
import TelegramVoip
|
import TelegramVoip
|
||||||
import TelegramAudio
|
import TelegramAudio
|
||||||
|
import DeviceProximity
|
||||||
|
|
||||||
public final class SharedCallAudioContext {
|
public final class SharedCallAudioContext {
|
||||||
private static weak var current: SharedCallAudioContext?
|
private static weak var current: SharedCallAudioContext?
|
||||||
@ -33,6 +34,8 @@ public final class SharedCallAudioContext {
|
|||||||
|
|
||||||
private let audioSessionShouldBeActive = Promise<Bool>(true)
|
private let audioSessionShouldBeActive = Promise<Bool>(true)
|
||||||
private var initialSetupTimer: Foundation.Timer?
|
private var initialSetupTimer: Foundation.Timer?
|
||||||
|
|
||||||
|
private var proximityManagerIndex: Int?
|
||||||
|
|
||||||
static func get(audioSession: ManagedAudioSession, callKitIntegration: CallKitIntegration?, defaultToSpeaker: Bool = false, reuseCurrent: Bool = false) -> SharedCallAudioContext {
|
static func get(audioSession: ManagedAudioSession, callKitIntegration: CallKitIntegration?, defaultToSpeaker: Bool = false, reuseCurrent: Bool = false) -> SharedCallAudioContext {
|
||||||
if let current = self.current, reuseCurrent {
|
if let current = self.current, reuseCurrent {
|
||||||
@ -105,6 +108,8 @@ public final class SharedCallAudioContext {
|
|||||||
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
|
audioSessionControl.setOutputMode(.custom(self.currentAudioOutputValue))
|
||||||
audioSessionControl.setup(synchronous: true)
|
audioSessionControl.setup(synchronous: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.updateProximityMonitoring()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -129,6 +134,7 @@ public final class SharedCallAudioContext {
|
|||||||
if let currentOutput = currentOutput {
|
if let currentOutput = currentOutput {
|
||||||
self.currentAudioOutputValue = currentOutput
|
self.currentAudioOutputValue = currentOutput
|
||||||
self.didSetCurrentAudioOutputValue = true
|
self.didSetCurrentAudioOutputValue = true
|
||||||
|
self.updateProximityMonitoring()
|
||||||
}
|
}
|
||||||
|
|
||||||
var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput))
|
var signal: Signal<([AudioSessionOutput], AudioSessionOutput?), NoError> = .single((availableOutputs, currentOutput))
|
||||||
@ -186,6 +192,7 @@ public final class SharedCallAudioContext {
|
|||||||
self.audioOutputStateValue = value
|
self.audioOutputStateValue = value
|
||||||
if let currentOutput = value.1 {
|
if let currentOutput = value.1 {
|
||||||
self.currentAudioOutputValue = currentOutput
|
self.currentAudioOutputValue = currentOutput
|
||||||
|
self.updateProximityMonitoring()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -196,6 +203,10 @@ public final class SharedCallAudioContext {
|
|||||||
self.isAudioSessionActiveDisposable?.dispose()
|
self.isAudioSessionActiveDisposable?.dispose()
|
||||||
self.audioOutputStateDisposable?.dispose()
|
self.audioOutputStateDisposable?.dispose()
|
||||||
self.initialSetupTimer?.invalidate()
|
self.initialSetupTimer?.invalidate()
|
||||||
|
|
||||||
|
if let proximityManagerIndex = self.proximityManagerIndex {
|
||||||
|
DeviceProximityManager.shared().remove(proximityManagerIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
func setCurrentAudioOutput(_ output: AudioSessionOutput) {
|
||||||
@ -228,4 +239,26 @@ public final class SharedCallAudioContext {
|
|||||||
self.setCurrentAudioOutput(.speaker)
|
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.setPosition(view: self.icon.view, position: iconFrame.center)
|
||||||
transition.setBounds(view: self.icon.view, bounds: CGRect(origin: CGPoint(), size: iconFrame.size))
|
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 {
|
switch component.content {
|
||||||
case .connecting:
|
case .connecting:
|
||||||
|
@ -25,12 +25,16 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var leftInset: CGFloat
|
||||||
|
var rightInset: CGFloat
|
||||||
var videoColumn: Column?
|
var videoColumn: Column?
|
||||||
var mainColumn: Column
|
var mainColumn: Column
|
||||||
var columnSpacing: CGFloat
|
var columnSpacing: CGFloat
|
||||||
var isMainColumnHidden: Bool
|
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.videoColumn = videoColumn
|
||||||
self.mainColumn = mainColumn
|
self.mainColumn = mainColumn
|
||||||
self.columnSpacing = columnSpacing
|
self.columnSpacing = columnSpacing
|
||||||
@ -509,13 +513,13 @@ final class VideoChatParticipantsComponent: Component {
|
|||||||
|
|
||||||
if let videoColumn = layout.videoColumn {
|
if let videoColumn = layout.videoColumn {
|
||||||
let columnsWidth: CGFloat = videoColumn.width + layout.columnSpacing + layout.mainColumn.width
|
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))
|
var listFrame = CGRect(origin: CGPoint(x: separateVideoGridFrame.maxX + layout.columnSpacing, y: 0.0), size: CGSize(width: listWidth, height: containerSize.height))
|
||||||
if isUIHidden || layout.isMainColumnHidden {
|
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))
|
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()
|
let targetContainer = SimpleLayer()
|
||||||
targetContainer.masksToBounds = true
|
targetContainer.masksToBounds = true
|
||||||
targetContainer.backgroundColor = UIColor.blue.cgColor
|
|
||||||
targetContainer.cornerRadius = 10.0
|
targetContainer.cornerRadius = 10.0
|
||||||
|
|
||||||
self.containerView.layer.insertSublayer(targetContainer, above: participantsView.layer)
|
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
|
self.members = members
|
||||||
|
|
||||||
if let members {
|
if let members {
|
||||||
self.invitedPeers.removeAll(where: { invitedPeer in members.participants.contains(where: { $0.id == .peer(invitedPeer.peer.id) }) })
|
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 {
|
if let members, let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
||||||
var videoCount = 0
|
var videoCount = 0
|
||||||
for participant in members.participants {
|
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 topInset: CGFloat = environment.statusBarHeight + 2.0
|
||||||
let navigationBarHeight: CGFloat = 61.0
|
let navigationBarHeight: CGFloat = 61.0
|
||||||
@ -1944,7 +2004,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
containerSize: CGSize(width: navigationButtonDiameter, height: navigationButtonDiameter)
|
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 let navigationLeftButtonView = self.navigationLeftButton.view {
|
||||||
if navigationLeftButtonView.superview == nil {
|
if navigationLeftButtonView.superview == nil {
|
||||||
self.containerView.addSubview(navigationLeftButtonView)
|
self.containerView.addSubview(navigationLeftButtonView)
|
||||||
@ -1953,7 +2013,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
alphaTransition.setAlpha(view: navigationLeftButtonView, alpha: self.isAnimatedOutFromPrivateCall ? 0.0 : 1.0)
|
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 let navigationRightButtonView = self.navigationRightButton.view {
|
||||||
if navigationRightButtonView.superview == nil {
|
if navigationRightButtonView.superview == nil {
|
||||||
self.containerView.addSubview(navigationRightButtonView)
|
self.containerView.addSubview(navigationRightButtonView)
|
||||||
@ -2037,7 +2097,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
|
|
||||||
let canManageCall = self.callState?.canManageCall ?? false
|
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 {
|
if isTwoColumnLayout {
|
||||||
maxTitleWidth -= 110.0
|
maxTitleWidth -= 110.0
|
||||||
}
|
}
|
||||||
@ -2086,7 +2146,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: maxTitleWidth, height: 100.0)
|
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 let titleView = self.title.view {
|
||||||
if titleView.superview == nil {
|
if titleView.superview == nil {
|
||||||
self.containerView.addSubview(titleView)
|
self.containerView.addSubview(titleView)
|
||||||
@ -2132,9 +2192,9 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
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
|
encryptionKeyFrame = encryptionKeyFrameValue
|
||||||
|
|
||||||
navigationHeight += encryptionKeySize.height
|
navigationHeight += encryptionKeySize.height
|
||||||
@ -2164,7 +2224,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
mainColumnSideInset = 0.0
|
mainColumnSideInset = 0.0
|
||||||
} else {
|
} else {
|
||||||
mainColumnWidth = availableSize.width
|
mainColumnWidth = availableSize.width
|
||||||
mainColumnSideInset = sideInset
|
mainColumnSideInset = max(leftInset, rightInset)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2181,7 +2241,9 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let microphoneButtonDiameter: CGFloat
|
let microphoneButtonDiameter: CGFloat
|
||||||
if isTwoColumnLayout {
|
if buttonsOnTheSide {
|
||||||
|
microphoneButtonDiameter = actionButtonDiameter
|
||||||
|
} else if isTwoColumnLayout {
|
||||||
microphoneButtonDiameter = collapsedMicrophoneButtonDiameter
|
microphoneButtonDiameter = collapsedMicrophoneButtonDiameter
|
||||||
} else {
|
} else {
|
||||||
if areButtonsCollapsed {
|
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 {
|
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 {
|
} 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
|
expandedMicrophoneButtonFrame = collapsedMicrophoneButtonFrame
|
||||||
} else {
|
} else {
|
||||||
@ -2242,7 +2314,11 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let collapsedParticipantsClippingY: CGFloat
|
let collapsedParticipantsClippingY: CGFloat
|
||||||
collapsedParticipantsClippingY = collapsedMicrophoneButtonFrame.minY - 16.0
|
if buttonsOnTheSide {
|
||||||
|
collapsedParticipantsClippingY = availableSize.height
|
||||||
|
} else {
|
||||||
|
collapsedParticipantsClippingY = collapsedMicrophoneButtonFrame.minY - 16.0
|
||||||
|
}
|
||||||
|
|
||||||
let expandedParticipantsClippingY: CGFloat
|
let expandedParticipantsClippingY: CGFloat
|
||||||
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, expandedParticipantsVideoState.isUIHidden {
|
||||||
@ -2255,8 +2331,16 @@ final class VideoChatScreenComponent: Component {
|
|||||||
expandedParticipantsClippingY = expandedMicrophoneButtonFrame.minY - 24.0
|
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))
|
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))
|
||||||
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 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
|
let participantsSize = availableSize
|
||||||
|
|
||||||
@ -2264,8 +2348,10 @@ final class VideoChatScreenComponent: Component {
|
|||||||
let participantsLayout: VideoChatParticipantsComponent.Layout
|
let participantsLayout: VideoChatParticipantsComponent.Layout
|
||||||
if isTwoColumnLayout {
|
if isTwoColumnLayout {
|
||||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
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(
|
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||||
|
leftInset: leftInset,
|
||||||
|
rightInset: rightInset,
|
||||||
videoColumn: VideoChatParticipantsComponent.Layout.Column(
|
videoColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||||
width: videoColumnWidth,
|
width: videoColumnWidth,
|
||||||
insets: UIEdgeInsets(top: navigationHeight, left: 0.0, bottom: max(14.0, environment.safeInsets.bottom), right: 0.0)
|
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 {
|
} else {
|
||||||
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
let mainColumnInsets: UIEdgeInsets = UIEdgeInsets(top: navigationHeight, left: mainColumnSideInset, bottom: availableSize.height - collapsedParticipantsClippingY, right: mainColumnSideInset)
|
||||||
participantsLayout = VideoChatParticipantsComponent.Layout(
|
participantsLayout = VideoChatParticipantsComponent.Layout(
|
||||||
|
leftInset: leftInset,
|
||||||
|
rightInset: rightInset,
|
||||||
videoColumn: nil,
|
videoColumn: nil,
|
||||||
mainColumn: VideoChatParticipantsComponent.Layout.Column(
|
mainColumn: VideoChatParticipantsComponent.Layout.Column(
|
||||||
width: mainColumnWidth,
|
width: mainColumnWidth,
|
||||||
@ -2354,7 +2442,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
isUIHidden = alsoSetIsUIHidden
|
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.focusedSpeakerAutoSwitchDeadline = CFAbsoluteTimeGetCurrent() + 3.0
|
||||||
self.state?.updated(transition: .spring(duration: 0.4))
|
self.state?.updated(transition: .spring(duration: 0.4))
|
||||||
} else if self.expandedParticipantsVideoState != nil {
|
} else if self.expandedParticipantsVideoState != nil {
|
||||||
@ -2585,7 +2673,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
call: call,
|
call: call,
|
||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
content: micButtonContent,
|
content: micButtonContent,
|
||||||
isCollapsed: areButtonsCollapsed,
|
isCollapsed: areButtonsCollapsed || buttonsOnTheSide,
|
||||||
updateUnmutedStateIsPushToTalk: { [weak self] unmutedStateIsPushToTalk in
|
updateUnmutedStateIsPushToTalk: { [weak self] unmutedStateIsPushToTalk in
|
||||||
guard let self, let currentCall = self.currentCall else {
|
guard let self, let currentCall = self.currentCall else {
|
||||||
return
|
return
|
||||||
@ -2654,7 +2742,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
)),
|
)),
|
||||||
environment: {},
|
environment: {},
|
||||||
containerSize: CGSize(width: microphoneButtonDiameter, height: microphoneButtonDiameter)
|
containerSize: microphoneButtonFrame.size
|
||||||
)
|
)
|
||||||
if let microphoneButtonView = self.microphoneButton.view {
|
if let microphoneButtonView = self.microphoneButton.view {
|
||||||
if microphoneButtonView.superview == nil {
|
if microphoneButtonView.superview == nil {
|
||||||
@ -2709,14 +2797,14 @@ final class VideoChatScreenComponent: Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var displayVideoControlButton = true
|
var displayVideoControlButton = true
|
||||||
if areButtonsCollapsed {
|
if areButtonsCollapsed || buttonsOnTheSide {
|
||||||
displayVideoControlButton = false
|
displayVideoControlButton = false
|
||||||
} else if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
} else if let expandedParticipantsVideoState = self.expandedParticipantsVideoState, !expandedParticipantsVideoState.isUIHidden {
|
||||||
displayVideoControlButton = false
|
displayVideoControlButton = false
|
||||||
}
|
}
|
||||||
if case .audio = videoControlButtonContent {
|
if case .audio = videoControlButtonContent {
|
||||||
if let (availableOutputs, _) = self.audioOutputState {
|
if let (availableOutputs, _) = self.audioOutputState {
|
||||||
if availableOutputs.count <= 0 {
|
if availableOutputs.count <= 1 {
|
||||||
displayVideoControlButton = false
|
displayVideoControlButton = false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -2762,7 +2850,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
content: videoButtonContent,
|
content: videoButtonContent,
|
||||||
microphoneState: actionButtonMicrophoneState,
|
microphoneState: actionButtonMicrophoneState,
|
||||||
isCollapsed: areButtonsCollapsed
|
isCollapsed: areButtonsCollapsed || buttonsOnTheSide
|
||||||
)),
|
)),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
@ -2816,7 +2904,7 @@ final class VideoChatScreenComponent: Component {
|
|||||||
strings: environment.strings,
|
strings: environment.strings,
|
||||||
content: .leave,
|
content: .leave,
|
||||||
microphoneState: actionButtonMicrophoneState,
|
microphoneState: actionButtonMicrophoneState,
|
||||||
isCollapsed: areButtonsCollapsed
|
isCollapsed: areButtonsCollapsed || buttonsOnTheSide
|
||||||
)),
|
)),
|
||||||
effectAlignment: .center,
|
effectAlignment: .center,
|
||||||
action: { [weak self] in
|
action: { [weak self] in
|
||||||
|
@ -298,6 +298,8 @@ extension VideoChatScreenComponent.View {
|
|||||||
let nameDisplayOrder = presentationData.nameDisplayOrder
|
let nameDisplayOrder = presentationData.nameDisplayOrder
|
||||||
if let chatPeer {
|
if let chatPeer {
|
||||||
items.append(DeleteChatPeerActionSheetItem(context: groupCall.accountContext, peer: peer, chatPeer: chatPeer, action: .removeFromGroup, strings: environment.strings, nameDisplayOrder: nameDisplayOrder))
|
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
|
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 final class JoinCallLinkInformation {
|
||||||
|
public let reference: InternalGroupCallReference
|
||||||
public let id: Int64
|
public let id: Int64
|
||||||
public let accessHash: Int64
|
public let accessHash: Int64
|
||||||
public let inviter: EnginePeer?
|
public let inviter: EnginePeer?
|
||||||
public let members: [EnginePeer]
|
public let members: [EnginePeer]
|
||||||
public let totalMemberCount: Int
|
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.id = id
|
||||||
self.accessHash = accessHash
|
self.accessHash = accessHash
|
||||||
self.inviter = inviter
|
self.inviter = inviter
|
||||||
@ -176,7 +178,7 @@ func _internal_joinCallLinkInformation(_ hash: String, account: Account) -> Sign
|
|||||||
members.append(peer)
|
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)
|
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 {
|
public final class PermissionContentNode: ASDisplayNode {
|
||||||
private var theme: PresentationTheme
|
private var theme: PresentationTheme
|
||||||
public let kind: Int32
|
public let kind: Int32
|
||||||
|
private let filterHitTest: Bool
|
||||||
|
|
||||||
private let iconNode: ASImageNode
|
private let iconNode: ASImageNode
|
||||||
private let nearbyIconNode: PeersNearbyIconNode?
|
private let nearbyIconNode: PeersNearbyIconNode?
|
||||||
@ -55,12 +56,13 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||||||
|
|
||||||
public var validLayout: (CGSize, UIEdgeInsets)?
|
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.theme = theme
|
||||||
self.kind = kind
|
self.kind = kind
|
||||||
|
|
||||||
self.buttonAction = buttonAction
|
self.buttonAction = buttonAction
|
||||||
self.openPrivacyPolicy = openPrivacyPolicy
|
self.openPrivacyPolicy = openPrivacyPolicy
|
||||||
|
self.filterHitTest = filterHitTest
|
||||||
|
|
||||||
self.icon = icon
|
self.icon = icon
|
||||||
self.title = title
|
self.title = title
|
||||||
@ -163,6 +165,18 @@ public final class PermissionContentNode: ASDisplayNode {
|
|||||||
self.privacyPolicyButton.addTarget(self, action: #selector(self.privacyPolicyPressed), forControlEvents: .touchUpInside)
|
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) {
|
public func updatePresentationData(_ presentationData: PresentationData) {
|
||||||
let theme = presentationData.theme
|
let theme = presentationData.theme
|
||||||
self.theme = theme
|
self.theme = theme
|
||||||
|
@ -1392,7 +1392,8 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
slug: link,
|
slug: link,
|
||||||
inviter: resolvedCallLink.inviter,
|
inviter: resolvedCallLink.inviter,
|
||||||
members: resolvedCallLink.members,
|
members: resolvedCallLink.members,
|
||||||
totalMemberCount: resolvedCallLink.totalMemberCount
|
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||||
|
info: resolvedCallLink
|
||||||
))))
|
))))
|
||||||
})
|
})
|
||||||
case let .localization(identifier):
|
case let .localization(identifier):
|
||||||
|
@ -394,20 +394,7 @@ private final class JoinSubjectScreenComponent: Component {
|
|||||||
self.environment?.controller()?.dismiss()
|
self.environment?.controller()?.dismiss()
|
||||||
})
|
})
|
||||||
case let .groupCall(groupCall):
|
case let .groupCall(groupCall):
|
||||||
component.context.sharedContext.callManager?.joinConferenceCall(
|
component.context.joinConferenceCall(call: groupCall.info, isVideo: false)
|
||||||
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: []
|
|
||||||
)
|
|
||||||
|
|
||||||
self.environment?.controller()?.dismiss()
|
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) {
|
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 {
|
guard let callResult = self.sharedContext.callManager?.requestCall(context: self, peerId: peerId, isVideo: isVideo, endCurrentIfAny: false) else {
|
||||||
return
|
return
|
||||||
|
@ -2235,15 +2235,43 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
if context.account.id == accountId {
|
if context.account.id == accountId {
|
||||||
context.account.callSessionManager.addConferenceInvitationMessages(ids: [(messageId, IncomingConferenceTermporaryExternalInfo(callId: groupCallId, isVideo: isVideo))])
|
context.account.callSessionManager.addConferenceInvitationMessages(ids: [(messageId, IncomingConferenceTermporaryExternalInfo(callId: groupCallId, isVideo: isVideo))])
|
||||||
|
|
||||||
/*disposable.set((context.account.callSessionManager.callState(internalId: internalId)
|
let disposable = MetaDisposable()
|
||||||
|> deliverOnMainQueue).start(next: { state in
|
self.watchedCallsDisposables.add(disposable)
|
||||||
switch state.state {
|
|
||||||
case .terminated:
|
if let callManager = context.sharedContext.callManager {
|
||||||
callKitIntegration.dropCall(uuid: internalId)
|
let signal = combineLatest(queue: .mainQueue(), context.account.callSessionManager.ringingStates()
|
||||||
default:
|
|> map { ringingStates -> Bool in
|
||||||
break
|
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
|
processed = true
|
||||||
|
|
||||||
@ -2266,6 +2294,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
} else {
|
} else {
|
||||||
guard var updateString = payloadJson["updates"] as? String else {
|
guard var updateString = payloadJson["updates"] as? String else {
|
||||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "updates is nil")
|
Logger.shared.log("App \(self.episodeId) PushRegistry", "updates is nil")
|
||||||
|
self.reportFailedIncomingCallKitCall()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2277,11 +2306,13 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
}
|
}
|
||||||
guard let updateData = Data(base64Encoded: updateString) else {
|
guard let updateData = Data(base64Encoded: updateString) else {
|
||||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't decode updateData")
|
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't decode updateData")
|
||||||
|
self.reportFailedIncomingCallKitCall()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else {
|
guard let callUpdate = AccountStateManager.extractIncomingCallUpdate(data: updateData) else {
|
||||||
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't extract call update")
|
Logger.shared.log("App \(self.episodeId) PushRegistry", "Couldn't extract call update")
|
||||||
|
self.reportFailedIncomingCallKitCall()
|
||||||
completion()
|
completion()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -2368,6 +2399,33 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
Logger.shared.log("App \(self.episodeId)", "invalidated token for \(type)")
|
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> {
|
private func authorizedContext() -> Signal<AuthorizedApplicationContext, NoError> {
|
||||||
return self.context.get()
|
return self.context.get()
|
||||||
|> mapToSignal { context -> Signal<AuthorizedApplicationContext, NoError> in
|
|> mapToSignal { context -> Signal<AuthorizedApplicationContext, NoError> in
|
||||||
|
@ -2883,54 +2883,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var action: TelegramMediaAction?
|
self.joinConferenceCall(message: EngineMessage(message))
|
||||||
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))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}, longTap: { [weak self] action, params in
|
}, longTap: { [weak self] action, params in
|
||||||
if let self {
|
if let self {
|
||||||
self.openLinkLongTap(action, params: params)
|
self.openLinkLongTap(action, params: params)
|
||||||
|
@ -405,7 +405,8 @@ func openResolvedUrlImpl(
|
|||||||
slug: link,
|
slug: link,
|
||||||
inviter: resolvedCallLink.inviter,
|
inviter: resolvedCallLink.inviter,
|
||||||
members: resolvedCallLink.members,
|
members: resolvedCallLink.members,
|
||||||
totalMemberCount: resolvedCallLink.totalMemberCount
|
totalMemberCount: resolvedCallLink.totalMemberCount,
|
||||||
|
info: resolvedCallLink
|
||||||
))))
|
))))
|
||||||
}, error: { _ in
|
}, error: { _ in
|
||||||
var elevatedLayout = true
|
var elevatedLayout = true
|
||||||
|
@ -2048,7 +2048,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let openCall: () -> Void = {
|
let openCall: () -> Void = {
|
||||||
context.sharedContext.callManager?.joinConferenceCall(
|
let _ = context.sharedContext.callManager?.joinConferenceCall(
|
||||||
accountContext: context,
|
accountContext: context,
|
||||||
initialCall: EngineGroupCallDescription(
|
initialCall: EngineGroupCallDescription(
|
||||||
id: call.callInfo.id,
|
id: call.callInfo.id,
|
||||||
@ -2060,7 +2060,8 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
),
|
),
|
||||||
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
reference: .id(id: call.callInfo.id, accessHash: call.callInfo.accessHash),
|
||||||
beginWithVideo: isVideo,
|
beginWithVideo: isVideo,
|
||||||
invitePeerIds: peerIds
|
invitePeerIds: peerIds,
|
||||||
|
endCurrentIfAny: true
|
||||||
)
|
)
|
||||||
completion?()
|
completion?()
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user