mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
758016c638
@ -1,15 +1,21 @@
|
||||
#import <AppBundle/AppBundle.h>
|
||||
|
||||
NSBundle * _Nonnull getAppBundle() {
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"]) {
|
||||
bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
|
||||
} else if ([[bundle.bundleURL pathExtension] isEqualToString:@"framework"]) {
|
||||
bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
|
||||
} else if ([[bundle.bundleURL pathExtension] isEqualToString:@"Frameworks"]) {
|
||||
bundle = [NSBundle bundleWithURL:[bundle.bundleURL URLByDeletingLastPathComponent]];
|
||||
}
|
||||
return bundle;
|
||||
static NSBundle *appBundle = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
NSBundle *bundle = [NSBundle mainBundle];
|
||||
if ([[bundle.bundleURL pathExtension] isEqualToString:@"appex"]) {
|
||||
bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
|
||||
} else if ([[bundle.bundleURL pathExtension] isEqualToString:@"framework"]) {
|
||||
bundle = [NSBundle bundleWithURL:[[bundle.bundleURL URLByDeletingLastPathComponent] URLByDeletingLastPathComponent]];
|
||||
} else if ([[bundle.bundleURL pathExtension] isEqualToString:@"Frameworks"]) {
|
||||
bundle = [NSBundle bundleWithURL:[bundle.bundleURL URLByDeletingLastPathComponent]];
|
||||
}
|
||||
appBundle = bundle;
|
||||
});
|
||||
|
||||
return appBundle;
|
||||
}
|
||||
|
||||
@implementation UIImage (AppBundle)
|
||||
|
@ -1690,7 +1690,7 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
})
|
||||
|
||||
let foundThreads: Signal<[EngineChatList.Item], NoError>
|
||||
if case .forum = location, (key == .topics || key == .chats) {
|
||||
if case let .forum(peerId) = location, (key == .topics || key == .chats) {
|
||||
foundThreads = chatListViewForLocation(chatListLocation: location, location: .initial(count: 1000, filter: nil), account: context.account)
|
||||
|> map { view -> [EngineChatList.Item] in
|
||||
var filteredItems: [EngineChatList.Item] = []
|
||||
@ -1708,6 +1708,24 @@ final class ChatListSearchListPaneNode: ASDisplayNode, ChatListSearchPaneNode {
|
||||
|
||||
return filteredItems
|
||||
}
|
||||
|> mapToSignal { local -> Signal<[EngineChatList.Item], NoError> in
|
||||
return .single(local)
|
||||
|> then(context.engine.messages.searchForumTopics(peerId: peerId, query: finalQuery)
|
||||
|> map { remoteResult in
|
||||
var mergedResult = local
|
||||
for item in remoteResult {
|
||||
guard case let .forum(threadId) = item.id else {
|
||||
continue
|
||||
}
|
||||
if !mergedResult.contains(where: { $0.id == .forum(threadId) }) {
|
||||
mergedResult.append(item)
|
||||
}
|
||||
}
|
||||
|
||||
return mergedResult
|
||||
})
|
||||
}
|
||||
|> distinctUntilChanged
|
||||
} else {
|
||||
foundThreads = .single([])
|
||||
}
|
||||
|
@ -333,10 +333,10 @@ public final class ManagedAudioSession: NSObject {
|
||||
var headphonesAreActive = false
|
||||
loop: for currentOutput in audioSession.currentRoute.outputs {
|
||||
switch currentOutput.portType {
|
||||
case .headphones, .bluetoothA2DP, .bluetoothHFP:
|
||||
case .headphones, .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
|
||||
headphonesAreActive = true
|
||||
hasHeadphones = true
|
||||
hasBluetoothHeadphones = [.bluetoothA2DP, .bluetoothHFP].contains(currentOutput.portType)
|
||||
hasBluetoothHeadphones = [.bluetoothA2DP, .bluetoothHFP, .bluetoothLE].contains(currentOutput.portType)
|
||||
activeOutput = .headphones
|
||||
break loop
|
||||
default:
|
||||
@ -730,7 +730,7 @@ public final class ManagedAudioSession: NSObject {
|
||||
let route = AVAudioSession.sharedInstance().currentRoute
|
||||
//managedAudioSessionLog("\(route)")
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -977,7 +977,7 @@ public final class ManagedAudioSession: NSObject {
|
||||
} else {
|
||||
loop: for route in routes {
|
||||
switch route.portType {
|
||||
case .headphones, .bluetoothA2DP, .bluetoothHFP:
|
||||
case .headphones, .bluetoothA2DP, .bluetoothHFP, .bluetoothLE:
|
||||
let _ = try? AVAudioSession.sharedInstance().setPreferredInput(route)
|
||||
alreadySet = true
|
||||
break loop
|
||||
|
@ -50,6 +50,9 @@ public final class CallController: ViewController {
|
||||
return self._ready
|
||||
}
|
||||
|
||||
private let isDataReady = Promise<Bool>(false)
|
||||
private let isContentsReady = Promise<Bool>(false)
|
||||
|
||||
private let sharedContext: SharedAccountContext
|
||||
private let account: Account
|
||||
public let call: PresentationCall
|
||||
@ -85,6 +88,14 @@ public final class CallController: ViewController {
|
||||
|
||||
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.statusBar.statusBarStyle = .White
|
||||
@ -140,6 +151,7 @@ public final class CallController: ViewController {
|
||||
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)
|
||||
self.displayNode = displayNode
|
||||
self.isContentsReady.set(displayNode.isReady.get())
|
||||
|
||||
displayNode.restoreUIForPictureInPicture = { [weak self] completion in
|
||||
guard let self, let restoreUIForPictureInPicture = self.restoreUIForPictureInPicture else {
|
||||
@ -150,6 +162,7 @@ public final class CallController: ViewController {
|
||||
}
|
||||
} 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.isContentsReady.set(.single(true))
|
||||
}
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
@ -320,7 +333,7 @@ public final class CallController: ViewController {
|
||||
if let accountPeer = accountView.peers[accountView.peerId], let peer = view.peers[view.peerId] {
|
||||
strongSelf.peer = peer
|
||||
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 var callScreenState: PrivateCallScreen.State?
|
||||
|
||||
let isReady = Promise<Bool>()
|
||||
private var didInitializeIsReady: Bool = false
|
||||
|
||||
private var callStartTimestamp: Double?
|
||||
|
||||
private var callState: PresentationCallState?
|
||||
@ -307,18 +310,17 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
let mappedLifecycleState: PrivateCallScreen.State.LifecycleState
|
||||
switch callState.state {
|
||||
case .waiting:
|
||||
mappedLifecycleState = .connecting
|
||||
mappedLifecycleState = .requesting
|
||||
case .ringing:
|
||||
mappedLifecycleState = .ringing
|
||||
case let .requesting(isRinging):
|
||||
if isRinging {
|
||||
mappedLifecycleState = .ringing
|
||||
} else {
|
||||
mappedLifecycleState = .connecting
|
||||
mappedLifecycleState = .requesting
|
||||
}
|
||||
case let .connecting(keyData):
|
||||
let _ = keyData
|
||||
mappedLifecycleState = .exchangingKeys
|
||||
case .connecting:
|
||||
mappedLifecycleState = .connecting
|
||||
case let .active(startTime, signalQuality, keyData):
|
||||
self.callStartTimestamp = startTime
|
||||
|
||||
@ -332,20 +334,47 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||
))
|
||||
case let .reconnecting(startTime, _, keyData):
|
||||
let _ = keyData
|
||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
||||
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 1.0),
|
||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||
))
|
||||
case .terminating, .terminated:
|
||||
if self.callStartTimestamp != nil {
|
||||
mappedLifecycleState = .active(PrivateCallScreen.State.ActiveState(
|
||||
startTime: startTime + kCFAbsoluteTimeIntervalSince1970,
|
||||
signalInfo: PrivateCallScreen.State.SignalInfo(quality: 0.0),
|
||||
emojiKey: self.resolvedEmojiKey(data: keyData)
|
||||
))
|
||||
} else {
|
||||
mappedLifecycleState = .connecting
|
||||
}
|
||||
case .terminating(let reason), .terminated(_, let reason, _):
|
||||
let duration: Double
|
||||
if let callStartTimestamp = self.callStartTimestamp {
|
||||
duration = CFAbsoluteTimeGetCurrent() - callStartTimestamp
|
||||
} else {
|
||||
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 {
|
||||
@ -404,6 +433,21 @@ final class CallControllerNodeV2: ViewControllerTracingNode, CallControllerNodeP
|
||||
if case let .terminated(_, _, reportRating) = callState.state {
|
||||
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) {
|
||||
|
@ -10,12 +10,8 @@ import AccountContext
|
||||
import TelegramAudio
|
||||
import TelegramVoip
|
||||
|
||||
private let sharedProviderDelegate: AnyObject? = {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
return CallKitProviderDelegate()
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
private let sharedProviderDelegate: CallKitProviderDelegate? = {
|
||||
return CallKitProviderDelegate()
|
||||
}()
|
||||
|
||||
public final class CallKitIntegration {
|
||||
@ -53,69 +49,50 @@ public final class CallKitIntegration {
|
||||
setCallMuted: @escaping (UUID, Bool) -> Void,
|
||||
audioSessionActivationChanged: @escaping (Bool) -> Void
|
||||
) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged, hasActiveCallsValue: hasActiveCallsValue)
|
||||
}
|
||||
sharedProviderDelegate?.setup(audioSessionActivePromise: self.audioSessionActivePromise, startCall: startCall, answerCall: answerCall, endCall: endCall, setCallMuted: setCallMuted, audioSessionActivationChanged: audioSessionActivationChanged, hasActiveCallsValue: hasActiveCallsValue)
|
||||
}
|
||||
|
||||
private init?() {
|
||||
if !CallKitIntegration.isAvailable {
|
||||
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) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle)
|
||||
self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId)
|
||||
}
|
||||
sharedProviderDelegate?.startCall(context: context, peerId: peerId, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle)
|
||||
self.donateIntent(peerId: peerId, displayTitle: displayTitle, localContactId: localContactId)
|
||||
}
|
||||
|
||||
func answerCall(uuid: UUID) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.answerCall(uuid: uuid)
|
||||
}
|
||||
sharedProviderDelegate?.answerCall(uuid: uuid)
|
||||
}
|
||||
|
||||
public func dropCall(uuid: UUID) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.dropCall(uuid: uuid)
|
||||
}
|
||||
sharedProviderDelegate?.dropCall(uuid: uuid)
|
||||
}
|
||||
|
||||
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 as? CallKitProviderDelegate)?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||
}
|
||||
sharedProviderDelegate?.reportIncomingCall(uuid: uuid, stableId: stableId, handle: handle, phoneNumber: phoneNumber, isVideo: isVideo, displayTitle: displayTitle, completion: completion)
|
||||
}
|
||||
|
||||
func reportOutgoingCallConnected(uuid: UUID, at date: Date) {
|
||||
if #available(iOSApplicationExtension 10.0, iOS 10.0, *) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.reportOutgoingCallConnected(uuid: uuid, at: date)
|
||||
}
|
||||
sharedProviderDelegate?.reportOutgoingCallConnected(uuid: uuid, at: date)
|
||||
}
|
||||
|
||||
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 contact = INPerson(personHandle: handle, nameComponents: nil, displayName: displayTitle, image: nil, contactIdentifier: localContactId, customIdentifier: "tg\(peerId.id._internalGetInt64Value())")
|
||||
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 intent = INStartAudioCallIntent(destinationType: .normal, contacts: [contact])
|
||||
|
||||
let intent = INStartAudioCallIntent(destinationType: .normal, contacts: [contact])
|
||||
|
||||
let interaction = INInteraction(intent: intent, response: nil)
|
||||
interaction.direction = .outgoing
|
||||
interaction.donate { _ in
|
||||
}
|
||||
let interaction = INInteraction(intent: intent, response: nil)
|
||||
interaction.direction = .outgoing
|
||||
interaction.donate { _ in
|
||||
}
|
||||
}
|
||||
|
||||
public func applyVoiceChatOutputMode(outputMode: AudioSessionOutputMode) {
|
||||
(sharedProviderDelegate as? CallKitProviderDelegate)?.applyVoiceChatOutputMode(outputMode: outputMode)
|
||||
sharedProviderDelegate?.applyVoiceChatOutputMode(outputMode: outputMode)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -570,7 +570,7 @@ enum LoadMessageHistoryThreadsError {
|
||||
case generic
|
||||
}
|
||||
|
||||
func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, offsetIndex: StoredPeerThreadCombinedState.Index?, limit: Int) -> Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> {
|
||||
func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Postbox, network: Network, peerId: PeerId, query: String?, offsetIndex: StoredPeerThreadCombinedState.Index?, limit: Int) -> Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> {
|
||||
let signal: Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> = postbox.transaction { transaction -> Api.InputChannel? in
|
||||
guard let channel = transaction.getPeer(peerId) as? TelegramChannel else {
|
||||
return nil
|
||||
@ -585,7 +585,12 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
|
||||
guard let inputChannel = inputChannel else {
|
||||
return .fail(.generic)
|
||||
}
|
||||
let flags: Int32 = 0
|
||||
var flags: Int32 = 0
|
||||
|
||||
if query != nil {
|
||||
flags |= 1 << 0
|
||||
}
|
||||
|
||||
var offsetDate: Int32 = 0
|
||||
var offsetId: Int32 = 0
|
||||
var offsetTopic: Int32 = 0
|
||||
@ -597,7 +602,7 @@ func _internal_requestMessageHistoryThreads(accountPeerId: PeerId, postbox: Post
|
||||
let signal: Signal<LoadMessageHistoryThreadsResult, LoadMessageHistoryThreadsError> = network.request(Api.functions.channels.getForumTopics(
|
||||
flags: flags,
|
||||
channel: inputChannel,
|
||||
q: nil,
|
||||
q: query,
|
||||
offsetDate: offsetDate,
|
||||
offsetId: offsetId,
|
||||
offsetTopic: offsetTopic,
|
||||
@ -839,6 +844,59 @@ func _internal_forumChannelTopicNotificationExceptions(account: Account, id: Eng
|
||||
}
|
||||
}
|
||||
|
||||
public func _internal_searchForumTopics(account: Account, peerId: EnginePeer.Id, query: String) -> Signal<[EngineChatList.Item], NoError> {
|
||||
return _internal_requestMessageHistoryThreads(accountPeerId: account.peerId, postbox: account.postbox, network: account.network, peerId: peerId, query: query, offsetIndex: nil, limit: 100)
|
||||
|> map(Optional.init)
|
||||
|> `catch` { _ -> Signal<LoadMessageHistoryThreadsResult?, NoError> in
|
||||
return .single(nil)
|
||||
}
|
||||
|> mapToSignal { result -> Signal<[EngineChatList.Item], NoError> in
|
||||
guard let result else {
|
||||
return .single([])
|
||||
}
|
||||
return account.postbox.transaction { transcation -> [EngineChatList.Item] in
|
||||
guard let peer = transcation.getPeer(peerId) else {
|
||||
return []
|
||||
}
|
||||
|
||||
var items: [EngineChatList.Item] = []
|
||||
for item in result.items {
|
||||
guard let index = item.index else {
|
||||
continue
|
||||
}
|
||||
items.append(EngineChatList.Item(
|
||||
id: .forum(item.threadId),
|
||||
index: .forum(pinnedIndex: .none, timestamp: index.timestamp, threadId: index.threadId, namespace: Namespaces.Message.Cloud, id: index.messageId),
|
||||
messages: [],
|
||||
readCounters: nil,
|
||||
isMuted: false,
|
||||
draft: nil,
|
||||
threadData: item.data,
|
||||
renderedPeer: EngineRenderedPeer(peer: EnginePeer(peer)),
|
||||
presence: nil,
|
||||
hasUnseenMentions: false,
|
||||
hasUnseenReactions: false,
|
||||
forumTopicData: EngineChatList.ForumTopicData(
|
||||
id: item.threadId,
|
||||
title: item.data.info.title,
|
||||
iconFileId: item.data.info.icon,
|
||||
iconColor: item.data.info.iconColor,
|
||||
maxOutgoingReadMessageId: EngineMessage.Id(peerId: peerId, namespace: Namespaces.Message.Cloud, id: item.data.maxOutgoingReadId),
|
||||
isUnread: false
|
||||
),
|
||||
topForumTopicItems: [],
|
||||
hasFailed: false,
|
||||
isContact: false,
|
||||
autoremoveTimeout: nil,
|
||||
storyStats: nil
|
||||
))
|
||||
}
|
||||
|
||||
return items
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final class ForumChannelTopics {
|
||||
private final class Impl {
|
||||
private let queue: Queue
|
||||
@ -859,8 +917,6 @@ public final class ForumChannelTopics {
|
||||
self.account = account
|
||||
self.peerId = peerId
|
||||
|
||||
//let _ = _internal_loadMessageHistoryThreads(account: self.account, peerId: peerId, offsetIndex: nil, limit: 100).start()
|
||||
|
||||
self.updateDisposable.set(account.viewTracker.polledChannel(peerId: peerId).start())
|
||||
}
|
||||
|
||||
|
@ -2833,7 +2833,7 @@ func resetChannels(accountPeerId: PeerId, postbox: Postbox, network: Network, pe
|
||||
|
||||
var resetTopicsSignals: [Signal<StateResetForumTopics, NoError>] = []
|
||||
for resetForumTopicPeerId in resetForumTopics {
|
||||
resetTopicsSignals.append(_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: resetForumTopicPeerId, offsetIndex: nil, limit: 20)
|
||||
resetTopicsSignals.append(_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: resetForumTopicPeerId, query: nil, offsetIndex: nil, limit: 20)
|
||||
|> map(StateResetForumTopics.result)
|
||||
|> `catch` { _ -> Signal<StateResetForumTopics, NoError> in
|
||||
return .single(.error(resetForumTopicPeerId))
|
||||
@ -3113,7 +3113,7 @@ private func pollChannel(accountPeerId: PeerId, postbox: Postbox, network: Netwo
|
||||
|
||||
var resetTopicsSignals: [Signal<StateResetForumTopics, NoError>] = []
|
||||
for resetForumTopicPeerId in resetForumTopics {
|
||||
resetTopicsSignals.append(_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: resetForumTopicPeerId, offsetIndex: nil, limit: 20)
|
||||
resetTopicsSignals.append(_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: resetForumTopicPeerId, query: nil, offsetIndex: nil, limit: 20)
|
||||
|> map(StateResetForumTopics.result)
|
||||
|> `catch` { _ -> Signal<StateResetForumTopics, NoError> in
|
||||
return .single(.error(resetForumTopicPeerId))
|
||||
|
@ -55,17 +55,20 @@ public final class AccountStateManager {
|
||||
public let callAccessHash: Int64
|
||||
public let timestamp: Int32
|
||||
public let peer: EnginePeer
|
||||
public let isVideo: Bool
|
||||
|
||||
init(
|
||||
callId: Int64,
|
||||
callAccessHash: Int64,
|
||||
timestamp: Int32,
|
||||
peer: EnginePeer
|
||||
peer: EnginePeer,
|
||||
isVideo: Bool
|
||||
) {
|
||||
self.callId = callId
|
||||
self.callAccessHash = callAccessHash
|
||||
self.timestamp = timestamp
|
||||
self.peer = peer
|
||||
self.isVideo = isVideo
|
||||
}
|
||||
}
|
||||
|
||||
@ -1821,7 +1824,7 @@ public final class AccountStateManager {
|
||||
switch update {
|
||||
case let .updatePhoneCall(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 {
|
||||
return nil
|
||||
}
|
||||
@ -1829,7 +1832,8 @@ public final class AccountStateManager {
|
||||
callId: id,
|
||||
callAccessHash: accessHash,
|
||||
timestamp: date,
|
||||
peer: EnginePeer(peer)
|
||||
peer: EnginePeer(peer),
|
||||
isVideo: (flags & (1 << 6)) != 0
|
||||
)
|
||||
default:
|
||||
break
|
||||
|
@ -656,12 +656,24 @@ private final class CallSessionManagerContext {
|
||||
|
||||
if let (id, accessHash, reason) = dropData {
|
||||
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)
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] reportRating, sendDebugLogs in
|
||||
if let strongSelf = self {
|
||||
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 {
|
||||
let network = strongSelf.network
|
||||
let _ = (debugLog
|
||||
|
@ -143,7 +143,7 @@ func managedForumTopicListHoles(network: Network, postbox: Postbox, accountPeerI
|
||||
}
|
||||
|
||||
for (entry, disposable) in added {
|
||||
disposable.set((_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: entry.peerId, offsetIndex: entry.index, limit: 100)
|
||||
disposable.set((_internal_requestMessageHistoryThreads(accountPeerId: accountPeerId, postbox: postbox, network: network, peerId: entry.peerId, query: nil, offsetIndex: entry.index, limit: 100)
|
||||
|> mapToSignal { result -> Signal<Never, LoadMessageHistoryThreadsError> in
|
||||
return postbox.transaction { transaction in
|
||||
return applyLoadMessageHistoryThreadsResults(accountPeerId: accountPeerId, transaction: transaction, results: [result])
|
||||
|
@ -637,6 +637,10 @@ public extension TelegramEngine {
|
||||
|> ignoreValues
|
||||
}
|
||||
|
||||
public func searchForumTopics(peerId: EnginePeer.Id, query: String) -> Signal<[EngineChatList.Item], NoError> {
|
||||
return _internal_searchForumTopics(account: self.account, peerId: peerId, query: query)
|
||||
}
|
||||
|
||||
public func debugAddHoles() -> Signal<Never, NoError> {
|
||||
return self.account.postbox.transaction { transaction -> Void in
|
||||
transaction.addHolesEverywhere(peerNamespaces: [Namespaces.Peer.CloudUser, Namespaces.Peer.CloudGroup, Namespaces.Peer.CloudChannel], holeNamespace: Namespaces.Message.Cloud)
|
||||
|
@ -38,10 +38,12 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
}
|
||||
|
||||
let content: Content
|
||||
let isEnabled: Bool
|
||||
let action: () -> Void
|
||||
|
||||
init(content: Content, action: @escaping () -> Void) {
|
||||
init(content: Content, isEnabled: Bool, action: @escaping () -> Void) {
|
||||
self.content = content
|
||||
self.isEnabled = isEnabled
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
@ -260,7 +262,7 @@ final class ButtonGroupView: OverlayMaskContainerView {
|
||||
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)))
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -9,12 +9,14 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
||||
var image: UIImage?
|
||||
var isSelected: 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.image = image
|
||||
self.isSelected = isSelected
|
||||
self.isDestructive = isDestructive
|
||||
self.isEnabled = isEnabled
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,13 +95,15 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
||||
self.action?()
|
||||
}
|
||||
|
||||
func update(size: CGSize, image: UIImage?, isSelected: Bool, isDestructive: Bool, title: String, transition: Transition) {
|
||||
let contentParams = ContentParams(size: size, image: image, isSelected: isSelected, isDestructive: isDestructive)
|
||||
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, isEnabled: isEnabled)
|
||||
if self.contentParams != contentParams {
|
||||
self.contentParams = contentParams
|
||||
self.updateContent(contentParams: contentParams, transition: transition)
|
||||
}
|
||||
|
||||
self.isUserInteractionEnabled = isEnabled
|
||||
|
||||
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)
|
||||
@ -128,7 +132,7 @@ final class ContentOverlayButton: HighlightTrackingButton, OverlayMaskContainerV
|
||||
|
||||
context.clip(to: imageFrame, mask: cgImage)
|
||||
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.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
|
||||
let _ = previousImage
|
||||
let _ = image
|
||||
let _ = currentContentViewIsSelected
|
||||
|
||||
let previousContentView = UIImageView(image: previousImage)
|
||||
previousContentView.frame = self.contentView.frame
|
||||
self.addSubview(previousContentView)
|
||||
|
@ -166,7 +166,8 @@ final class StatusView: UIView {
|
||||
enum WaitingState {
|
||||
case requesting
|
||||
case ringing
|
||||
case generatingKeys
|
||||
case connecting
|
||||
case reconnecting
|
||||
}
|
||||
|
||||
struct ActiveState: Equatable {
|
||||
@ -299,8 +300,10 @@ final class StatusView: UIView {
|
||||
textString = "Requesting"
|
||||
case .ringing:
|
||||
textString = "Ringing"
|
||||
case .generatingKeys:
|
||||
textString = "Exchanging encryption keys"
|
||||
case .connecting:
|
||||
textString = "Connecting"
|
||||
case .reconnecting:
|
||||
textString = "Reconnecting"
|
||||
}
|
||||
case let .active(activeState):
|
||||
monospacedDigits = true
|
||||
@ -310,7 +313,11 @@ final class StatusView: UIView {
|
||||
textString = stringForDuration(Int(duration))
|
||||
signalStrength = activeState.signalStrength
|
||||
case let .terminated(terminatedState):
|
||||
textString = stringForDuration(Int(terminatedState.duration))
|
||||
if Int(terminatedState.duration) == 0 {
|
||||
textString = " "
|
||||
} else {
|
||||
textString = stringForDuration(Int(terminatedState.duration))
|
||||
}
|
||||
}
|
||||
|
||||
var contentSize = CGSize()
|
||||
|
@ -31,17 +31,28 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}
|
||||
|
||||
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.reason = reason
|
||||
}
|
||||
}
|
||||
|
||||
public enum LifecycleState: Equatable {
|
||||
case connecting
|
||||
case requesting
|
||||
case ringing
|
||||
case exchangingKeys
|
||||
case connecting
|
||||
case reconnecting
|
||||
case active(ActiveState)
|
||||
case terminated(TerminatedState)
|
||||
}
|
||||
@ -177,6 +188,9 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
private var swapLocalAndRemoteVideo: Bool = false
|
||||
private var isPictureInPictureActive: Bool = false
|
||||
|
||||
private var hideEmojiTooltipTimer: Foundation.Timer?
|
||||
private var hideControlsTimer: Foundation.Timer?
|
||||
|
||||
private var processedInitialAudioLevelBump: Bool = false
|
||||
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 {
|
||||
switch previousParams.state.lifecycleState {
|
||||
case .connecting, .exchangingKeys, .ringing:
|
||||
self.displayEmojiTooltip = true
|
||||
case .requesting, .ringing, .connecting, .reconnecting:
|
||||
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:
|
||||
break
|
||||
}
|
||||
@ -559,6 +585,18 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}
|
||||
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 havePrimaryVideo, let pipVideoCallViewController = self.pipVideoCallViewController as? AVPictureInPictureVideoCallViewController {
|
||||
if self.pipController == nil {
|
||||
@ -607,11 +645,7 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
|
||||
let backgroundStateIndex: Int
|
||||
switch params.state.lifecycleState {
|
||||
case .connecting:
|
||||
backgroundStateIndex = 0
|
||||
case .ringing:
|
||||
backgroundStateIndex = 0
|
||||
case .exchangingKeys:
|
||||
case .requesting, .ringing, .connecting, .reconnecting:
|
||||
backgroundStateIndex = 0
|
||||
case let .active(activeState):
|
||||
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))
|
||||
|
||||
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] = [
|
||||
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 {
|
||||
return
|
||||
}
|
||||
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 {
|
||||
return
|
||||
}
|
||||
self.microhoneMuteAction?()
|
||||
}),
|
||||
ButtonGroupView.Button(content: .end, action: { [weak self] in
|
||||
ButtonGroupView.Button(content: .end, isEnabled: !isTerminated, action: { [weak self] in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
@ -647,14 +697,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
})
|
||||
]
|
||||
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 {
|
||||
return
|
||||
}
|
||||
self.flipCameraAction?()
|
||||
}), at: 0)
|
||||
} 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 {
|
||||
return
|
||||
}
|
||||
@ -663,23 +713,27 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
}
|
||||
|
||||
var notices: [ButtonGroupView.Notice] = []
|
||||
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.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 !isTerminated {
|
||||
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.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"))
|
||||
}
|
||||
}
|
||||
|
||||
var displayClose = false
|
||||
/*var displayClose = false
|
||||
if case .terminated = params.state.lifecycleState {
|
||||
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)
|
||||
|
||||
var expandedEmojiKeyRect: CGRect?
|
||||
@ -1105,9 +1159,21 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
|
||||
let titleString: String
|
||||
switch params.state.lifecycleState {
|
||||
case .terminated:
|
||||
case let .terminated(terminatedState):
|
||||
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.setAlpha(layer: self.blobLayer, alpha: 0.0)
|
||||
self.canAnimateAudioLevel = false
|
||||
@ -1133,12 +1199,14 @@ public final class PrivateCallScreen: OverlayMaskContainerView, AVPictureInPictu
|
||||
|
||||
let statusState: StatusView.State
|
||||
switch params.state.lifecycleState {
|
||||
case .connecting:
|
||||
case .requesting:
|
||||
statusState = .waiting(.requesting)
|
||||
case .connecting:
|
||||
statusState = .waiting(.connecting)
|
||||
case .reconnecting:
|
||||
statusState = .waiting(.reconnecting)
|
||||
case .ringing:
|
||||
statusState = .waiting(.ringing)
|
||||
case .exchangingKeys:
|
||||
statusState = .waiting(.generatingKeys)
|
||||
case let .active(activeState):
|
||||
statusState = .active(StatusView.ActiveState(startTimestamp: activeState.startTime, signalStrength: activeState.signalInfo.quality))
|
||||
|
||||
|
@ -2030,7 +2030,7 @@ private func extractAccountManagerState(records: AccountRecordsView<TelegramAcco
|
||||
stableId: callUpdate.callId,
|
||||
handle: "\(callUpdate.peer.id.id._internalGetInt64Value())",
|
||||
phoneNumber: phoneNumber.flatMap(formatPhoneNumber),
|
||||
isVideo: false,
|
||||
isVideo: callUpdate.isVideo,
|
||||
displayTitle: callUpdate.peer.debugDisplayTitle,
|
||||
completion: { error in
|
||||
if let error = error {
|
||||
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"app": "10.3.1",
|
||||
"app": "10.3.2",
|
||||
"bazel": "6.4.0",
|
||||
"xcode": "15.0"
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user