Merge branch 'ios-notifications' into ep/ios-file-provider
This commit is contained in:
commit
e15d4ac6b6
@ -11,7 +11,7 @@ android {
|
||||
applicationId "chat.simplex.app"
|
||||
minSdk 29
|
||||
targetSdk 32
|
||||
versionCode 35
|
||||
versionCode 36
|
||||
versionName "2.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
@ -100,6 +100,7 @@ class MainActivity: FragmentActivity(), LifecycleEventObserver {
|
||||
userAuthorized.value = true
|
||||
} else {
|
||||
userAuthorized.value = false
|
||||
ModalManager.shared.closeModals()
|
||||
authenticate(
|
||||
generalGetString(R.string.auth_unlock),
|
||||
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
|
||||
var chatsAccessAuthorized by remember { mutableStateOf(false) }
|
||||
LaunchedEffect(userAuthorized.value) {
|
||||
delay(500L)
|
||||
if (chatModel.controller.appPrefs.performLA.get()) {
|
||||
delay(500L)
|
||||
}
|
||||
chatsAccessAuthorized = userAuthorized.value == true
|
||||
}
|
||||
var showAdvertiseLAAlert by remember { mutableStateOf(false) }
|
||||
|
@ -70,18 +70,21 @@ struct ContentView: View {
|
||||
if !prefPerformLA {
|
||||
userAuthorized = true
|
||||
} else {
|
||||
userAuthorized = false
|
||||
authenticate(reason: NSLocalizedString("Unlock", comment: "authentication reason")) { laResult in
|
||||
switch (laResult) {
|
||||
case .success:
|
||||
userAuthorized = true
|
||||
case .failed:
|
||||
laFailed = true
|
||||
AlertManager.shared.showAlert(laFailedAlert())
|
||||
case .unavailable:
|
||||
userAuthorized = true
|
||||
prefPerformLA = false
|
||||
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
|
||||
chatModel.showChatInfo = false
|
||||
DispatchQueue.main.async() {
|
||||
userAuthorized = false
|
||||
authenticate(reason: NSLocalizedString("Unlock", comment: "authentication reason")) { laResult in
|
||||
switch (laResult) {
|
||||
case .success:
|
||||
userAuthorized = true
|
||||
case .failed:
|
||||
laFailed = true
|
||||
AlertManager.shared.showAlert(laFailedAlert())
|
||||
case .unavailable:
|
||||
userAuthorized = true
|
||||
prefPerformLA = false
|
||||
AlertManager.shared.showAlert(laUnavailableTurningOffAlert())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import SimpleXChat
|
||||
final class ChatModel: ObservableObject {
|
||||
@Published var onboardingStage: OnboardingStage?
|
||||
@Published var currentUser: User?
|
||||
@Published var showChatInfo: Bool = false // TODO comprehensively close modal views on authentication
|
||||
// list of chat "previews"
|
||||
@Published var chats: [Chat] = []
|
||||
// current chat
|
||||
|
@ -11,7 +11,6 @@ import UIKit
|
||||
import Dispatch
|
||||
import BackgroundTasks
|
||||
import SwiftUI
|
||||
import CallKit
|
||||
import SimpleXChat
|
||||
|
||||
private var chatController: chat_ctrl?
|
||||
@ -681,7 +680,7 @@ func processReceivedMsg(_ res: ChatResponse) {
|
||||
}
|
||||
withCall(contact) { call in
|
||||
m.callCommand = .end
|
||||
CallController.shared.reportCallRemoteEnded(call: call)
|
||||
// CallController.shared.reportCallRemoteEnded(call: call)
|
||||
}
|
||||
default:
|
||||
logger.debug("unsupported event: \(res.responseType)")
|
||||
|
@ -94,9 +94,9 @@ struct ActiveCallView: View {
|
||||
case let .connection(state):
|
||||
if let callStatus = WebRTCCallStatus.init(rawValue: state.connectionState),
|
||||
case .connected = callStatus {
|
||||
if case .outgoing = call.direction {
|
||||
CallController.shared.reportOutgoingCall(call: call, connectedAt: nil)
|
||||
}
|
||||
// if case .outgoing = call.direction {
|
||||
// CallController.shared.reportOutgoingCall(call: call, connectedAt: nil)
|
||||
// }
|
||||
call.callState = .connected
|
||||
// CallKit doesn't work well with WKWebView
|
||||
// This is a hack to enable microphone in WKWebView after CallKit takes over it
|
||||
|
@ -7,91 +7,92 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CallKit
|
||||
//import CallKit
|
||||
import AVFoundation
|
||||
import SimpleXChat
|
||||
|
||||
class CallController: NSObject, CXProviderDelegate, ObservableObject {
|
||||
//class CallController: NSObject, CXProviderDelegate, ObservableObject {
|
||||
class CallController: NSObject, ObservableObject {
|
||||
static let useCallKit = false
|
||||
static let shared = CallController()
|
||||
private let provider = CXProvider(configuration: CallController.configuration)
|
||||
private let controller = CXCallController()
|
||||
// private let provider = CXProvider(configuration: CallController.configuration)
|
||||
// private let controller = CXCallController()
|
||||
private let callManager = CallManager()
|
||||
@Published var activeCallInvitation: CallInvitation?
|
||||
|
||||
// PKPushRegistry will be used from notification service extension
|
||||
// let registry = PKPushRegistry(queue: nil)
|
||||
|
||||
static let configuration: CXProviderConfiguration = {
|
||||
let configuration = CXProviderConfiguration()
|
||||
configuration.supportsVideo = true
|
||||
configuration.supportedHandleTypes = [.generic]
|
||||
configuration.includesCallsInRecents = true // TODO disable or add option
|
||||
configuration.maximumCallsPerCallGroup = 1
|
||||
return configuration
|
||||
}()
|
||||
// static let configuration: CXProviderConfiguration = {
|
||||
// let configuration = CXProviderConfiguration()
|
||||
// configuration.supportsVideo = true
|
||||
// configuration.supportedHandleTypes = [.generic]
|
||||
// configuration.includesCallsInRecents = true // TODO disable or add option
|
||||
// configuration.maximumCallsPerCallGroup = 1
|
||||
// return configuration
|
||||
// }()
|
||||
|
||||
override init() {
|
||||
super.init()
|
||||
self.provider.setDelegate(self, queue: nil)
|
||||
// self.provider.setDelegate(self, queue: nil)
|
||||
// self.registry.delegate = self
|
||||
// self.registry.desiredPushTypes = [.voIP]
|
||||
}
|
||||
|
||||
func providerDidReset(_ provider: CXProvider) {
|
||||
}
|
||||
// func providerDidReset(_ provider: CXProvider) {
|
||||
// }
|
||||
|
||||
func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
|
||||
logger.debug("CallController.provider CXStartCallAction")
|
||||
if callManager.startOutgoingCall(callUUID: action.callUUID) {
|
||||
action.fulfill()
|
||||
provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
|
||||
} else {
|
||||
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, perform action: CXStartCallAction) {
|
||||
// logger.debug("CallController.provider CXStartCallAction")
|
||||
// if callManager.startOutgoingCall(callUUID: action.callUUID) {
|
||||
// action.fulfill()
|
||||
// provider.reportOutgoingCall(with: action.callUUID, startedConnectingAt: nil)
|
||||
// } else {
|
||||
// action.fail()
|
||||
// }
|
||||
}
|
||||
// }
|
||||
|
||||
func provider(_ provider: CXProvider, didDeactivate audioSession: AVAudioSession) {
|
||||
print("received", #function)
|
||||
}
|
||||
// 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) {
|
||||
// print("received", #function)
|
||||
// }
|
||||
|
||||
// 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) {
|
||||
logger.debug("CallController.reportNewIncomingCall")
|
||||
if !UserDefaults.standard.bool(forKey: DEFAULT_EXPERIMENTAL_CALLS) { return }
|
||||
if CallController.useCallKit, let uuid = invitation.callkitUUID {
|
||||
let update = CXCallUpdate()
|
||||
update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.displayName)
|
||||
update.hasVideo = invitation.peerMedia == .video
|
||||
provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
|
||||
} else {
|
||||
// if CallController.useCallKit, let uuid = invitation.callkitUUID {
|
||||
// let update = CXCallUpdate()
|
||||
// update.remoteHandle = CXHandle(type: .generic, value: invitation.contact.displayName)
|
||||
// update.hasVideo = invitation.peerMedia == .video
|
||||
// provider.reportNewIncomingCall(with: uuid, update: update, completion: completion)
|
||||
// } else {
|
||||
NtfManager.shared.notifyCallInvitation(invitation)
|
||||
if invitation.callTs.timeIntervalSinceNow >= -180 {
|
||||
activeCallInvitation = invitation
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
|
||||
if CallController.useCallKit, let uuid = call.callkitUUID {
|
||||
provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
|
||||
}
|
||||
}
|
||||
// func reportOutgoingCall(call: Call, connectedAt dateConnected: Date?) {
|
||||
// if CallController.useCallKit, let uuid = call.callkitUUID {
|
||||
// provider.reportOutgoingCall(with: uuid, connectedAt: dateConnected)
|
||||
// }
|
||||
// }
|
||||
|
||||
func reportCallRemoteEnded(invitation: CallInvitation) {
|
||||
if CallController.useCallKit, let uuid = invitation.callkitUUID {
|
||||
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
|
||||
} else if invitation.contact.id == activeCallInvitation?.contact.id {
|
||||
// if CallController.useCallKit, let uuid = invitation.callkitUUID {
|
||||
// provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
|
||||
// } else if invitation.contact.id == activeCallInvitation?.contact.id {
|
||||
activeCallInvitation = nil
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
func reportCallRemoteEnded(call: Call) {
|
||||
if CallController.useCallKit, let uuid = call.callkitUUID {
|
||||
provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
|
||||
}
|
||||
}
|
||||
// func reportCallRemoteEnded(call: Call) {
|
||||
// if CallController.useCallKit, let uuid = call.callkitUUID {
|
||||
// provider.reportCall(with: uuid, endedAt: nil, reason: .remoteEnded)
|
||||
// }
|
||||
// }
|
||||
|
||||
func startCall(_ contact: Contact, _ media: CallMediaType) {
|
||||
logger.debug("CallController.startCall")
|
||||
let uuid = callManager.newOutgoingCall(contact, media)
|
||||
if CallController.useCallKit {
|
||||
let handle = CXHandle(type: .generic, value: contact.displayName)
|
||||
let action = CXStartCallAction(call: uuid, handle: handle)
|
||||
action.isVideo = media == .video
|
||||
requestTransaction(with: action)
|
||||
} else if callManager.startOutgoingCall(callUUID: uuid) {
|
||||
// if CallController.useCallKit {
|
||||
// let handle = CXHandle(type: .generic, value: contact.displayName)
|
||||
// let action = CXStartCallAction(call: uuid, handle: handle)
|
||||
// action.isVideo = media == .video
|
||||
// requestTransaction(with: action)
|
||||
// } else if callManager.startOutgoingCall(callUUID: uuid) {
|
||||
if callManager.startOutgoingCall(callUUID: uuid) {
|
||||
logger.debug("CallController.startCall: call started")
|
||||
} else {
|
||||
logger.error("CallController.startCall: no active call")
|
||||
@ -178,9 +180,9 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
|
||||
}
|
||||
|
||||
func endCall(callUUID: UUID) {
|
||||
if CallController.useCallKit {
|
||||
requestTransaction(with: CXEndCallAction(call: callUUID))
|
||||
} else {
|
||||
// if CallController.useCallKit {
|
||||
// requestTransaction(with: CXEndCallAction(call: callUUID))
|
||||
// } else {
|
||||
callManager.endCall(callUUID: callUUID) { ok in
|
||||
if ok {
|
||||
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")
|
||||
}
|
||||
}
|
||||
}
|
||||
// }
|
||||
}
|
||||
|
||||
func endCall(invitation: CallInvitation) {
|
||||
@ -205,15 +207,15 @@ class CallController: NSObject, CXProviderDelegate, ObservableObject {
|
||||
callManager.endCall(call: call, completed: completed)
|
||||
}
|
||||
|
||||
private func requestTransaction(with action: CXAction) {
|
||||
let t = CXTransaction()
|
||||
t.addAction(action)
|
||||
controller.request(t) { error in
|
||||
if let error = error {
|
||||
logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
|
||||
} else {
|
||||
logger.debug("CallController.requestTransaction requested transaction successfully")
|
||||
}
|
||||
}
|
||||
}
|
||||
// private func requestTransaction(with action: CXAction) {
|
||||
// let t = CXTransaction()
|
||||
// t.addAction(action)
|
||||
// controller.request(t) { error in
|
||||
// if let error = error {
|
||||
// logger.error("CallController.requestTransaction error requesting transaction: \(error.localizedDescription)")
|
||||
// } else {
|
||||
// logger.debug("CallController.requestTransaction requested transaction successfully")
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
@ -13,7 +13,6 @@ struct ChatInfoView: View {
|
||||
@EnvironmentObject var chatModel: ChatModel
|
||||
@ObservedObject var alertManager = AlertManager.shared
|
||||
@ObservedObject var chat: Chat
|
||||
@Binding var showChatInfo: Bool
|
||||
@State var alert: ChatInfoViewAlert? = nil
|
||||
@State var deletingContact: Contact?
|
||||
|
||||
@ -100,7 +99,7 @@ struct ChatInfoView: View {
|
||||
try await apiDeleteChat(type: .direct, id: contact.apiId)
|
||||
DispatchQueue.main.async {
|
||||
chatModel.removeChat(contact.id)
|
||||
showChatInfo = false
|
||||
chatModel.showChatInfo = false
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("ChatInfoView.deleteContactAlert apiDeleteChat error: \(error.localizedDescription)")
|
||||
@ -119,7 +118,7 @@ struct ChatInfoView: View {
|
||||
Task {
|
||||
await clearChat(chat)
|
||||
DispatchQueue.main.async {
|
||||
showChatInfo = false
|
||||
chatModel.showChatInfo = false
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -131,6 +130,6 @@ struct ChatInfoView: View {
|
||||
struct ChatInfoView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
@State var showChatInfo = true
|
||||
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []), showChatInfo: $showChatInfo)
|
||||
return ChatInfoView(chat: Chat(chatInfo: ChatInfo.sampleData.direct, chatItems: []))
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ struct ChatView: View {
|
||||
@State private var composeState = ComposeState()
|
||||
@State private var deletingItem: ChatItem? = nil
|
||||
@FocusState private var keyboardVisible: Bool
|
||||
@State private var showChatInfo = false
|
||||
@State private var showDeleteMessage = false
|
||||
|
||||
var body: some View {
|
||||
@ -99,12 +98,12 @@ struct ChatView: View {
|
||||
}
|
||||
ToolbarItem(placement: .principal) {
|
||||
Button {
|
||||
showChatInfo = true
|
||||
chatModel.showChatInfo = true
|
||||
} label: {
|
||||
ChatInfoToolbar(chat: chat)
|
||||
}
|
||||
.sheet(isPresented: $showChatInfo) {
|
||||
ChatInfoView(chat: chat, showChatInfo: $showChatInfo)
|
||||
.sheet(isPresented: $chatModel.showChatInfo) {
|
||||
ChatInfoView(chat: chat)
|
||||
}
|
||||
}
|
||||
ToolbarItem(placement: .navigationBarTrailing) {
|
||||
|
@ -1505,7 +1505,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1548,7 +1548,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@ -1629,7 +1629,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@ -1668,7 +1668,7 @@
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 49;
|
||||
CURRENT_PROJECT_VERSION = 51;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
|
@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 2.1.0
|
||||
version: 2.2.0
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
@ -5,7 +5,7 @@ cabal-version: 1.12
|
||||
-- see: https://github.com/sol/hpack
|
||||
|
||||
name: simplex-chat
|
||||
version: 2.1.0
|
||||
version: 2.2.0
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
|
Loading…
Reference in New Issue
Block a user