Merge branch 'ios-notifications' into ep/ios-file-provider

This commit is contained in:
Evgeny Poberezkin 2022-06-02 12:41:07 +01:00
commit e15d4ac6b6
12 changed files with 145 additions and 139 deletions

View File

@ -11,7 +11,7 @@ android {
applicationId "chat.simplex.app" applicationId "chat.simplex.app"
minSdk 29 minSdk 29
targetSdk 32 targetSdk 32
versionCode 35 versionCode 36
versionName "2.2" versionName "2.2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View File

@ -100,6 +100,7 @@ class MainActivity: FragmentActivity(), LifecycleEventObserver {
userAuthorized.value = true userAuthorized.value = true
} else { } else {
userAuthorized.value = false userAuthorized.value = false
ModalManager.shared.closeModals()
authenticate( authenticate(
generalGetString(R.string.auth_unlock), generalGetString(R.string.auth_unlock),
generalGetString(R.string.auth_log_in_using_credential), generalGetString(R.string.auth_log_in_using_credential),
@ -241,7 +242,9 @@ fun MainPage(
// this with LaunchedEffect(userAuthorized.value) fixes bottom sheet visibly collapsing after authentication // this with LaunchedEffect(userAuthorized.value) fixes bottom sheet visibly collapsing after authentication
var chatsAccessAuthorized by remember { mutableStateOf(false) } var chatsAccessAuthorized by remember { mutableStateOf(false) }
LaunchedEffect(userAuthorized.value) { LaunchedEffect(userAuthorized.value) {
delay(500L) if (chatModel.controller.appPrefs.performLA.get()) {
delay(500L)
}
chatsAccessAuthorized = userAuthorized.value == true chatsAccessAuthorized = userAuthorized.value == true
} }
var showAdvertiseLAAlert by remember { mutableStateOf(false) } var showAdvertiseLAAlert by remember { mutableStateOf(false) }

View File

@ -70,18 +70,21 @@ struct ContentView: View {
if !prefPerformLA { if !prefPerformLA {
userAuthorized = true userAuthorized = true
} else { } else {
userAuthorized = false chatModel.showChatInfo = false
authenticate(reason: NSLocalizedString("Unlock", comment: "authentication reason")) { laResult in DispatchQueue.main.async() {
switch (laResult) { userAuthorized = false
case .success: authenticate(reason: NSLocalizedString("Unlock", comment: "authentication reason")) { laResult in
userAuthorized = true switch (laResult) {
case .failed: case .success:
laFailed = true userAuthorized = true
AlertManager.shared.showAlert(laFailedAlert()) case .failed:
case .unavailable: laFailed = true
userAuthorized = true AlertManager.shared.showAlert(laFailedAlert())
prefPerformLA = false case .unavailable:
AlertManager.shared.showAlert(laUnavailableTurningOffAlert()) userAuthorized = true
prefPerformLA = false
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
}
} }
} }
} }

View File

@ -15,6 +15,7 @@ import SimpleXChat
final class ChatModel: ObservableObject { final class ChatModel: ObservableObject {
@Published var onboardingStage: OnboardingStage? @Published var onboardingStage: OnboardingStage?
@Published var currentUser: User? @Published var currentUser: User?
@Published var showChatInfo: Bool = false // TODO comprehensively close modal views on authentication
// list of chat "previews" // list of chat "previews"
@Published var chats: [Chat] = [] @Published var chats: [Chat] = []
// current chat // current chat

View File

@ -11,7 +11,6 @@ import UIKit
import Dispatch import Dispatch
import BackgroundTasks import BackgroundTasks
import SwiftUI import SwiftUI
import CallKit
import SimpleXChat import SimpleXChat
private var chatController: chat_ctrl? private var chatController: chat_ctrl?
@ -681,7 +680,7 @@ func processReceivedMsg(_ res: ChatResponse) {
} }
withCall(contact) { call in withCall(contact) { call in
m.callCommand = .end m.callCommand = .end
CallController.shared.reportCallRemoteEnded(call: call) // CallController.shared.reportCallRemoteEnded(call: call)
} }
default: default:
logger.debug("unsupported event: \(res.responseType)") logger.debug("unsupported event: \(res.responseType)")

View File

@ -94,9 +94,9 @@ struct ActiveCallView: View {
case let .connection(state): case let .connection(state):
if let callStatus = WebRTCCallStatus.init(rawValue: state.connectionState), if let callStatus = WebRTCCallStatus.init(rawValue: state.connectionState),
case .connected = callStatus { case .connected = callStatus {
if case .outgoing = call.direction { // if case .outgoing = call.direction {
CallController.shared.reportOutgoingCall(call: call, connectedAt: nil) // CallController.shared.reportOutgoingCall(call: call, connectedAt: nil)
} // }
call.callState = .connected call.callState = .connected
// CallKit doesn't work well with WKWebView // CallKit doesn't work well with WKWebView
// This is a hack to enable microphone in WKWebView after CallKit takes over it // This is a hack to enable microphone in WKWebView after CallKit takes over it

View File

@ -7,91 +7,92 @@
// //
import Foundation import Foundation
import CallKit //import CallKit
import AVFoundation import AVFoundation
import SimpleXChat import SimpleXChat
class CallController: NSObject, CXProviderDelegate, ObservableObject { //class CallController: NSObject, CXProviderDelegate, ObservableObject {
class CallController: NSObject, ObservableObject {
static let useCallKit = false static let useCallKit = false
static let shared = CallController() static let shared = CallController()
private let provider = CXProvider(configuration: CallController.configuration) // private let provider = CXProvider(configuration: CallController.configuration)
private let controller = CXCallController() // private let controller = CXCallController()
private let callManager = CallManager() private let callManager = CallManager()
@Published var activeCallInvitation: CallInvitation? @Published var activeCallInvitation: CallInvitation?
// PKPushRegistry will be used from notification service extension // PKPushRegistry will be used from notification service extension
// let registry = PKPushRegistry(queue: nil) // let registry = PKPushRegistry(queue: nil)
static let configuration: CXProviderConfiguration = { // static let configuration: CXProviderConfiguration = {
let configuration = CXProviderConfiguration() // let configuration = CXProviderConfiguration()
configuration.supportsVideo = true // configuration.supportsVideo = true
configuration.supportedHandleTypes = [.generic] // configuration.supportedHandleTypes = [.generic]
configuration.includesCallsInRecents = true // TODO disable or add option // configuration.includesCallsInRecents = true // TODO disable or add option
configuration.maximumCallsPerCallGroup = 1 // configuration.maximumCallsPerCallGroup = 1
return configuration // return configuration
}() // }()
override init() { override init() {
super.init() super.init()
self.provider.setDelegate(self, queue: nil) // self.provider.setDelegate(self, queue: nil)
// self.registry.delegate = self // self.registry.delegate = self
// self.registry.desiredPushTypes = [.voIP] // self.registry.desiredPushTypes = [.voIP]
} }
func providerDidReset(_ provider: CXProvider) { // func providerDidReset(_ provider: CXProvider) {
} // }
func provider(_ provider: CXProvider, perform action: CXStartCallAction) { // func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
logger.debug("CallController.provider CXStartCallAction") // logger.debug("CallController.provider CXStartCallAction")
if callManager.startOutgoingCall(callUUID: action.callUUID) { // if callManager.startOutgoingCall(callUUID: action.callUUID) {
action.fulfill() // action.fulfill()
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil) // provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
} else { // } else {
action.fail() // action.fail()
}
}
func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
logger.debug("CallController.provider CXAnswerCallAction")
if callManager.answerIncomingCall(callUUID: action.callUUID) {
action.fulfill()
} else {
action.fail()
}
}
func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
logger.debug("CallController.provider CXEndCallAction")
callManager.endCall(callUUID: action.callUUID) { ok in
if ok {
action.fulfill()
} else {
action.fail()
}
}
}
func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
print("timed out", #function)
action.fulfill()
}
func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
print("received", #function)
// do {
// try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers)
// logger.debug("audioSession category set")
// try audioSession.setActive(true)
// logger.debug("audioSession activated")
// } catch {
// print(error)
// logger.error("failed activating audio session")
// } // }
} // }
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) { // func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
print("received", #function) // logger.debug("CallController.provider CXAnswerCallAction")
} // if callManager.answerIncomingCall(callUUID: action.callUUID) {
// action.fulfill()
// } else {
// action.fail()
// }
// }
// func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
// logger.debug("CallController.provider CXEndCallAction")
// callManager.endCall(callUUID: action.callUUID) { ok in
// if ok {
// action.fulfill()
// } else {
// action.fail()
// }
// }
// }
// func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) {
// print("timed out", #function)
// action.fulfill()
// }
// func provider(_ provider: CXProvider, didActivate audioSession: AVAudioSession) {
// print("received", #function)
//// do {
//// try audioSession.setCategory(.playAndRecord, mode: .voiceChat, options: .mixWithOthers)
//// logger.debug("audioSession category set")
//// try audioSession.setActive(true)
//// logger.debug("audioSession activated")
//// } catch {
//// print(error)
//// logger.error("failed activating audio session")
//// }
// }
// func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
// print("received", #function)
// }
// func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { // func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
// //
@ -122,48 +123,49 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
func reportNewIncomingCall(invitation: CallInvitation, completion: @escaping (Error?) -> Void) { func reportNewIncomingCall(invitation: CallInvitation, completion: @escaping (Error?) -> Void) {
logger.debug("CallController.reportNewIncomingCall") logger.debug("CallController.reportNewIncomingCall")
if !UserDefaults.standard.bool(forKey: DEFAULT_EXPERIMENTAL_CALLS) { return } if !UserDefaults.standard.bool(forKey: DEFAULT_EXPERIMENTAL_CALLS) { return }
if CallController.useCallKit, let uuid = invitation.callkitUUID { // if CallController.useCallKit, let uuid = invitation.callkitUUID {
let update = CXCallUpdate() // let update = CXCallUpdate()
update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.displayName) // update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.displayName)
update.hasVideo = invitation.peerMedia == .video // update.hasVideo = invitation.peerMedia == .video
provider.reportNewIncomingCall(with: uuid, update: update, completion: completion) // provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
} else { // } else {
NtfManager.shared.notifyCallInvitation(invitation) NtfManager.shared.notifyCallInvitation(invitation)
if invitation.callTs.timeIntervalSinceNow >= -180 { if invitation.callTs.timeIntervalSinceNow >= -180 {
activeCallInvitation = invitation activeCallInvitation = invitation
} }
} // }
} }
func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) { // func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
if CallController.useCallKit, let uuid = call.callkitUUID { // if CallController.useCallKit, let uuid = call.callkitUUID {
provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected) // provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
} // }
} // }
func reportCallRemoteEnded(invitation: CallInvitation) { func reportCallRemoteEnded(invitation: CallInvitation) {
if CallController.useCallKit, let uuid = invitation.callkitUUID { // if CallController.useCallKit, let uuid = invitation.callkitUUID {
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) // provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
} else if invitation.contact.id == activeCallInvitation?.contact.id { // } else if invitation.contact.id == activeCallInvitation?.contact.id {
activeCallInvitation = nil activeCallInvitation = nil
} // }
} }
func reportCallRemoteEnded(call: Call) { // func reportCallRemoteEnded(call: Call) {
if CallController.useCallKit, let uuid = call.callkitUUID { // if CallController.useCallKit, let uuid = call.callkitUUID {
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded) // provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
} // }
} // }
func startCall(_ contact: Contact, _ media: CallMediaType) { func startCall(_ contact: Contact, _ media: CallMediaType) {
logger.debug("CallController.startCall") logger.debug("CallController.startCall")
let uuid = callManager.newOutgoingCall(contact, media) let uuid = callManager.newOutgoingCall(contact, media)
if CallController.useCallKit { // if CallController.useCallKit {
let handle = CXHandle(type: .generic, value: contact.displayName) // let handle = CXHandle(type: .generic, value: contact.displayName)
let action = CXStartCallAction(call: uuid, handle: handle) // let action = CXStartCallAction(call: uuid, handle: handle)
action.isVideo = media == .video // action.isVideo = media == .video
requestTransaction(with: action) // requestTransaction(with: action)
} else if callManager.startOutgoingCall(callUUID: uuid) { // } else if callManager.startOutgoingCall(callUUID: uuid) {
if callManager.startOutgoingCall(callUUID: uuid) {
logger.debug("CallController.startCall: call started") logger.debug("CallController.startCall: call started")
} else { } else {
logger.error("CallController.startCall: no active call") logger.error("CallController.startCall: no active call")
@ -178,9 +180,9 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
} }
func endCall(callUUID: UUID) { func endCall(callUUID: UUID) {
if CallController.useCallKit { // if CallController.useCallKit {
requestTransaction(with: CXEndCallAction(call: callUUID)) // requestTransaction(with: CXEndCallAction(call: callUUID))
} else { // } else {
callManager.endCall(callUUID: callUUID) { ok in callManager.endCall(callUUID: callUUID) { ok in
if ok { if ok {
logger.debug("CallController.endCall: call ended") logger.debug("CallController.endCall: call ended")
@ -188,7 +190,7 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
logger.error("CallController.endCall: no actove call pr call invitation to end") logger.error("CallController.endCall: no actove call pr call invitation to end")
} }
} }
} // }
} }
func endCall(invitation: CallInvitation) { func endCall(invitation: CallInvitation) {
@ -205,15 +207,15 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
callManager.endCall(call: call, completed: completed) callManager.endCall(call: call, completed: completed)
} }
private func requestTransaction(with action: CXAction) { // private func requestTransaction(with action: CXAction) {
let t = CXTransaction() // let t = CXTransaction()
t.addAction(action) // t.addAction(action)
controller.request(t) { error in // controller.request(t) { error in
if let error = error { // if let error = error {
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)") // logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
} else { // } else {
logger.debug("CallController.requestTransaction requested transaction successfully") // logger.debug("CallController.requestTransaction requested transaction successfully")
} // }
} // }
} // }
} }

View File

@ -13,7 +13,6 @@ struct ChatInfoView: View {
@EnvironmentObject var chatModel: ChatModel @EnvironmentObject var chatModel: ChatModel
@ObservedObject var alertManager = AlertManager.shared @ObservedObject var alertManager = AlertManager.shared
@ObservedObject var chat: Chat @ObservedObject var chat: Chat
@Binding var showChatInfo: Bool
@State var alert: ChatInfoViewAlert? = nil @State var alert: ChatInfoViewAlert? = nil
@State var deletingContact: Contact? @State var deletingContact: Contact?
@ -100,7 +99,7 @@ struct ChatInfoView: View {
try await apiDeleteChat(type: .direct, id: contact.apiId) try await apiDeleteChat(type: .direct, id: contact.apiId)
DispatchQueue.main.async { DispatchQueue.main.async {
chatModel.removeChat(contact.id) chatModel.removeChat(contact.id)
showChatInfo = false chatModel.showChatInfo = false
} }
} catch let error { } catch let error {
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)") logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
@ -119,7 +118,7 @@ struct ChatInfoView: View {
Task { Task {
await clearChat(chat) await clearChat(chat)
DispatchQueue.main.async { DispatchQueue.main.async {
showChatInfo = false chatModel.showChatInfo = false
} }
} }
}, },
@ -131,6 +130,6 @@ struct ChatInfoView: View {
struct ChatInfoView_Previews: PreviewProvider { struct ChatInfoView_Previews: PreviewProvider {
static var previews: some View { static var previews: some View {
@State var showChatInfo = true @State var showChatInfo = true
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showChatInfo: $showChatInfo) return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
} }
} }

View File

@ -19,7 +19,6 @@ struct ChatView: View {
@State private var composeState = ComposeState() @State private var composeState = ComposeState()
@State private var deletingItem: ChatItem? = nil @State private var deletingItem: ChatItem? = nil
@FocusState private var keyboardVisible: Bool @FocusState private var keyboardVisible: Bool
@State private var showChatInfo = false
@State private var showDeleteMessage = false @State private var showDeleteMessage = false
var body: some View { var body: some View {
@ -99,12 +98,12 @@ struct ChatView: View {
} }
ToolbarItem(placement: .principal) { ToolbarItem(placement: .principal) {
Button { Button {
showChatInfo = true chatModel.showChatInfo = true
} label: { } label: {
ChatInfoToolbar(chat: chat) ChatInfoToolbar(chat: chat)
} }
.sheet(isPresented: $showChatInfo) { .sheet(isPresented: $chatModel.showChatInfo) {
ChatInfoView(chat: chat, showChatInfo: $showChatInfo) ChatInfoView(chat: chat)
} }
} }
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {

View File

@ -1505,7 +1505,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49; CURRENT_PROJECT_VERSION = 51;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -1548,7 +1548,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49; CURRENT_PROJECT_VERSION = 51;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
ENABLE_PREVIEWS = YES; ENABLE_PREVIEWS = YES;
@ -1629,7 +1629,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49; CURRENT_PROJECT_VERSION = 51;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;
@ -1668,7 +1668,7 @@
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_STYLE = Automatic; CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 49; CURRENT_PROJECT_VERSION = 51;
DEVELOPMENT_TEAM = 5NN7GUYB6T; DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
GENERATE_INFOPLIST_FILE = YES; GENERATE_INFOPLIST_FILE = YES;

View File

@ -1,5 +1,5 @@
name: simplex-chat name: simplex-chat
version: 2.1.0 version: 2.2.0
#synopsis: #synopsis:
#description: #description:
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme

View File

@ -5,7 +5,7 @@ cabal-version: 1.12
-- see: https://github.com/sol/hpack -- see: https://github.com/sol/hpack
name: simplex-chat name: simplex-chat
version: 2.1.0 version: 2.2.0
category: Web, System, Services, Cryptography category: Web, System, Services, Cryptography
homepage: https://github.com/simplex-chat/simplex-chat#readme homepage: https://github.com/simplex-chat/simplex-chat#readme
author: simplex.chat author: simplex.chat