diff --git a/Linphone/GeneratedGit.swift b/Linphone/GeneratedGit.swift index 50e57d835..5a07cbbbf 100644 --- a/Linphone/GeneratedGit.swift +++ b/Linphone/GeneratedGit.swift @@ -2,6 +2,6 @@ import Foundation public enum AppGitInfo { public static let branch = "master" - public static let commit = "9cc8923e3" + public static let commit = "75e96ed8a" public static let tag = "6.1.0-alpha" } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index 0b3182e4d..8d14f9212 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -23,8 +23,9 @@ import linphonesw // swiftlint:disable type_body_length struct ContactInnerActionsFragment: View { - @ObservedObject var contactsManager = ContactsManager.shared @ObservedObject private var telecomManager = TelecomManager.shared + @ObservedObject private var contactsManager = ContactsManager.shared + @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared @EnvironmentObject var contactAvatarModel: ContactAvatarModel @EnvironmentObject var contactsListViewModel: ContactsListViewModel @@ -37,6 +38,8 @@ struct ContactInnerActionsFragment: View { @Binding var isShowDeletePopup: Bool @Binding var isShowDismissPopup: Bool @Binding var isShowTrustLevelPopup: Bool + @Binding var isShowMediaFilesFragment: Bool + @Binding var isShowDocumentsFilesFragment: Bool @Binding var isShowIncreaseTrustLevelPopup: Bool @Binding var isShowEditContactFragmentInContactDetails: Bool @@ -386,6 +389,87 @@ struct ContactInnerActionsFragment: View { .transition(.move(edge: .top)) } + if sharedMainViewModel.displayedFriendExistingChatRoom != nil { + HStack(alignment: .center) { + Text("conversation_details_media_documents_title") + .default_text_style_800(styleSize: 16) + + Spacer() + + Image("caret-up") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .hidden() + } + .padding(.top, 20) + .padding(.bottom, 10) + .padding(.horizontal, 16) + .background(Color.gray100) + + VStack(spacing: 0) { + Button { + withAnimation { + isShowMediaFilesFragment = true + } + } label: { + HStack { + Image("image") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .padding(.all, 10) + + Text("conversation_menu_media_files") + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) + } + + VStack { + Divider() + } + .padding(.horizontal) + + Button { + withAnimation { + isShowDocumentsFilesFragment = true + } + } label: { + HStack { + Image("file-pdf") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .padding(.all, 10) + + Text("conversation_menu_documents_files") + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + .fixedSize(horizontal: false, vertical: true) + Spacer() + } + .padding(.vertical, 15) + .padding(.horizontal, 20) + } + } + .background(.white) + .cornerRadius(15) + .padding(.horizontal) + .zIndex(-1) + .transition(.move(edge: .top)) + } + HStack(alignment: .center) { Text("contact_details_actions_title") .default_text_style_800(styleSize: 16) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index 3aa2685b2..cdf38530c 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -34,6 +34,8 @@ struct ContactInnerFragment: View { @State var cnContact: CNContact? @State private var presentingEditContact = false + @State private var isShowMediaFilesFragment = false + @State private var isShowDocumentsFilesFragment = false @Binding var isShowDeletePopup: Bool @Binding var showingSheet: Bool @@ -280,6 +282,8 @@ struct ContactInnerFragment: View { isShowDeletePopup: $isShowDeletePopup, isShowDismissPopup: $isShowDismissPopup, isShowTrustLevelPopup: $isShowTrustLevelPopup, + isShowMediaFilesFragment: $isShowMediaFilesFragment, + isShowDocumentsFilesFragment: $isShowDocumentsFilesFragment, isShowIncreaseTrustLevelPopup: $isShowIncreaseTrustLevelPopup, isShowEditContactFragmentInContactDetails: $isShowEditContactFragmentInContactDetails, geometry: geometry, @@ -287,9 +291,18 @@ struct ContactInnerFragment: View { ) .onAppear { contactsListViewModel.fetchDevicesAndTrust() + contactsListViewModel.getOneToOneChatRoomWith() } .onChange(of: SharedMainViewModel.shared.displayedFriend?.id) { _ in + isShowMediaFilesFragment = false + isShowDocumentsFilesFragment = false + SharedMainViewModel.shared.displayedFriendExistingChatRoom = nil + contactsListViewModel.fetchDevicesAndTrust() + contactsListViewModel.getOneToOneChatRoomWith() + } + .onDisappear { + SharedMainViewModel.shared.displayedFriendExistingChatRoom = nil } } .frame(maxWidth: SharedMainViewModel.shared.maxWidth) @@ -311,6 +324,22 @@ struct ContactInnerFragment: View { .edgesIgnoringSafeArea(.vertical) } } + + if isShowMediaFilesFragment { + ConversationMediaListFragment( + isShowMediaFilesFragment: $isShowMediaFilesFragment + ) + .zIndex(5) + .transition(.move(edge: .trailing)) + } + + if isShowDocumentsFilesFragment { + ConversationDocumentsListFragment( + isShowDocumentsFilesFragment: $isShowDocumentsFilesFragment + ) + .zIndex(5) + .transition(.move(edge: .trailing)) + } } } } diff --git a/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift b/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift index c23f4b178..0ae56cf0d 100644 --- a/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift +++ b/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift @@ -337,5 +337,102 @@ class ContactsListViewModel: ObservableObject { devices = devicesList } } + + func getOneToOneChatRoomWith() { + CoreContext.shared.doOnCoreQueue { core in + if let contactAvatarModel = SharedMainViewModel.shared.displayedFriend { + var remote: Address? + + if contactAvatarModel.addresses.count == 1 { + do { + remote = try Factory.Instance.createAddress(addr: contactAvatarModel.address) + } catch { + Log.error("\(Self.TAG) unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error)") + return + } + } else if contactAvatarModel.addresses.isEmpty && + contactAvatarModel.phoneNumbersWithLabel.count == 1 { + + if let firstPhone = contactAvatarModel.phoneNumbersWithLabel.first, + let address = core.interpretUrl( + url: firstPhone.phoneNumber, + applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core) + ) { + remote = address + } + } + + guard let remote else { + Log.error("\(Self.TAG) No valid remote address found") + return + } + + let account = core.defaultAccount + if account == nil { + Log.error( + "\(Self.TAG) No default account found, can't create conversation with \(remote.asStringUriOnly())" + ) + return + } + + do { + let params = try core.createConferenceParams(conference: nil) + params.chatEnabled = true + params.groupEnabled = false + params.subject = NSLocalizedString("conversation_one_to_one_hidden_subject", comment: "") + params.account = account + + guard let chatParams = params.chatParams else { return } + chatParams.ephemeralLifetime = 0 // Make sure ephemeral is disabled by default + + let sameDomain = remote.domain == AppServices.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( + "\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME is available, creating an E2E encrypted conversation" + ) + chatParams.backend = ChatRoom.Backend.FlexisipChat + params.securityLevel = Conference.SecurityLevel.EndToEnd + } else { + Log.info( + "\(ConversationForwardMessageViewModel.TAG) Account is in interop mode but LIME isn't available, creating a SIP simple conversation" + ) + chatParams.backend = ChatRoom.Backend.Basic + params.securityLevel = Conference.SecurityLevel.None + } + } else { + Log.error( + "\(ConversationForwardMessageViewModel.TAG) Account is in secure mode, can't chat with SIP address of different domain \(remote.asStringUriOnly())" + ) + + DispatchQueue.main.async { + SharedMainViewModel.shared.operationInProgress = false + ToastViewModel.shared.show("Failed_to_create_conversation_error") + } + return + } + + let participants = [remote] + let localAddress = account?.params?.identityAddress + if let existingChatRoomTmp = core.searchChatRoom(params: params, localAddr: localAddress, remoteAddr: nil, participants: participants) { + Log.warn( + "\(ConversationForwardMessageViewModel.TAG) A 1-1 conversation between local account \(localAddress?.asStringUriOnly() ?? "") and remote \(remote.asStringUriOnly()) for given parameters already exists!" + ) + + let conversationModel = ConversationModel(chatRoom: existingChatRoomTmp) + + DispatchQueue.main.async { + SharedMainViewModel.shared.displayedFriendExistingChatRoom = conversationModel + } + } + } catch { + } + } + } + } } // swiftlint:enable line_length diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationDocumentsListFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationDocumentsListFragment.swift index fcf928d69..8ef3a7467 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationDocumentsListFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationDocumentsListFragment.swift @@ -21,7 +21,6 @@ import SwiftUI import linphonesw struct ConversationDocumentsListFragment: View { - @EnvironmentObject var conversationViewModel: ConversationViewModel @StateObject private var conversationDocumentsListViewModel = ConversationDocumentsListViewModel() diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 27ba3099c..aaab69720 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -1603,7 +1603,6 @@ struct ConversationFragment: View { ConversationMediaListFragment( isShowMediaFilesFragment: $isShowMediaFilesFragment ) - .environmentObject(conversationViewModel) .zIndex(5) .transition(.move(edge: .trailing)) } @@ -1612,7 +1611,6 @@ struct ConversationFragment: View { ConversationDocumentsListFragment( isShowDocumentsFilesFragment: $isShowDocumentsFilesFragment ) - .environmentObject(conversationViewModel) .zIndex(5) .transition(.move(edge: .trailing)) } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationMediaListFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationMediaListFragment.swift index 475692242..14d61389e 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationMediaListFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationMediaListFragment.swift @@ -21,8 +21,6 @@ import SwiftUI import linphonesw struct ConversationMediaListFragment: View { - @EnvironmentObject var conversationViewModel: ConversationViewModel - @StateObject private var conversationMediaListViewModel = ConversationMediaListViewModel() @Binding var isShowMediaFilesFragment: Bool diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationDocumentsListViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationDocumentsListViewModel.swift index 29aeb0ed7..f142d8547 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationDocumentsListViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationDocumentsListViewModel.swift @@ -36,6 +36,9 @@ final class ConversationDocumentsListViewModel: ObservableObject { if let conversationModelTmp = SharedMainViewModel.shared.displayedConversation { self.conversationModel = conversationModelTmp loadDocumentsList() + } else if let conversationModelTmp = SharedMainViewModel.shared.displayedFriendExistingChatRoom { + self.conversationModel = conversationModelTmp + loadDocumentsList() } } diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationMediaListViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationMediaListViewModel.swift index 105ccbc83..e386051f0 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationMediaListViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationMediaListViewModel.swift @@ -36,6 +36,9 @@ final class ConversationMediaListViewModel: ObservableObject { if let conversationModelTmp = SharedMainViewModel.shared.displayedConversation { self.conversationModel = conversationModelTmp loadMediaList() + } else if let conversationModelTmp = SharedMainViewModel.shared.displayedFriendExistingChatRoom { + self.conversationModel = conversationModelTmp + loadMediaList() } } diff --git a/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift b/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift index a84ac2804..9044ba316 100644 --- a/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift +++ b/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift @@ -45,6 +45,8 @@ class SharedMainViewModel: ObservableObject { @Published var displayedConversation: ConversationModel? @Published var displayedMeeting: MeetingModel? + @Published var displayedFriendExistingChatRoom: ConversationModel? + @Published var dialPlansList: [DialPlan?] = [] @Published var dialPlansLabelList: [String] = [] @Published var dialPlansShortLabelList: [String] = []