diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index debe46112..ad56b44ab 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; + 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; }; 6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */; }; @@ -412,7 +412,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */, + 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1388,13 +1388,14 @@ CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 72; DEVELOPMENT_TEAM = Z2V957B3D6; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", + "USE_CRASHLYTICS=1", ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = msgNotificationService/Info.plist; @@ -1408,7 +1409,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 6.0.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1430,11 +1431,14 @@ CODE_SIGN_ENTITLEMENTS = msgNotificationService/msgNotificationService.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 72; DEVELOPMENT_TEAM = Z2V957B3D6; ENABLE_USER_SCRIPT_SANDBOXING = NO; GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "USE_CRASHLYTICS=1", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = msgNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; @@ -1447,7 +1451,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 6.0.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1584,7 +1588,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 72; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; @@ -1594,6 +1598,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", + "USE_CRASHLYTICS=1", ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Linphone/Info.plist; @@ -1623,7 +1628,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 6.0.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1643,13 +1648,16 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 70; + CURRENT_PROJECT_VERSION = 72; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; DEVELOPMENT_TEAM = Z2V957B3D6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "USE_CRASHLYTICS=1", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Linphone/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Linphone; @@ -1678,7 +1686,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 6.0.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index d8070c598..f01e2f2ac 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -43,7 +43,7 @@ final class CoreContext: ObservableObject { @Published var coreIsStarted: Bool = false @Published var accounts: [AccountModel] = [] @Published var shortcuts: [ShortcutModel] = [] - private var mCore: Core! + var mCore: Core! private var mIterateSuscription: AnyCancellable? var bearerAuthInfoPendingPasswordUpdate: AuthInfo? diff --git a/Linphone/Core/CorePreferences.swift b/Linphone/Core/CorePreferences.swift index 99eb1548e..2926ef260 100644 --- a/Linphone/Core/CorePreferences.swift +++ b/Linphone/Core/CorePreferences.swift @@ -242,6 +242,15 @@ class CorePreferences { } } + static var defaultDomain: String { + get { + return Config.get().getString(section: "app", key: "default_domain", defaultString: "sip.linphone.org") + } + set { + Config.get().setString(section: "app", key: "default_domain", value: newValue) + } + } + private func copy(from: String, to: String, overrideIfExists: Bool = false) { let fileManager = FileManager.default if fileManager.fileExists(atPath: to), !overrideIfExists { diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 38bff373a..e842549f1 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -3486,6 +3486,17 @@ } } }, + "conversation_one_to_one_hidden_subject" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Dummy subject" + } + } + } + }, "conversation_reply_to_message_title" : { "extractionState" : "manual", "localizations" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index eacddcc40..e8d1d2112 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -2262,16 +2262,14 @@ struct CallView: View { HStack(spacing: 0) { VStack { Button { - if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil { - callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!) - } + callViewModel.createConversation() } label: { HStack { if !callViewModel.operationInProgress { Image("chat-teardrop-text") .renderingMode(.template) .resizable() - .foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500) + .foregroundStyle(.white) .frame(width: 32, height: 32) } else { ProgressView() @@ -2279,8 +2277,13 @@ struct CallView: View { .progressViewStyle(CircularProgressViewStyle(tint: .white)) .frame(width: 32, height: 32, alignment: .center) .onDisappear { - if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil { - conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!) + if callViewModel.displayedConversation != nil { + indexPage = 2 + self.conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!) + callViewModel.displayedConversation = nil + withAnimation { + telecomManager.callDisplayed = false + } } } } @@ -2288,9 +2291,8 @@ struct CallView: View { } .buttonStyle(PressedButtonStyle(buttonSize: buttonSize)) .frame(width: buttonSize, height: buttonSize) - .background(callViewModel.isOneOneCall ? Color.gray500 : .white) + .background(Color.gray500) .cornerRadius(40) - .disabled(!callViewModel.isOneOneCall) Text("call_action_show_messages") .foregroundStyle(.white) @@ -2631,16 +2633,14 @@ struct CallView: View { VStack { Button { - if callViewModel.isOneOneCall && callViewModel.remoteAddress != nil { - callViewModel.createOneToOneChatRoomWith(remote: callViewModel.remoteAddress!) - } + callViewModel.createConversation() } label: { HStack { if !callViewModel.operationInProgress { Image("chat-teardrop-text") .renderingMode(.template) .resizable() - .foregroundStyle(callViewModel.isOneOneCall ? .white : Color.gray500) + .foregroundStyle(.white) .frame(width: 32, height: 32) } else { ProgressView() @@ -2648,7 +2648,7 @@ struct CallView: View { .progressViewStyle(CircularProgressViewStyle(tint: .white)) .frame(width: 32, height: 32, alignment: .center) .onDisappear { - if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil { + if callViewModel.displayedConversation != nil { conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!) } } @@ -2657,9 +2657,8 @@ struct CallView: View { } .buttonStyle(PressedButtonStyle(buttonSize: buttonSize)) .frame(width: buttonSize, height: buttonSize) - .background(callViewModel.isOneOneCall ? Color.gray500 : .white) + .background(Color.gray500) .cornerRadius(40) - .disabled(!callViewModel.isOneOneCall) Text("call_action_show_messages") .foregroundStyle(.white) diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 12562219b..203918c13 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -1162,141 +1162,179 @@ class CallViewModel: ObservableObject { Log.info("\(CallViewModel.TAG) \(list.count) participants added to conference") } - func createOneToOneChatRoomWith(remote: Address) { - CoreContext.shared.doOnCoreQueue { core in - let account = core.defaultAccount - if account == nil { - Log.error( - "\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!" - ) - return + func createConversation() { + if currentCall != nil { + self.operationInProgress = true + CoreContext.shared.doOnCoreQueue { _ in + let existingConversation = self.lookupCurrentCallConversation(call: self.currentCall!) + if existingConversation != nil { + Log.info("\(CallViewModel.TAG) Found existing conversation \(LinphoneUtils.getConversationId(chatRoom: existingConversation!)), going to it") + + let model = ConversationModel(chatRoom: existingConversation!) + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false + } + } else { + Log.info("\(CallViewModel.TAG) No existing conversation was found, let's create it") + self.createCurrentCallConversation(call: self.currentCall!) + } } - + } + } + + func lookupCurrentCallConversation(call: Call) -> ChatRoom? { + let localAddress = call.callLog?.localAddress + let remoteAddress = call.remoteAddress + + let params: ConferenceParams? = nil + let existingConversation: ChatRoom? + if call.conference != nil { + existingConversation = call.core?.searchChatRoom( + params: params, + localAddr: localAddress, + remoteAddr: remoteAddress, + participants: [] + ) + } else { + let participants = [remoteAddress!] + existingConversation = call.core?.searchChatRoom( + params: params, + localAddr: localAddress, + remoteAddr: nil, + participants: participants + ) + } + + if existingConversation != nil { + Log.info("\(CallViewModel.TAG) Found existing conversation \(existingConversation!.peerAddress?.asStringUriOnly() ?? "") found for current call with local address \(localAddress?.asStringUriOnly() ?? "") and remote address \(remoteAddress?.asStringUriOnly() ?? "")") + } else { + Log.warn("\(CallViewModel.TAG) No existing conversation found for current call with local address \(localAddress?.asStringUriOnly() ?? "") and remote address \(remoteAddress?.asStringUriOnly() ?? "")") + } + + return existingConversation + } + + func createCurrentCallConversation(call: Call) { + if let remoteAddress = call.remoteAddress { + let participants = [remoteAddress] + let core = call.core DispatchQueue.main.async { self.operationInProgress = true } - do { - let params: ChatRoomParams = try core.createDefaultChatRoomParams() - params.groupEnabled = false - params.subject = "Dummy subject" - params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default - - let sameDomain = remote.domain == account?.params?.domain ?? "" - if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain { - Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation") - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true - } else if !StartConversationViewModel.isEndToEndEncryptionMandatory() { - if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) { - Log.info( - "\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation" - ) - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true - } else { - Log.info( - "\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation" - ) - params.backend = ChatRoom.Backend.Basic - params.encryptionEnabled = false + if let params = getChatRoomParams(call: call) { + do { + if core != nil { + let chatRoom = try core!.createChatRoom(params: params, participants: participants) + if params.chatParams?.backend == ChatRoom.Backend.FlexisipChat { + if chatRoom.state == ChatRoom.State.Created { + let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(CallViewModel.TAG) 1-1 conversation \(chatRoomId) has been created") + let model = ConversationModel(chatRoom: chatRoom) + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false + } + } else { + Log.info("\(CallViewModel.TAG) Conversation isn't in Created state yet, wait for it") + self.chatRoomAddDelegate(core: core!, chatRoom: chatRoom) + } + } else { + let id = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(CallViewModel.TAG) Conversation successfully created \(id)") + + let model = ConversationModel(chatRoom: chatRoom) + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false + } + + } } - } else { + } catch { Log.error( - "\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())" + "\(CallViewModel.TAG) Failed to create 1-1 conversation with \(remoteAddress.asStringUriOnly())" ) DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error" + ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" ToastViewModel.shared.displayToast = true } - return } + } + } + } + + func getChatRoomParams(call: Call) -> ConferenceParams? { + let localAddress = call.callLog?.localAddress + let remoteAddress = call.remoteAddress + let core = call.core + if let account = LinphoneUtils.getAccountForAddress(address: localAddress!) ?? LinphoneUtils.getDefaultAccount() { + do { + let params = try coreContext.mCore.createConferenceParams(conference: call.conference) + params.chatEnabled = true + params.groupEnabled = false + params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "") + params.account = account - let participants = [remote] - let localAddress = account?.params?.identityAddress - let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants) - if existingChatRoom == nil { - Log.info( - "\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account " - + "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it" - ) - let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants) - if params.backend == ChatRoom.Backend.FlexisipChat { - if chatRoom.state == ChatRoom.State.Created { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } - } - } else { - Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it") - self.chatRoomAddDelegate(core: core, chatRoom: chatRoom) - } - } else { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } - } - } - } else { - Log.warn( - "\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!" - ) + if let chatParams = params.chatParams { + chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default - let model = ConversationModel(chatRoom: existingChatRoom!) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model + let sameDomain = remoteAddress?.domain == CorePreferences.defaultDomain && remoteAddress?.domain == account.params?.domain + if account.params != nil && (account.params!.instantMessagingEncryptionMandatory && sameDomain) { + Log.info( + "\(CallViewModel.TAG) Account is in secure mode & domain matches, requesting E2E encryption" + ) + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd + } else if account.params != nil && !account.params!.instantMessagingEncryptionMandatory { + if core != nil && LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core!) { + Log.info( + "\(CallViewModel.TAG) Account is in interop mode but LIME is available, requesting E2E encryption" + ) + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd + } else { + Log.info( + "\(CallViewModel.TAG) Account is in interop mode but LIME isn't available, disabling E2E encryption" + ) + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + Log.error( + "\(CallViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remoteAddress?.asStringUriOnly() ?? "")" + ) + return nil } + return params + } else { + return nil } } catch { - DispatchQueue.main.async { - self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true - } - Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!") + Log.error("\(CallViewModel.TAG) Can't create ConferenceParams \(remoteAddress?.asStringUriOnly() ?? "")") + return nil + } + } else { + return nil + } + } + + func goToConversation(model: ConversationModel) { + if self.operationInProgress == false { + DispatchQueue.main.async { + self.operationInProgress = true + } + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + self.displayedConversation = model + self.operationInProgress = false + } + } else { + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } } @@ -1331,13 +1369,13 @@ class CallViewModel: ObservableObject { } DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false self.displayedConversation = model + self.operationInProgress = false } } else { DispatchQueue.main.async { - self.operationInProgress = false self.displayedConversation = model + self.operationInProgress = false } } } else if state == ChatRoom.State.CreationFailed { diff --git a/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift b/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift index 38390ac93..061e60269 100644 --- a/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift +++ b/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift @@ -43,7 +43,7 @@ class ContactViewModel: ObservableObject { let account = core.defaultAccount if account == nil { Log.error( - "\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!" + "\(ConversationForwardMessageViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())" ) return } @@ -53,37 +53,42 @@ class ContactViewModel: ObservableObject { } do { - let params: ChatRoomParams = try core.createDefaultChatRoomParams() + let params = try core.createConferenceParams(conference: nil) + params.chatEnabled = true params.groupEnabled = false - params.subject = "Dummy subject" - params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "") + params.account = account - let sameDomain = remote.domain == account?.params?.domain ?? "" - if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain { - Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation") - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true - } else if !StartConversationViewModel.isEndToEndEncryptionMandatory() { + guard let chatParams = params.chatParams else { return } + chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + + let sameDomain = remote.domain == CorePreferences.defaultDomain && remote.domain == account!.params?.domain + if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) { + Log.info("\(ConversationForwardMessageViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation") + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd + } else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) { if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) { Log.info( - "\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation" + "\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.info( - "\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation" + "\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.encryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.error( - "\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())" + "\(ConversationForwardMessageViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())" ) + DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error" + ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" ToastViewModel.shared.displayToast = true } return @@ -94,85 +99,56 @@ class ContactViewModel: ObservableObject { let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants) if existingChatRoom == nil { Log.info( - "\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account " - + "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it" + "\(ConversationForwardMessageViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it" ) - let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants) - if params.backend == ChatRoom.Backend.FlexisipChat { - if chatRoom.state == ChatRoom.State.Created { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } + + do { + let chatRoom = try core.createChatRoom(params: params, participants: participants) + if chatParams.backend == ChatRoom.Backend.FlexisipChat { + let state = chatRoom.state + if state == ChatRoom.State.Created { + let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(ConversationForwardMessageViewModel.TAG) 1-1 conversation \(chatRoomId) has been created") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false + let model = ConversationModel(chatRoom: chatRoom) + DispatchQueue.main.async { self.displayedConversation = model + self.operationInProgress = false } } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it") + self.chatRoomAddDelegate(core: core, chatRoom: chatRoom) } } else { - Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it") - self.chatRoomAddDelegate(core: core, chatRoom: chatRoom) - } - } else { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } + let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(ConversationForwardMessageViewModel.TAG) Conversation successfully created \(chatRoomId)") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { + let model = ConversationModel(chatRoom: chatRoom) DispatchQueue.main.async { - self.operationInProgress = false self.displayedConversation = model + self.operationInProgress = false } } + } catch { + Log.error("\(ConversationForwardMessageViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())") + + DispatchQueue.main.async { + self.operationInProgress = false + ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" + ToastViewModel.shared.displayToast = true + } } } else { Log.warn( - "\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!" + "\(ConversationForwardMessageViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!" ) - let model = ConversationModel(chatRoom: existingChatRoom!) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } } catch { - DispatchQueue.main.async { - self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true - } - Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!") } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift index a5d2fe4bd..3d88aac23 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift @@ -223,6 +223,11 @@ struct ConversationForwardMessageFragment: View { } .navigationTitle("") .navigationBarHidden(true) + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { + MagicSearchSingleton.shared.searchForSuggestions() + } + } .onDisappear { DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) { magicSearch.searchForContacts( diff --git a/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift index 19bb3f3cd..47f6f1144 100644 --- a/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift @@ -187,9 +187,7 @@ struct StartConversationFragment: View { ContactsListFragment(contactViewModel: ContactViewModel(), contactsListViewModel: ContactsListViewModel(), showingSheet: .constant(false) , startCallFunc: { addr in - withAnimation { startConversationViewModel.createOneToOneChatRoomWith(remote: addr) - } }) .padding(.horizontal, 16) @@ -266,10 +264,8 @@ struct StartConversationFragment: View { var suggestionsList: some View { ForEach(0.. indexMessageEventLogId { self.conversationMessagesSection[0].rows[indexMessageEventLogId].eventModel.eventLogId = message.messageId } - if let indexMessage = indexMessage { + if let indexMessage = indexMessage, !self.conversationMessagesSection.isEmpty, !self.conversationMessagesSection[0].rows.isEmpty, self.conversationMessagesSection[0].rows.count > indexMessage { self.conversationMessagesSection[0].rows[indexMessage].message.status = statusTmp ?? .error } } diff --git a/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift index 5eb1cfa8a..760dce214 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift @@ -95,6 +95,10 @@ class StartConversationViewModel: ObservableObject { participantsTmp.append(participant.address) } + DispatchQueue.main.async { + self.participants.removeAll() + } + if account!.params != nil { let chatRoom = try core.createChatRoom(params: chatRoomParams, participants: participantsTmp) @@ -106,20 +110,9 @@ class StartConversationViewModel: ObservableObject { ) let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } else { Log.info( @@ -132,20 +125,9 @@ class StartConversationViewModel: ObservableObject { Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id) \(groupChatRoomSubject)") let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } } @@ -163,11 +145,11 @@ class StartConversationViewModel: ObservableObject { } func createOneToOneChatRoomWith(remote: Address) { - coreContext.doOnCoreQueue { core in + CoreContext.shared.doOnCoreQueue { core in let account = core.defaultAccount if account == nil { Log.error( - "\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())!" + "\(StartConversationViewModel.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())" ) return } @@ -177,37 +159,42 @@ class StartConversationViewModel: ObservableObject { } do { - let params: ChatRoomParams = try core.createDefaultChatRoomParams() + let params = try core.createConferenceParams(conference: nil) + params.chatEnabled = true params.groupEnabled = false - params.subject = "Dummy subject" - params.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "") + params.account = account - let sameDomain = remote.domain == account?.params?.domain ?? "" - if StartConversationViewModel.isEndToEndEncryptionMandatory() && sameDomain { - Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating a E2E conversation") - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true - } else if !StartConversationViewModel.isEndToEndEncryptionMandatory() { + guard let chatParams = params.chatParams else { return } + chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + + let sameDomain = remote.domain == CorePreferences.defaultDomain && remote.domain == account!.params?.domain + if account!.params != nil && (account!.params!.instantMessagingEncryptionMandatory && sameDomain) { + Log.info("\(StartConversationViewModel.TAG) Account is in secure mode & domain matches, creating an E2E encrypted conversation") + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd + } else if account!.params != nil && (!account!.params!.instantMessagingEncryptionMandatory) { if LinphoneUtils.isEndToEndEncryptedChatAvailable(core: core) { Log.info( - "\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating a E2E conversation" + "\(StartConversationViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation" ) - params.backend = ChatRoom.Backend.FlexisipChat - params.encryptionEnabled = true + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd } else { Log.info( "\(StartConversationViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation" ) - params.backend = ChatRoom.Backend.Basic - params.encryptionEnabled = false + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None } } else { Log.error( "\(StartConversationViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())" ) + DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_invalid_participant_error" + ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" ToastViewModel.shared.displayToast = true } return @@ -218,85 +205,56 @@ class StartConversationViewModel: ObservableObject { let existingChatRoom = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants) if existingChatRoom == nil { Log.info( - "\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account " - + "\(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it" + "\(StartConversationViewModel.TAG) No existing 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) was found for given parameters, let's create it" ) - let chatRoom = try core.createChatRoom(params: params, localAddr: localAddress, participants: participants) - if params.backend == ChatRoom.Backend.FlexisipChat { - if chatRoom.state == ChatRoom.State.Created { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(id) has been created") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } + + do { + let chatRoom = try core.createChatRoom(params: params, participants: participants) + if chatParams.backend == ChatRoom.Backend.FlexisipChat { + let state = chatRoom.state + if state == ChatRoom.State.Created { + let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(StartConversationViewModel.TAG) 1-1 conversation \(chatRoomId) has been created") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false + let model = ConversationModel(chatRoom: chatRoom) + DispatchQueue.main.async { self.displayedConversation = model + self.operationInProgress = false } } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet (state is \(state)), wait for it") + self.chatRoomAddDelegate(core: core, chatRoom: chatRoom) } } else { - Log.info("\(StartConversationViewModel.TAG) Conversation isn't in Created state yet, wait for it") - self.chatRoomAddDelegate(core: core, chatRoom: chatRoom) - } - } else { - let id = LinphoneUtils.getChatRoomId(room: chatRoom) - Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(id)") - - let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } + let chatRoomId = LinphoneUtils.getConversationId(chatRoom: chatRoom) + Log.info("\(StartConversationViewModel.TAG) Conversation successfully created \(chatRoomId)") - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { + let model = ConversationModel(chatRoom: chatRoom) DispatchQueue.main.async { - self.operationInProgress = false self.displayedConversation = model + self.operationInProgress = false } } + } catch { + Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())") + + DispatchQueue.main.async { + self.operationInProgress = false + ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" + ToastViewModel.shared.displayToast = true + } } } else { Log.warn( "\(StartConversationViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!" ) - let model = ConversationModel(chatRoom: existingChatRoom!) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } } catch { - DispatchQueue.main.async { - self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true - } - Log.error("\(StartConversationViewModel.TAG) Failed to create 1-1 conversation with \(remote.asStringUriOnly())!") } } } @@ -304,9 +262,9 @@ class StartConversationViewModel: ObservableObject { func chatRoomAddDelegate(core: Core, chatRoom: ChatRoom) { self.chatRoomDelegate = ChatRoomDelegateStub(onStateChanged: { (chatRoom: ChatRoom, state: ChatRoom.State) in let state = chatRoom.state - let id = LinphoneUtils.getChatRoomId(room: chatRoom) + let chatRoomId = LinphoneUtils.getChatRoomId(room: chatRoom) if state == ChatRoom.State.CreationFailed { - Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!") + Log.error("\(StartConversationViewModel.TAG) Conversation \(chatRoomId) creation has failed!") if let chatRoomDelegate = self.chatRoomDelegate { chatRoom.removeDelegate(delegate: chatRoomDelegate) } @@ -329,20 +287,9 @@ class StartConversationViewModel: ObservableObject { self.chatRoomDelegate = nil let model = ConversationModel(chatRoom: chatRoom) - if self.operationInProgress == false { - DispatchQueue.main.async { - self.operationInProgress = true - } - - DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { - self.operationInProgress = false - self.displayedConversation = model - } - } else { - DispatchQueue.main.async { - self.operationInProgress = false - self.displayedConversation = model - } + DispatchQueue.main.async { + self.displayedConversation = model + self.operationInProgress = false } } else if state == ChatRoom.State.CreationFailed { Log.error("\(StartConversationViewModel.TAG) Conversation \(id) creation has failed!") diff --git a/Linphone/Utils/LinphoneUtils.swift b/Linphone/Utils/LinphoneUtils.swift index 549ba4a6a..b1265a110 100644 --- a/Linphone/Utils/LinphoneUtils.swift +++ b/Linphone/Utils/LinphoneUtils.swift @@ -100,4 +100,17 @@ class LinphoneUtils: NSObject { return nil } } + + public class func getConversationId(chatRoom: ChatRoom) -> String { + return chatRoom.identifier ?? "" + + } + + public class func getDefaultAccount() -> Account? { + return CoreContext.shared.mCore.defaultAccount ?? (CoreContext.shared.mCore.accountList.first ?? nil) + } + + public class func getAccountForAddress(address: Address) -> Account? { + return CoreContext.shared.mCore.accountList.first { $0.params?.identityAddress?.weakEqual(address2: address) == true } + } }