mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
[WIP] Call UI
This commit is contained in:
parent
5bfe7750cd
commit
820b038bbc
@ -333,10 +333,10 @@ public final class ManagedAudioSession: NSObject {
|
|||||||
var headphonesAreActive = false
|
var headphonesAreActive = false
|
||||||
loop: for currentOutput in audioSession.currentRoute.outputs {
|
loop: for currentOutput in audioSession.currentRoute.outputs {
|
||||||
switch currentOutput.portType {
|
switch currentOutput.portType {
|
||||||
case .headphones, .bluetoothA2DP, .bluetoothHFP:
|
case .headphones, .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
|
||||||
headphonesAreActive = true
|
headphonesAreActive = true
|
||||||
hasHeadphones = true
|
hasHeadphones = true
|
||||||
hasBluetoothHeadphones = [.bluetoothA2DP, .bluetoothHFP].contains(currentOutput.portType)
|
hasBluetoothHeadphones = [.bluetoothA2DP, .bluetoothHFP, .bluetoothLE].contains(currentOutput.portType)
|
||||||
activeOutput = .headphones
|
activeOutput = .headphones
|
||||||
break loop
|
break loop
|
||||||
default:
|
default:
|
||||||
@ -730,7 +730,7 @@ public final class ManagedAudioSession: NSObject {
|
|||||||
let route = AVAudioSession.sharedInstance().currentRoute
|
let route = AVAudioSession.sharedInstance().currentRoute
|
||||||
//managedAudioSessionLog("\(route)")
|
//managedAudioSessionLog("\(route)")
|
||||||
for desc in route.outputs {
|
for desc in route.outputs {
|
||||||
if desc.portType == .headphones || desc.portType == .bluetoothA2DP || desc.portType == .bluetoothHFP {
|
if desc.portType == .headphones || desc.portType == .bluetoothA2DP || desc.portType == .bluetoothHFP || desc.portType == .bluetoothLE {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -977,7 +977,7 @@ public final class ManagedAudioSession: NSObject {
|
|||||||
} else {
|
} else {
|
||||||
loop: for route in routes {
|
loop: for route in routes {
|
||||||
switch route.portType {
|
switch route.portType {
|
||||||
case .headphones, .bluetoothA2DP, .bluetoothHFP:
|
case .headphones, .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
|
||||||
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
|
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
|
||||||
alreadySet = true
|
alreadySet = true
|
||||||
break loop
|
break loop
|
||||||
|
@ -50,6 +50,9 @@ public final class CallController: ViewController {
|
|||||||
return self._ready
|
return self._ready
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private let isDataReady = Promise<Bool>(false)
|
||||||
|
private let isContentsReady = Promise<Bool>(false)
|
||||||
|
|
||||||
private let sharedContext: SharedAccountContext
|
private let sharedContext: SharedAccountContext
|
||||||
private let account: Account
|
private let account: Account
|
||||||
public let call: PresentationCall
|
public let call: PresentationCall
|
||||||
@ -85,6 +88,14 @@ public final class CallController: ViewController {
|
|||||||
|
|
||||||
super.init(navigationBarPresentationData: nil)
|
super.init(navigationBarPresentationData: nil)
|
||||||
|
|
||||||
|
self._ready.set(combineLatest(queue: .mainQueue(), self.isDataReady.get(), self.isContentsReady.get())
|
||||||
|
|> map { a, b -> Bool in
|
||||||
|
return a && b
|
||||||
|
}
|
||||||
|
|> filter { $0 }
|
||||||
|
|> take(1)
|
||||||
|
|> timeout(2.0, queue: .mainQueue(), alternate: .single(true)))
|
||||||
|
|
||||||
self.isOpaqueWhenInOverlay = true
|
self.isOpaqueWhenInOverlay = true
|
||||||
|
|
||||||
self.statusBar.statusBarStyle = .White
|
self.statusBar.statusBarStyle = .White
|
||||||
@ -140,6 +151,7 @@ public final class CallController: ViewController {
|
|||||||
if self.sharedContext.immediateExperimentalUISettings.callUIV2 {
|
if self.sharedContext.immediateExperimentalUISettings.callUIV2 {
|
||||||
let displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
|
let displayNode = CallControllerNodeV2(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
self.displayNode = displayNode
|
self.displayNode = displayNode
|
||||||
|
self.isContentsReady.set(displayNode.isReady.get())
|
||||||
|
|
||||||
displayNode.restoreUIForPictureInPicture = { [weak self] completion in
|
displayNode.restoreUIForPictureInPicture = { [weak self] completion in
|
||||||
guard let self, let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture else {
|
guard let self, let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture else {
|
||||||
@ -150,6 +162,7 @@ public final class CallController: ViewController {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
self.displayNode = CallControllerNode(sharedContext: self.sharedContext, account: self.account, presentationData: self.presentationData, statusBar: self.statusBar, debugInfo: self.call.debugInfo(), shouldStayHiddenUntilConnection: !self.call.isOutgoing && self.call.isIntegratedWithCallKit, easyDebugAccess: self.easyDebugAccess, call: self.call)
|
||||||
|
self.isContentsReady.set(.single(true))
|
||||||
}
|
}
|
||||||
self.displayNodeDidLoad()
|
self.displayNodeDidLoad()
|
||||||
|
|
||||||
@ -320,7 +333,7 @@ public final class CallController: ViewController {
|
|||||||
if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] {
|
if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] {
|
||||||
strongSelf.peer = peer
|
strongSelf.peer = peer
|
||||||
strongSelf.controllerNode.updatePeer(accountPeer: accountPeer, peer: peer, hasOther: activeAccountsWithInfo.accounts.count > 1)
|
strongSelf.controllerNode.updatePeer(accountPeer: accountPeer, peer: peer, hasOther: activeAccountsWithInfo.accounts.count > 1)
|
||||||
strongSelf._ready.set(.single(true))
|
strongSelf.isDataReady.set(.single(true))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -30,6 +30,9 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
private let callScreen: PrivateCallScreen
|
private let callScreen: PrivateCallScreen
|
||||||
private var callScreenState: PrivateCallScreen.State?
|
private var callScreenState: PrivateCallScreen.State?
|
||||||
|
|
||||||
|
let isReady = Promise<Bool>()
|
||||||
|
private var didInitializeIsReady: Bool = false
|
||||||
|
|
||||||
private var callStartTimestamp: Double?
|
private var callStartTimestamp: Double?
|
||||||
|
|
||||||
private var callState: PresentationCallState?
|
private var callState: PresentationCallState?
|
||||||
@ -307,18 +310,17 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
let mappedLifecycleState: PrivateCallScreen.State.LifecycleState
|
let mappedLifecycleState: PrivateCallScreen.State.LifecycleState
|
||||||
switch callState.state {
|
switch callState.state {
|
||||||
case .waiting:
|
case .waiting:
|
||||||
mappedLifecycleState = .connecting
|
mappedLifecycleState = .requesting
|
||||||
case .ringing:
|
case .ringing:
|
||||||
mappedLifecycleState = .ringing
|
mappedLifecycleState = .ringing
|
||||||
case let .requesting(isRinging):
|
case let .requesting(isRinging):
|
||||||
if isRinging {
|
if isRinging {
|
||||||
mappedLifecycleState = .ringing
|
mappedLifecycleState = .ringing
|
||||||
} else {
|
} else {
|
||||||
mappedLifecycleState = .connecting
|
mappedLifecycleState = .requesting
|
||||||
}
|
}
|
||||||
case let .connecting(keyData):
|
case .connecting:
|
||||||
let _ = keyData
|
mappedLifecycleState = .connecting
|
||||||
mappedLifecycleState = .exchangingKeys
|
|
||||||
case let .active(startTime, signalQuality, keyData):
|
case let .active(startTime, signalQuality, keyData):
|
||||||
self.callStartTimestamp = startTime
|
self.callStartTimestamp = startTime
|
||||||
|
|
||||||
@ -332,20 +334,47 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||||
))
|
))
|
||||||
case let .reconnecting(startTime, _, keyData):
|
case let .reconnecting(startTime, _, keyData):
|
||||||
let _ = keyData
|
if self.callStartTimestamp != nil {
|
||||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||||
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
||||||
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 1.0),
|
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 0.0),
|
||||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||||
))
|
))
|
||||||
case .terminating, .terminated:
|
} else {
|
||||||
|
mappedLifecycleState = .connecting
|
||||||
|
}
|
||||||
|
case .terminating(let reason), .terminated(_, let reason, _):
|
||||||
let duration: Double
|
let duration: Double
|
||||||
if let callStartTimestamp = self.callStartTimestamp {
|
if let callStartTimestamp = self.callStartTimestamp {
|
||||||
duration = CFAbsoluteTimeGetCurrent() - callStartTimestamp
|
duration = CFAbsoluteTimeGetCurrent() - callStartTimestamp
|
||||||
} else {
|
} else {
|
||||||
duration = 0.0
|
duration = 0.0
|
||||||
}
|
}
|
||||||
mappedLifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: duration))
|
|
||||||
|
let mappedReason: PrivateCallScreen.State.TerminatedState.Reason
|
||||||
|
if let reason {
|
||||||
|
switch reason {
|
||||||
|
case let .ended(type):
|
||||||
|
switch type {
|
||||||
|
case .missed:
|
||||||
|
mappedReason = .missed
|
||||||
|
case .busy:
|
||||||
|
mappedReason = .busy
|
||||||
|
case .hungUp:
|
||||||
|
if self.callStartTimestamp != nil {
|
||||||
|
mappedReason = .hangUp
|
||||||
|
} else {
|
||||||
|
mappedReason = .declined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case .error:
|
||||||
|
mappedReason = .failed
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
mappedReason = .hangUp
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedLifecycleState = .terminated(PrivateCallScreen.State.TerminatedState(duration: duration, reason: mappedReason))
|
||||||
}
|
}
|
||||||
|
|
||||||
switch callState.state {
|
switch callState.state {
|
||||||
@ -404,6 +433,21 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
|||||||
if case let .terminated(_, _, reportRating) = callState.state {
|
if case let .terminated(_, _, reportRating) = callState.state {
|
||||||
self.callEnded?(reportRating)
|
self.callEnded?(reportRating)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !self.didInitializeIsReady {
|
||||||
|
self.didInitializeIsReady = true
|
||||||
|
|
||||||
|
if let localVideo = self.localVideo {
|
||||||
|
self.isReady.set(Signal { subscriber in
|
||||||
|
return localVideo.addOnUpdated {
|
||||||
|
subscriber.putNext(true)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
self.isReady.set(.single(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) {
|
func updatePeer(accountPeer: Peer, peer: Peer, hasOther: Bool) {
|
||||||
|
@ -10,12 +10,8 @@ import AccountContext
|
|||||||
import TelegramAudio
|
import TelegramAudio
|
||||||
import TelegramVoip
|
import TelegramVoip
|
||||||
|
|
||||||
private let sharedProviderDelegate: AnyObject? = {
|
private let sharedProviderDelegate: CallKitProviderDelegate? = {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
return CallKitProviderDelegate()
|
||||||
return CallKitProviderDelegate()
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public final class CallKitIntegration {
|
public final class CallKitIntegration {
|
||||||
@ -53,69 +49,50 @@ public final class CallKitIntegration {
|
|||||||
setCallMuted: @escaping (UUID, Bool) -> Void,
|
setCallMuted: @escaping (UUID, Bool) -> Void,
|
||||||
audioSessionActivationChanged: @escaping (Bool) -> Void
|
audioSessionActivationChanged: @escaping (Bool) -> Void
|
||||||
) {
|
) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged, hasActiveCallsValue: hasActiveCallsValue)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged, hasActiveCallsValue: hasActiveCallsValue)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private init?() {
|
private init?() {
|
||||||
if !CallKitIntegration.isAvailable {
|
if !CallKitIntegration.isAvailable {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
|
||||||
} else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func startCall(context: AccountContext, peerId: EnginePeer.Id, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) {
|
func startCall(context: AccountContext, peerId: EnginePeer.Id, phoneNumber: String?, localContactId: String?, isVideo: Bool, displayTitle: String) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle)
|
self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId)
|
||||||
self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func answerCall(uuid: UUID) {
|
func answerCall(uuid: UUID) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.answerCall(uuid: uuid)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.answerCall(uuid: uuid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func dropCall(uuid: UUID) {
|
public func dropCall(uuid: UUID) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.dropCall(uuid: uuid)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.dropCall(uuid: uuid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
public func reportIncomingCall(uuid: UUID, stableId: Int64, handle: String, phoneNumber: String?, isVideo: Bool, displayTitle: String, completion: ((NSError?) -> Void)?) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func reportOutgoingCallConnected(uuid: UUID, at date: Date) {
|
func reportOutgoingCallConnected(uuid: UUID, at date: Date) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
sharedProviderDelegate?.reportOutgoingCallConnected(uuid: uuid, at: date)
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportOutgoingCallConnected(uuid: uuid, at: date)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func donateIntent(peerId: EnginePeer.Id, displayTitle: String, localContactId: String?) {
|
private func donateIntent(peerId: EnginePeer.Id, displayTitle: String, localContactId: String?) {
|
||||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
let handle = INPersonHandle(value: "tg\(peerId.id._internalGetInt64Value())", type: .unknown)
|
||||||
let handle = INPersonHandle(value: "tg\(peerId.id._internalGetInt64Value())", type: .unknown)
|
let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())")
|
||||||
let contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())")
|
|
||||||
|
let intent = INStartAudioCallIntent(destinationType: .normal, contacts: [contact])
|
||||||
|
|
||||||
let intent = INStartAudioCallIntent(destinationType: .normal, contacts: [contact])
|
let interaction = INInteraction(intent: intent, response: nil)
|
||||||
|
interaction.direction = .outgoing
|
||||||
let interaction = INInteraction(intent: intent, response: nil)
|
interaction.donate { _ in
|
||||||
interaction.direction = .outgoing
|
|
||||||
interaction.donate { _ in
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
|
public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
|
||||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.applyVoiceChatOutputMode(outputMode: outputMode)
|
sharedProviderDelegate?.applyVoiceChatOutputMode(outputMode: outputMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,17 +55,20 @@ public final class AccountStateManager {
|
|||||||
public let callAccessHash: Int64
|
public let callAccessHash: Int64
|
||||||
public let timestamp: Int32
|
public let timestamp: Int32
|
||||||
public let peer: EnginePeer
|
public let peer: EnginePeer
|
||||||
|
public let isVideo: Bool
|
||||||
|
|
||||||
init(
|
init(
|
||||||
callId: Int64,
|
callId: Int64,
|
||||||
callAccessHash: Int64,
|
callAccessHash: Int64,
|
||||||
timestamp: Int32,
|
timestamp: Int32,
|
||||||
peer: EnginePeer
|
peer: EnginePeer,
|
||||||
|
isVideo: Bool
|
||||||
) {
|
) {
|
||||||
self.callId = callId
|
self.callId = callId
|
||||||
self.callAccessHash = callAccessHash
|
self.callAccessHash = callAccessHash
|
||||||
self.timestamp = timestamp
|
self.timestamp = timestamp
|
||||||
self.peer = peer
|
self.peer = peer
|
||||||
|
self.isVideo = isVideo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1821,7 +1824,7 @@ public final class AccountStateManager {
|
|||||||
switch update {
|
switch update {
|
||||||
case let .updatePhoneCall(phoneCall):
|
case let .updatePhoneCall(phoneCall):
|
||||||
switch phoneCall {
|
switch phoneCall {
|
||||||
case let .phoneCallRequested(_, id, accessHash, date, adminId, _, _, _):
|
case let .phoneCallRequested(flags, id, accessHash, date, adminId, _, _, _):
|
||||||
guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else {
|
guard let peer = peers.first(where: { $0.id == PeerId(namespace: Namespaces.Peer.CloudUser, id: PeerId.Id._internalFromInt64Value(adminId)) }) else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -1829,7 +1832,8 @@ public final class AccountStateManager {
|
|||||||
callId: id,
|
callId: id,
|
||||||
callAccessHash: accessHash,
|
callAccessHash: accessHash,
|
||||||
timestamp: date,
|
timestamp: date,
|
||||||
peer: EnginePeer(peer)
|
peer: EnginePeer(peer),
|
||||||
|
isVideo: (flags & (1 << 6)) != 0
|
||||||
)
|
)
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
|
@ -656,12 +656,24 @@ private final class CallSessionManagerContext {
|
|||||||
|
|
||||||
if let (id, accessHash, reason) = dropData {
|
if let (id, accessHash, reason) = dropData {
|
||||||
self.contextIdByStableId.removeValue(forKey: id)
|
self.contextIdByStableId.removeValue(forKey: id)
|
||||||
let mappedReason: CallSessionTerminationReason = .ended(.hungUp)
|
let mappedReason: CallSessionTerminationReason
|
||||||
|
switch reason {
|
||||||
|
case .abort:
|
||||||
|
mappedReason = .ended(.hungUp)
|
||||||
|
case .busy:
|
||||||
|
mappedReason = .ended(.busy)
|
||||||
|
case .disconnect:
|
||||||
|
mappedReason = .error(.disconnected)
|
||||||
|
case .hangUp:
|
||||||
|
mappedReason = .ended(.hungUp)
|
||||||
|
case .missed:
|
||||||
|
mappedReason = .ended(.missed)
|
||||||
|
}
|
||||||
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|
context.state = .dropping(reason: mappedReason, disposable: (dropCallSession(network: self.network, addUpdates: self.addUpdates, stableId: id, accessHash: accessHash, isVideo: isVideo, reason: reason)
|
||||||
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
if let context = strongSelf.contexts[internalId] {
|
if let context = strongSelf.contexts[internalId] {
|
||||||
context.state = .terminated(id: id, accessHash: accessHash, reason: .ended(.hungUp), reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
context.state = .terminated(id: id, accessHash: accessHash, reason: mappedReason, reportRating: reportRating, sendDebugLogs: sendDebugLogs)
|
||||||
/*if sendDebugLogs {
|
/*if sendDebugLogs {
|
||||||
let network = strongSelf.network
|
let network = strongSelf.network
|
||||||
let _ = (debugLog
|
let _ = (debugLog
|
||||||
|
@ -38,10 +38,12 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let content: Content
|
let content: Content
|
||||||
|
let isEnabled: Bool
|
||||||
let action: () -> Void
|
let action: () -> Void
|
||||||
|
|
||||||
init(content: Content, action: @escaping () -> Void) {
|
init(content: Content, isEnabled: Bool, action: @escaping () -> Void) {
|
||||||
self.content = content
|
self.content = content
|
||||||
|
self.isEnabled = isEnabled
|
||||||
self.action = action
|
self.action = action
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,7 +262,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
|||||||
transition.setAlpha(view: buttonView, alpha: displayClose ? 0.0 : 1.0)
|
transition.setAlpha(view: buttonView, alpha: displayClose ? 0.0 : 1.0)
|
||||||
|
|
||||||
buttonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonX, y: buttonY), size: CGSize(width: buttonSize, height: buttonSize)))
|
buttonTransition.setFrame(view: buttonView, frame: CGRect(origin: CGPoint(x: buttonX, y: buttonY), size: CGSize(width: buttonSize, height: buttonSize)))
|
||||||
buttonView.update(size: CGSize(width: buttonSize, height: buttonSize), image: image, isSelected: isActive, isDestructive: isDestructive, title: title, transition: buttonTransition)
|
buttonView.update(size: CGSize(width: buttonSize, height: buttonSize), image: image, isSelected: isActive, isDestructive: isDestructive, isEnabled: button.isEnabled, title: title, transition: buttonTransition)
|
||||||
buttonX += buttonSize + buttonSpacing
|
buttonX += buttonSize + buttonSpacing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,12 +9,14 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
|||||||
var image: UIImage?
|
var image: UIImage?
|
||||||
var isSelected: Bool
|
var isSelected: Bool
|
||||||
var isDestructive: Bool
|
var isDestructive: Bool
|
||||||
|
var isEnabled: Bool
|
||||||
|
|
||||||
init(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool) {
|
init(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool, isEnabled: Bool) {
|
||||||
self.size = size
|
self.size = size
|
||||||
self.image = image
|
self.image = image
|
||||||
self.isSelected = isSelected
|
self.isSelected = isSelected
|
||||||
self.isDestructive = isDestructive
|
self.isDestructive = isDestructive
|
||||||
|
self.isEnabled = isEnabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -93,13 +95,15 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
|||||||
self.action?()
|
self.action?()
|
||||||
}
|
}
|
||||||
|
|
||||||
func update(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool, title: String, transition: Transition) {
|
func update(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool, isEnabled: Bool, title: String, transition: Transition) {
|
||||||
let contentParams = ContentParams(size: size, image: image, isSelected: isSelected, isDestructive: isDestructive)
|
let contentParams = ContentParams(size: size, image: image, isSelected: isSelected, isDestructive: isDestructive, isEnabled: isEnabled)
|
||||||
if self.contentParams != contentParams {
|
if self.contentParams != contentParams {
|
||||||
self.contentParams = contentParams
|
self.contentParams = contentParams
|
||||||
self.updateContent(contentParams: contentParams, transition: transition)
|
self.updateContent(contentParams: contentParams, transition: transition)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.isUserInteractionEnabled = isEnabled
|
||||||
|
|
||||||
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
|
transition.setFrame(view: self.contentView, frame: CGRect(origin: CGPoint(), size: size))
|
||||||
|
|
||||||
let textSize = self.textView.update(string: title, fontSize: 13.0, fontWeight: 0.0, color: .white, constrainedWidth: 100.0, transition: .immediate)
|
let textSize = self.textView.update(string: title, fontSize: 13.0, fontWeight: 0.0, color: .white, constrainedWidth: 100.0, transition: .immediate)
|
||||||
@ -128,7 +132,7 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
|||||||
|
|
||||||
context.clip(to: imageFrame, mask: cgImage)
|
context.clip(to: imageFrame, mask: cgImage)
|
||||||
context.setBlendMode(contentParams.isSelected ? .copy : .normal)
|
context.setBlendMode(contentParams.isSelected ? .copy : .normal)
|
||||||
context.setFillColor(contentParams.isSelected ? UIColor.clear.cgColor : UIColor(white: 1.0, alpha: 1.0).cgColor)
|
context.setFillColor(contentParams.isSelected ? UIColor(white: 1.0, alpha: contentParams.isEnabled ? 0.0 : 0.5).cgColor : UIColor(white: 1.0, alpha: contentParams.isEnabled ? 1.0 : 0.5).cgColor)
|
||||||
context.fill(imageFrame)
|
context.fill(imageFrame)
|
||||||
|
|
||||||
context.resetClip()
|
context.resetClip()
|
||||||
@ -136,12 +140,8 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
if !transition.animation.isImmediate, let currentContentViewIsSelected = self.currentContentViewIsSelected, currentContentViewIsSelected != contentParams.isSelected, let previousImage = self.contentView.image, let image {
|
if !transition.animation.isImmediate, let currentContentViewIsSelected = self.currentContentViewIsSelected, currentContentViewIsSelected != contentParams.isSelected, let previousImage = self.contentView.image {
|
||||||
self.contentView.layer.mask = nil
|
self.contentView.layer.mask = nil
|
||||||
let _ = previousImage
|
|
||||||
let _ = image
|
|
||||||
let _ = currentContentViewIsSelected
|
|
||||||
|
|
||||||
let previousContentView = UIImageView(image: previousImage)
|
let previousContentView = UIImageView(image: previousImage)
|
||||||
previousContentView.frame = self.contentView.frame
|
previousContentView.frame = self.contentView.frame
|
||||||
self.addSubview(previousContentView)
|
self.addSubview(previousContentView)
|
||||||
|
@ -166,7 +166,8 @@ final class StatusView: UIView {
|
|||||||
enum WaitingState {
|
enum WaitingState {
|
||||||
case requesting
|
case requesting
|
||||||
case ringing
|
case ringing
|
||||||
case generatingKeys
|
case connecting
|
||||||
|
case reconnecting
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ActiveState: Equatable {
|
struct ActiveState: Equatable {
|
||||||
@ -299,8 +300,10 @@ final class StatusView: UIView {
|
|||||||
textString = "Requesting"
|
textString = "Requesting"
|
||||||
case .ringing:
|
case .ringing:
|
||||||
textString = "Ringing"
|
textString = "Ringing"
|
||||||
case .generatingKeys:
|
case .connecting:
|
||||||
textString = "Exchanging encryption keys"
|
textString = "Connecting"
|
||||||
|
case .reconnecting:
|
||||||
|
textString = "Reconnecting"
|
||||||
}
|
}
|
||||||
case let .active(activeState):
|
case let .active(activeState):
|
||||||
monospacedDigits = true
|
monospacedDigits = true
|
||||||
@ -310,7 +313,11 @@ final class StatusView: UIView {
|
|||||||
textString = stringForDuration(Int(duration))
|
textString = stringForDuration(Int(duration))
|
||||||
signalStrength = activeState.signalStrength
|
signalStrength = activeState.signalStrength
|
||||||
case let .terminated(terminatedState):
|
case let .terminated(terminatedState):
|
||||||
textString = stringForDuration(Int(terminatedState.duration))
|
if Int(terminatedState.duration) == 0 {
|
||||||
|
textString = " "
|
||||||
|
} else {
|
||||||
|
textString = stringForDuration(Int(terminatedState.duration))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var contentSize = CGSize()
|
var contentSize = CGSize()
|
||||||
|
@ -31,17 +31,28 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
|
|
||||||
public struct TerminatedState: Equatable {
|
public struct TerminatedState: Equatable {
|
||||||
public var duration: Double
|
public enum Reason {
|
||||||
|
case missed
|
||||||
|
case hangUp
|
||||||
|
case failed
|
||||||
|
case busy
|
||||||
|
case declined
|
||||||
|
}
|
||||||
|
|
||||||
public init(duration: Double) {
|
public var duration: Double
|
||||||
|
public var reason: Reason
|
||||||
|
|
||||||
|
public init(duration: Double, reason: Reason) {
|
||||||
self.duration = duration
|
self.duration = duration
|
||||||
|
self.reason = reason
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum LifecycleState: Equatable {
|
public enum LifecycleState: Equatable {
|
||||||
case connecting
|
case requesting
|
||||||
case ringing
|
case ringing
|
||||||
case exchangingKeys
|
case connecting
|
||||||
|
case reconnecting
|
||||||
case active(ActiveState)
|
case active(ActiveState)
|
||||||
case terminated(TerminatedState)
|
case terminated(TerminatedState)
|
||||||
}
|
}
|
||||||
@ -177,6 +188,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
private var swapLocalAndRemoteVideo: Bool = false
|
private var swapLocalAndRemoteVideo: Bool = false
|
||||||
private var isPictureInPictureActive: Bool = false
|
private var isPictureInPictureActive: Bool = false
|
||||||
|
|
||||||
|
private var hideEmojiTooltipTimer: Foundation.Timer?
|
||||||
|
private var hideControlsTimer: Foundation.Timer?
|
||||||
|
|
||||||
private var processedInitialAudioLevelBump: Bool = false
|
private var processedInitialAudioLevelBump: Bool = false
|
||||||
private var audioLevelBump: Float = 0.0
|
private var audioLevelBump: Float = 0.0
|
||||||
|
|
||||||
@ -500,8 +514,20 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
if let previousParams = self.params, case .active = params.state.lifecycleState {
|
if let previousParams = self.params, case .active = params.state.lifecycleState {
|
||||||
switch previousParams.state.lifecycleState {
|
switch previousParams.state.lifecycleState {
|
||||||
case .connecting, .exchangingKeys, .ringing:
|
case .requesting, .ringing, .connecting, .reconnecting:
|
||||||
self.displayEmojiTooltip = true
|
if self.hideEmojiTooltipTimer == nil {
|
||||||
|
self.displayEmojiTooltip = true
|
||||||
|
|
||||||
|
self.hideEmojiTooltipTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if self.displayEmojiTooltip {
|
||||||
|
self.displayEmojiTooltip = false
|
||||||
|
self.update(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -559,6 +585,18 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
let havePrimaryVideo = !activeVideoSources.isEmpty
|
let havePrimaryVideo = !activeVideoSources.isEmpty
|
||||||
|
|
||||||
|
if havePrimaryVideo && self.hideControlsTimer == nil {
|
||||||
|
self.hideControlsTimer = Foundation.Timer.scheduledTimer(withTimeInterval: 2.0, repeats: false, block: { [weak self] _ in
|
||||||
|
guard let self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !self.areControlsHidden {
|
||||||
|
self.areControlsHidden = true
|
||||||
|
self.update(transition: .spring(duration: 0.4))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
if havePrimaryVideo, let pipVideoCallViewController = self.pipVideoCallViewController as? AVPictureInPictureVideoCallViewController {
|
if havePrimaryVideo, let pipVideoCallViewController = self.pipVideoCallViewController as? AVPictureInPictureVideoCallViewController {
|
||||||
if self.pipController == nil {
|
if self.pipController == nil {
|
||||||
@ -607,11 +645,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
let backgroundStateIndex: Int
|
let backgroundStateIndex: Int
|
||||||
switch params.state.lifecycleState {
|
switch params.state.lifecycleState {
|
||||||
case .connecting:
|
case .requesting, .ringing, .connecting, .reconnecting:
|
||||||
backgroundStateIndex = 0
|
|
||||||
case .ringing:
|
|
||||||
backgroundStateIndex = 0
|
|
||||||
case .exchangingKeys:
|
|
||||||
backgroundStateIndex = 0
|
backgroundStateIndex = 0
|
||||||
case let .active(activeState):
|
case let .active(activeState):
|
||||||
if activeState.signalInfo.quality <= 0.2 {
|
if activeState.signalInfo.quality <= 0.2 {
|
||||||
@ -626,20 +660,36 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size))
|
transition.setFrame(view: self.buttonGroupView, frame: CGRect(origin: CGPoint(), size: params.size))
|
||||||
|
|
||||||
|
var isVideoButtonEnabled = false
|
||||||
|
switch params.state.lifecycleState {
|
||||||
|
case .active, .reconnecting:
|
||||||
|
isVideoButtonEnabled = true
|
||||||
|
default:
|
||||||
|
isVideoButtonEnabled = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var isTerminated = false
|
||||||
|
switch params.state.lifecycleState {
|
||||||
|
case .terminated:
|
||||||
|
isTerminated = true
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
var buttons: [ButtonGroupView.Button] = [
|
var buttons: [ButtonGroupView.Button] = [
|
||||||
ButtonGroupView.Button(content: .video(isActive: params.state.localVideo != nil), action: { [weak self] in
|
ButtonGroupView.Button(content: .video(isActive: params.state.localVideo != nil), isEnabled: isVideoButtonEnabled && !isTerminated, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.videoAction?()
|
self.videoAction?()
|
||||||
}),
|
}),
|
||||||
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isLocalAudioMuted), action: { [weak self] in
|
ButtonGroupView.Button(content: .microphone(isMuted: params.state.isLocalAudioMuted), isEnabled: !isTerminated, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.microhoneMuteAction?()
|
self.microhoneMuteAction?()
|
||||||
}),
|
}),
|
||||||
ButtonGroupView.Button(content: .end, action: { [weak self] in
|
ButtonGroupView.Button(content: .end, isEnabled: !isTerminated, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -647,14 +697,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
})
|
})
|
||||||
]
|
]
|
||||||
if self.activeLocalVideoSource != nil {
|
if self.activeLocalVideoSource != nil {
|
||||||
buttons.insert(ButtonGroupView.Button(content: .flipCamera, action: { [weak self] in
|
buttons.insert(ButtonGroupView.Button(content: .flipCamera, isEnabled: !isTerminated, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
self.flipCameraAction?()
|
self.flipCameraAction?()
|
||||||
}), at: 0)
|
}), at: 0)
|
||||||
} else {
|
} else {
|
||||||
buttons.insert(ButtonGroupView.Button(content: .speaker(isActive: params.state.audioOutput != .internalSpeaker), action: { [weak self] in
|
buttons.insert(ButtonGroupView.Button(content: .speaker(isActive: params.state.audioOutput != .internalSpeaker), isEnabled: !isTerminated, action: { [weak self] in
|
||||||
guard let self else {
|
guard let self else {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -663,23 +713,27 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
}
|
}
|
||||||
|
|
||||||
var notices: [ButtonGroupView.Notice] = []
|
var notices: [ButtonGroupView.Notice] = []
|
||||||
if params.state.isLocalAudioMuted {
|
if !isTerminated {
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
|
if params.state.isLocalAudioMuted {
|
||||||
}
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(0 as Int), text: "Your microphone is turned off"))
|
||||||
if params.state.isRemoteAudioMuted {
|
}
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "\(params.state.shortName)'s microphone is turned off"))
|
if params.state.isRemoteAudioMuted {
|
||||||
}
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(1 as Int), text: "\(params.state.shortName)'s microphone is turned off"))
|
||||||
if params.state.remoteVideo != nil && params.state.localVideo == nil {
|
}
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "Your camera is turned off"))
|
if params.state.remoteVideo != nil && params.state.localVideo == nil {
|
||||||
}
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(2 as Int), text: "Your camera is turned off"))
|
||||||
if params.state.isRemoteBatteryLow {
|
}
|
||||||
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), text: "\(params.state.shortName)'s battery is low"))
|
if params.state.isRemoteBatteryLow {
|
||||||
|
notices.append(ButtonGroupView.Notice(id: AnyHashable(3 as Int), text: "\(params.state.shortName)'s battery is low"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var displayClose = false
|
/*var displayClose = false
|
||||||
if case .terminated = params.state.lifecycleState {
|
if case .terminated = params.state.lifecycleState {
|
||||||
displayClose = true
|
displayClose = true
|
||||||
}
|
}*/
|
||||||
|
let displayClose = false
|
||||||
|
|
||||||
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition)
|
let contentBottomInset = self.buttonGroupView.update(size: params.size, insets: params.insets, minWidth: wideContentWidth, controlsHidden: currentAreControlsHidden, displayClose: displayClose, buttons: buttons, notices: notices, transition: transition)
|
||||||
|
|
||||||
var expandedEmojiKeyRect: CGRect?
|
var expandedEmojiKeyRect: CGRect?
|
||||||
@ -1105,9 +1159,21 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
let titleString: String
|
let titleString: String
|
||||||
switch params.state.lifecycleState {
|
switch params.state.lifecycleState {
|
||||||
case .terminated:
|
case let .terminated(terminatedState):
|
||||||
self.titleView.contentMode = .center
|
self.titleView.contentMode = .center
|
||||||
titleString = "Call Ended"
|
|
||||||
|
switch terminatedState.reason {
|
||||||
|
case .busy:
|
||||||
|
titleString = "Line Busy"
|
||||||
|
case .declined:
|
||||||
|
titleString = "Call Declined"
|
||||||
|
case .failed:
|
||||||
|
titleString = "Call Failed"
|
||||||
|
case .hangUp:
|
||||||
|
titleString = "Call Ended"
|
||||||
|
case .missed:
|
||||||
|
titleString = "Call Missed"
|
||||||
|
}
|
||||||
genericAlphaTransition.setScale(layer: self.blobLayer, scale: 0.3)
|
genericAlphaTransition.setScale(layer: self.blobLayer, scale: 0.3)
|
||||||
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
genericAlphaTransition.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
||||||
self.canAnimateAudioLevel = false
|
self.canAnimateAudioLevel = false
|
||||||
@ -1133,12 +1199,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
|||||||
|
|
||||||
let statusState: StatusView.State
|
let statusState: StatusView.State
|
||||||
switch params.state.lifecycleState {
|
switch params.state.lifecycleState {
|
||||||
case .connecting:
|
case .requesting:
|
||||||
statusState = .waiting(.requesting)
|
statusState = .waiting(.requesting)
|
||||||
|
case .connecting:
|
||||||
|
statusState = .waiting(.connecting)
|
||||||
|
case .reconnecting:
|
||||||
|
statusState = .waiting(.reconnecting)
|
||||||
case .ringing:
|
case .ringing:
|
||||||
statusState = .waiting(.ringing)
|
statusState = .waiting(.ringing)
|
||||||
case .exchangingKeys:
|
|
||||||
statusState = .waiting(.generatingKeys)
|
|
||||||
case let .active(activeState):
|
case let .active(activeState):
|
||||||
statusState = .active(StatusView.ActiveState(startTimestamp: activeState.startTime, signalStrength: activeState.signalInfo.quality))
|
statusState = .active(StatusView.ActiveState(startTimestamp: activeState.startTime, signalStrength: activeState.signalInfo.quality))
|
||||||
|
|
||||||
|
@ -2030,7 +2030,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
|||||||
stableId: callUpdate.callId,
|
stableId: callUpdate.callId,
|
||||||
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
||||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||||
isVideo: false,
|
isVideo: callUpdate.isVideo,
|
||||||
displayTitle: callUpdate.peer.debugDisplayTitle,
|
displayTitle: callUpdate.peer.debugDisplayTitle,
|
||||||
completion: { error in
|
completion: { error in
|
||||||
if let error = error {
|
if let error = error {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user