From 840df89ca631eddb8c954fb89d7c54671747f3ef Mon Sep 17 00:00:00 2001 From: Stanislav Dmitrenko <7953703+avently@users.noreply.github.com> Date: Wed, 15 Mar 2023 18:32:27 +0300 Subject: [PATCH] ios: CallKit enhancements (#2010) * ios: CallKit enhancements * better checks --- apps/ios/Shared/AppDelegate.swift | 10 ++++++++ apps/ios/Shared/ContentView.swift | 4 ++- apps/ios/Shared/Model/ChatModel.swift | 5 ++-- apps/ios/Shared/SimpleXApp.swift | 15 +++++++++-- .../Shared/Views/Call/ActiveCallView.swift | 25 +++++++++++++------ 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/apps/ios/Shared/AppDelegate.swift b/apps/ios/Shared/AppDelegate.swift index f20882383a..457aaa2824 100644 --- a/apps/ios/Shared/AppDelegate.swift +++ b/apps/ios/Shared/AppDelegate.swift @@ -94,6 +94,16 @@ class AppDelegate: NSObject, UIApplicationDelegate { return configuration } + func applicationProtectedDataWillBecomeUnavailable(_ application: UIApplication) { + logger.debug("AppDelegate: will lock screen") + ChatModel.shared.onLockScreenCurrently = true + } + + func applicationProtectedDataDidBecomeAvailable(_ application: UIApplication) { + logger.debug("AppDelegate: did unlock screen") + ChatModel.shared.onLockScreenCurrently = false + } + private func receiveMessages(_ completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { let complete = BGManager.shared.completionHandler { logger.debug("AppDelegate: completed BGManager.receiveMessages") diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 88b2758726..2b56580136 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -17,6 +17,7 @@ struct ContentView: View { @Binding var doAuthenticate: Bool @Binding var userAuthorized: Bool? @Binding var canConnectCall: Bool + @Binding var lastSuccessfulUnlock: TimeInterval? @AppStorage(DEFAULT_SHOW_LA_NOTICE) private var prefShowLANotice = false @AppStorage(DEFAULT_LA_NOTICE_SHOWN) private var prefLANoticeShown = false @AppStorage(DEFAULT_PERFORM_LA) private var prefPerformLA = false @@ -27,7 +28,7 @@ struct ContentView: View { var body: some View { ZStack { if chatModel.showCallView, let call = chatModel.activeCall { - ActiveCallView(call: call, userAuthorized: $userAuthorized, canConnectCall: $canConnectCall) + ActiveCallView(call: call, canConnectCall: $canConnectCall) } if prefPerformLA && userAuthorized != true { Rectangle().fill(colorScheme == .dark ? .black : .white) @@ -128,6 +129,7 @@ struct ContentView: View { case .success: userAuthorized = true canConnectCall = true + lastSuccessfulUnlock = ProcessInfo.processInfo.systemUptime case .failed: break case .unavailable: diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index ac00f62689..11d7bce826 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -9,7 +9,6 @@ import Foundation import Combine import SwiftUI -import WebKit import SimpleXChat final class ChatModel: ObservableObject { @@ -59,7 +58,9 @@ final class ChatModel: ObservableObject { @Published var stopPreviousRecPlay: Bool = false // value is not taken into account, only the fact it switches @Published var draft: ComposeState? @Published var draftChatId: String? - var callWebView: WKWebView? + + var sceneWasActiveAtLeastOnce = false + var onLockScreenCurrently = false var messageDelivery: Dictionary Void> = [:] diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index ec216b8063..1e25e15426 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -22,6 +22,7 @@ struct SimpleXApp: App { @State private var doAuthenticate = false @State private var canConnectCall = false @State private var enteredBackground: TimeInterval? = nil + @State private var lastSuccessfulUnlock: TimeInterval? = nil init() { hs_init(0, nil) @@ -35,7 +36,7 @@ struct SimpleXApp: App { var body: some Scene { return WindowGroup { - ContentView(doAuthenticate: $doAuthenticate, userAuthorized: $userAuthorized, canConnectCall: $canConnectCall) + ContentView(doAuthenticate: $doAuthenticate, userAuthorized: $userAuthorized, canConnectCall: $canConnectCall, lastSuccessfulUnlock: $lastSuccessfulUnlock) .environmentObject(chatModel) .onOpenURL { url in logger.debug("ContentView.onOpenURL: \(url)") @@ -65,6 +66,7 @@ struct SimpleXApp: App { NtfManager.shared.setNtfBadgeCount(chatModel.totalUnreadCountForAllUsers()) case .active: CallController.shared.onEndCall = nil + chatModel.sceneWasActiveAtLeastOnce = true let appState = appStateGroupDefault.get() startChatAndActivate() if appState.inactive && chatModel.chatRunning == true { @@ -74,7 +76,7 @@ struct SimpleXApp: App { } } doAuthenticate = authenticationExpired() - canConnectCall = !(doAuthenticate && prefPerformLA) + canConnectCall = !(doAuthenticate && prefPerformLA) || unlockedRecently() default: break } @@ -114,6 +116,15 @@ struct SimpleXApp: App { } } + + private func unlockedRecently() -> Bool { + if let lastSuccessfulUnlock = lastSuccessfulUnlock { + return ProcessInfo.processInfo.systemUptime - lastSuccessfulUnlock < 2 + } else { + return false + } + } + private func updateChats() { do { let chats = try apiGetChats() diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index 9c8b256e00..98a006ae92 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -14,7 +14,6 @@ struct ActiveCallView: View { @EnvironmentObject var m: ChatModel @Environment(\.scenePhase) var scenePhase @ObservedObject var call: Call - @Binding var userAuthorized: Bool? @Binding var canConnectCall: Bool @State private var client: WebRTCClient? = nil @State private var activeCall: WebRTCClient.Call? = nil @@ -39,11 +38,7 @@ struct ActiveCallView: View { } } .onAppear { - logger.debug("ActiveCallView: appear client is nil \(client == nil), userAuthorized \(userAuthorized.debugDescription, privacy: .public), scenePhase \(String(describing: scenePhase), privacy: .public)") - createWebRTCClient() - } - .onChange(of: userAuthorized) { _ in - logger.debug("ActiveCallView: userAuthorized changed to \(userAuthorized.debugDescription, privacy: .public)") + logger.debug("ActiveCallView: appear client is nil \(client == nil), canConnectCall \(canConnectCall, privacy: .public), scenePhase \(String(describing: scenePhase), privacy: .public)") createWebRTCClient() } .onChange(of: canConnectCall) { _ in @@ -60,8 +55,22 @@ struct ActiveCallView: View { } private func createWebRTCClient() { - if client == nil && ((userAuthorized == true && canConnectCall) || scenePhase == .background) { - client = WebRTCClient($activeCall, { msg in await MainActor.run { processRtcMessage(msg: msg) } }, $localRendererAspectRatio) + if client == nil && (canConnectCall || m.onLockScreenCurrently) { + createWebRTCClientWithoutWait() + } else if (!m.sceneWasActiveAtLeastOnce) { + // This code waits a second until it recheck `sceneWasActiveAtLeastOnce`. + // It helps to know whether a call from lockscreen or not. + // After the second `sceneWasActiveAtLeastOnce` will still be false when the call from lockscreen + Task { + try? await Task.sleep(nanoseconds: 1_000_000_000) + createWebRTCClientWithoutWait() + } + } + } + + private func createWebRTCClientWithoutWait() { + if client == nil && (canConnectCall || !m.sceneWasActiveAtLeastOnce || m.onLockScreenCurrently) { + client = WebRTCClient($activeCall, { msg in await MainActor.run {processRtcMessage(msg: msg)} }, $localRendererAspectRatio) sendCommandToClient() } }