From f534ccb5602b18bb01abbe56631e8413ad19150d Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 3 Jun 2025 11:33:56 +0200 Subject: [PATCH] Refactored HistoryView --- Linphone/Contacts/ContactsManager.swift | 31 ++- .../Contacts/Fragments/ContactFragment.swift | 4 +- .../ContactInnerActionsFragment.swift | 65 ++--- .../Fragments/ContactInnerFragment.swift | 6 +- .../Fragments/ContactsInnerFragment.swift | 2 +- .../Fragments/ContactsListBottomSheet.swift | 8 +- .../Fragments/ContactsListFragment.swift | 124 +++++----- .../Fragments/EditContactFragment.swift | 161 +++++++----- .../FavoriteContactsListFragment.swift | 67 ++--- .../Contacts/Model/ContactAvatarModel.swift | 124 ++++++---- .../ViewModel/ContactsListViewModel.swift | 65 ++++- .../ViewModel/EditContactViewModel.swift | 12 +- Linphone/UI/Main/ContentView.swift | 204 ++++++++------- .../Fragments/ConversationInfoFragment.swift | 8 +- .../Fragments/HistoryContactFragment.swift | 6 +- .../History/Fragments/HistoryFragment.swift | 35 +-- .../Fragments/HistoryListBottomSheet.swift | 46 ++-- .../Fragments/HistoryListFragment.swift | 232 ++++++++++-------- Linphone/UI/Main/History/HistoryView.swift | 23 +- .../UI/Main/History/Model/HistoryModel.swift | 56 ++--- .../ViewModel/HistoryListViewModel.swift | 8 +- .../ViewModel/AccountProfileViewModel.swift | 18 +- .../Main/Viewmodel/SharedMainViewModel.swift | 2 +- Linphone/Utils/Avatar.swift | 7 +- Linphone/Utils/Extensions/URLExtension.swift | 7 + Linphone/Utils/MagicSearchSingleton.swift | 7 +- Linphone/Utils/ShareSheetController.swift | 38 ++- LinphoneApp.xcodeproj/project.pbxproj | 4 - 28 files changed, 741 insertions(+), 629 deletions(-) diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 5fd6bbf08..11bc99d04 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -42,7 +42,14 @@ final class ContactsManager: ObservableObject { @Published var lastSearch: [SearchResult] = [] @Published var lastSearchSuggestions: [SearchResult] = [] - @Published var avatarListModel: [ContactAvatarModel] = [] + @Published var avatarListModel: [ContactAvatarModel] = [] { + didSet { + setupSubscriptions() + } + } + + @Published var starredChangeTrigger = UUID() + private var cancellables = Set() private var coreDelegate: CoreDelegate? private var friendListDelegate: FriendListDelegate? @@ -97,6 +104,7 @@ final class ContactsManager: ObservableObject { print("\(#function) - failed to request access", error) self.addFriendListDelegate() self.addCoreDelegate(core: core) + MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) return } if granted { @@ -238,9 +246,15 @@ final class ContactsManager: ObservableObject { self.saveFriend(result: result, contact: contact, existingFriend: existingFriend) { resultFriend in if resultFriend != nil { if linphoneFriend && existingFriend == nil { - _ = self.linphoneFriendList?.addFriend(linphoneFriend: resultFriend!) + if let linphoneFL = self.linphoneFriendList { + _ = linphoneFL.addFriend(linphoneFriend: resultFriend!) + linphoneFL.updateSubscriptions() + } } else if existingFriend == nil { - _ = self.friendList?.addLocalFriend(linphoneFriend: resultFriend!) + if let friendListTmp = self.friendList { + _ = friendListTmp.addLocalFriend(linphoneFriend: resultFriend!) + friendListTmp.updateSubscriptions() + } } } completion() @@ -496,6 +510,17 @@ final class ContactsManager: ObservableObject { core.addDelegate(delegate: self.coreDelegate!) } } + + private func setupSubscriptions() { + cancellables.removeAll() + for contact in avatarListModel { + contact.$starred + .sink { [weak self] _ in + self?.starredChangeTrigger = UUID() // 🔁 Déclenche le refresh de la vue + } + .store(in: &cancellables) + } + } } struct PhoneNumber { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift index 1d69e1c6f..7405352dd 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift @@ -44,7 +44,7 @@ struct ContactFragment: View { .presentationDetents([.fraction(0.2)]) } .sheet(isPresented: $showShareSheet) { - ShareSheet(friendToShare: ContactsManager.shared.lastSearch[SharedMainViewModel.shared.indexDisplayedFriend!].friend!) + ShareSheet(friendToShare: contactAvatarModel) .presentationDetents([.medium]) .edgesIgnoringSafeArea(.bottom) } @@ -54,7 +54,7 @@ struct ContactFragment: View { ContactListBottomSheet(contactsListViewModel: contactsListViewModel, showingSheet: $showingSheet) } onDismiss: {} .sheet(isPresented: $showShareSheet) { - ShareSheet(friendToShare: ContactsManager.shared.lastSearch[SharedMainViewModel.shared.indexDisplayedFriend!].friend!) + ShareSheet(friendToShare: contactAvatarModel) .edgesIgnoringSafeArea(.bottom) } } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index 065ec2a93..18a5addc3 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -106,7 +106,7 @@ struct ContactInnerActionsFragment: View { showingSheet.toggle() } - if contactAvatarModel.friend != nil && !contactAvatarModel.friend!.phoneNumbers.isEmpty + if !contactAvatarModel.phoneNumbersWithLabel.isEmpty || index < contactAvatarModel.addresses.count - 1 { VStack { Divider() @@ -114,22 +114,22 @@ struct ContactInnerActionsFragment: View { .padding(.horizontal) } } - if contactAvatarModel.friend != nil { - ForEach(0.. Void var body: some View { - ForEach(0.. Void + + var body: some View { + HStack { HStack { - HStack { - if index == 0 - || contactsManager.avatarListModel[index].name.lowercased().folding( - options: .diacriticInsensitive, - locale: .current - ).first - != contactsManager.avatarListModel[index-1].name.lowercased().folding( - options: .diacriticInsensitive, - locale: .current - ).first { - Text( - String( - (contactsManager.avatarListModel[index].name.uppercased().folding( - options: .diacriticInsensitive, - locale: .current - ).first)!)) + if index <= 0 + || (index < contactsManager.avatarListModel.count && contactAvatarModel.name.lowercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first + != contactsManager.avatarListModel[index-1].name.lowercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first) { + Text( + String( + (contactAvatarModel.name.uppercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first) ?? "?")) + .contact_text_style_500(styleSize: 20) + .frame(width: 18) + .padding(.leading, -5) + .padding(.trailing, 10) + } else { + Text("") .contact_text_style_500(styleSize: 20) .frame(width: 18) .padding(.leading, -5) .padding(.trailing, 10) - } else { - Text("") - .contact_text_style_500(styleSize: 20) - .frame(width: 18) - .padding(.leading, -5) - .padding(.trailing, 10) - } - - if index < contactsManager.avatarListModel.count - && contactsManager.avatarListModel[index].friend!.photo != nil - && !contactsManager.avatarListModel[index].friend!.photo!.isEmpty { - Avatar(contactAvatarModel: contactsManager.avatarListModel[index], avatarSize: 50) - } else { - Image("profil-picture-default") - .resizable() - .frame(width: 50, height: 50) - .clipShape(Circle()) - } - Text(contactsManager.avatarListModel[index].name) - .default_text_style(styleSize: 16) - .frame(maxWidth: .infinity, alignment: .leading) - .foregroundStyle(Color.orangeMain500) - } - } - .frame(height: 50) - .buttonStyle(.borderless) - .listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20)) - .listRowSeparator(.hidden) - .background(.white) - .onTapGesture { - withAnimation { - SharedMainViewModel.shared.indexDisplayedFriend = index } - if index < contactsManager.avatarListModel.count && contactsManager.avatarListModel[index].friend != nil - && contactsManager.avatarListModel[index].friend!.address != nil { - startCallFunc(contactsManager.avatarListModel[index].friend!.address!) - } + Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 50) + + Text(contactAvatarModel.name) + .default_text_style(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .foregroundStyle(Color.orangeMain500) } - .onLongPressGesture(minimumDuration: 0.2) { - if index < contactsManager.avatarListModel.count && contactsManager.avatarListModel[index].friend != nil { - contactsListViewModel.selectedFriend = contactsManager.avatarListModel[index].friend - showingSheet.toggle() - } + } + .frame(height: 50) + .buttonStyle(.borderless) + .listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20)) + .listRowSeparator(.hidden) + .background(.white) + .onTapGesture { + withAnimation { + SharedMainViewModel.shared.displayedFriend = contactAvatarModel } + + if contactAvatarModel.friend != nil + && contactAvatarModel.friend!.address != nil { + startCallFunc(contactAvatarModel.friend!.address!) + } + } + .onLongPressGesture(minimumDuration: 0.2) { + contactsListViewModel.selectedFriend = contactAvatarModel + showingSheet.toggle() } } } diff --git a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift index d43cd7f0a..8b2542e76 100644 --- a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift @@ -32,6 +32,7 @@ struct EditContactFragment: View { @Binding var isShowEditContactFragment: Bool @Binding var isShowDismissPopup: Bool + let isShowEditContactFragmentAddress: String @State private var delayedColor = Color.white @@ -46,10 +47,11 @@ struct EditContactFragment: View { @State private var selectedImage: UIImage? @State private var removedImage = false - init(friend: Friend? = nil, isShowEditContactFragment: Binding, isShowDismissPopup: Binding) { - _editContactViewModel = StateObject(wrappedValue: EditContactViewModel(friend: friend)) + init(contactAvatarModel: ContactAvatarModel? = nil, isShowEditContactFragment: Binding, isShowDismissPopup: Binding, isShowEditContactFragmentAddress: String = "") { + _editContactViewModel = StateObject(wrappedValue: EditContactViewModel(contactAvatarModel: contactAvatarModel)) self._isShowEditContactFragment = isShowEditContactFragment self._isShowDismissPopup = isShowDismissPopup + self.isShowEditContactFragmentAddress = isShowEditContactFragmentAddress } var body: some View { @@ -152,18 +154,9 @@ struct EditContactFragment: View { VStack(spacing: 0) { VStack(spacing: 0) { VStack(spacing: 0) { - if editContactViewModel.selectedEditFriend != nil - && editContactViewModel.selectedEditFriend!.photo != nil - && !editContactViewModel.selectedEditFriend!.photo!.isEmpty && selectedImage == nil && !removedImage { + if editContactViewModel.selectedEditFriend != nil && selectedImage == nil && !removedImage { - Avatar(contactAvatarModel: - ContactAvatarModel( - friend: editContactViewModel.selectedEditFriend!, - name: editContactViewModel.selectedEditFriend?.name ?? "", - address: editContactViewModel.selectedEditFriend?.address?.asStringUriOnly() ?? "", - withPresence: false - ), avatarSize: 100 - ) + Avatar(contactAvatarModel: editContactViewModel.selectedEditFriend!, avatarSize: 100) } else if selectedImage == nil { Image("profil-picture-default") @@ -179,9 +172,8 @@ struct EditContactFragment: View { } if editContactViewModel.selectedEditFriend != nil - && editContactViewModel.selectedEditFriend!.photo != nil - && !editContactViewModel.selectedEditFriend!.photo!.isEmpty - && (editContactViewModel.selectedEditFriend!.photo!.suffix(11) != "default.png" || selectedImage != nil) && !removedImage { + && !editContactViewModel.selectedEditFriend!.photo.isEmpty + && (editContactViewModel.selectedEditFriend!.photo.suffix(11) != "default.png" || selectedImage != nil) && !removedImage { HStack { Spacer() @@ -213,6 +205,7 @@ struct EditContactFragment: View { removedImage = false } } + showPhotoPicker = false } } .edgesIgnoringSafeArea(.all) @@ -265,6 +258,7 @@ struct EditContactFragment: View { removedImage = false } } + showPhotoPicker = false } } .edgesIgnoringSafeArea(.all) @@ -323,13 +317,13 @@ struct EditContactFragment: View { .default_text_style_700(styleSize: 15) .padding(.bottom, -5) - ForEach(0.. 0 { + if historyListViewModel != nil && historyListViewModel!.missedCallsCount > 0 { VStack { HStack { Text( - historyListViewModel.missedCallsCount < 99 - ? String(historyListViewModel.missedCallsCount) + historyListViewModel!.missedCallsCount < 99 + ? String(historyListViewModel!.missedCallsCount) : "99+" ) .foregroundStyle(.white) @@ -194,17 +192,14 @@ struct ContentView: View { .padding(.bottom, 30) .padding(.leading, 30) } - */ Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 1) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedConversation = nil sharedMainViewModel.displayedMeeting = nil - /* - if historyListViewModel.missedCallsCount > 0 { - historyListViewModel.resetMissedCallsCount() + if historyListViewModel != nil && historyListViewModel!.missedCallsCount > 0 { + historyListViewModel!.resetMissedCallsCount() } - */ }, label: { VStack { Image("phone") @@ -249,7 +244,7 @@ struct ContentView: View { */ Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 2) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedCall = nil sharedMainViewModel.displayedMeeting = nil }, label: { @@ -275,7 +270,7 @@ struct ContentView: View { Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 3) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedCall = nil sharedMainViewModel.displayedConversation = nil }, label: { @@ -437,7 +432,7 @@ struct ContentView: View { Menu { if sharedMainViewModel.indexView == 0 { Button { - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil isMenuOpen = false magicSearch.allContact = true magicSearch.searchForContacts( @@ -456,7 +451,7 @@ struct ContentView: View { } Button { - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil isMenuOpen = false magicSearch.allContact = false magicSearch.searchForContacts( @@ -523,8 +518,8 @@ struct ContentView: View { magicSearch.currentFilter = "" magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } else if sharedMainViewModel.indexView == 1 { - //historyListViewModel.resetFilterCallLogs() + } else if sharedMainViewModel.indexView == 1 && historyListViewModel != nil { + historyListViewModel!.resetFilterCallLogs() } else if sharedMainViewModel.indexView == 2 { //conversationsListViewModel.resetFilterConversations() } else if sharedMainViewModel.indexView == 3 { @@ -570,10 +565,10 @@ struct ContentView: View { magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) } else if sharedMainViewModel.indexView == 1 { - if text.isEmpty { - //historyListViewModel.resetFilterCallLogs() - } else { - //historyListViewModel.filterCallLogs(filter: text) + if text.isEmpty && historyListViewModel != nil { + historyListViewModel!.resetFilterCallLogs() + } else if historyListViewModel != nil { + historyListViewModel!.filterCallLogs(filter: text) } } else if sharedMainViewModel.indexView == 2 { if text.isEmpty { @@ -612,8 +607,8 @@ struct ContentView: View { magicSearch.currentFilter = newValue magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } else if sharedMainViewModel.indexView == 1 { - //historyListViewModel.filterCallLogs(filter: text) + } else if sharedMainViewModel.indexView == 1 && historyListViewModel != nil { + historyListViewModel!.filterCallLogs(filter: text) } else if sharedMainViewModel.indexView == 2 { //conversationsListViewModel.filterConversations(filter: text) } else if sharedMainViewModel.indexView == 3 { @@ -676,33 +671,38 @@ struct ContentView: View { } } } else if sharedMainViewModel.indexView == 1 { - //TODO a changer - NavigationView { - ZStack(alignment: .bottomTrailing) { + if let historyListVM = historyListViewModel { + HistoryView( + isShowStartCallFragment: $isShowStartCallFragment, + isShowEditContactFragment: $isShowEditContactFragment, + text: $text, + isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress + ) + .environmentObject(historyListVM) + .roundedCorner(25, corners: [.topRight, .topLeft]) + .shadow( + color: (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + ? .white.opacity(0.0) + : .black.opacity(0.2), + radius: 25 + ) + } else { + NavigationView { + VStack { + Spacer() + + ProgressView() + .controlSize(.large) + + Spacer() + } + .onAppear { + historyListViewModel = HistoryListViewModel() + } } } - .navigationViewStyle(.stack) - /* - HistoryView( - historyListViewModel: historyListViewModel, - historyViewModel: historyViewModel, - contactsListViewModel: contactsListViewModel, - editContactViewModel: editContactViewModel, - index: $index, - isShowStartCallFragment: $isShowStartCallFragment, - isShowEditContactFragment: $isShowEditContactFragment, - text: $text - ) - .roundedCorner(25, corners: [.topRight, .topLeft]) - .shadow( - color: (orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - ? .white.opacity(0.0) - : .black.opacity(0.2), - radius: 25 - ) - */ } else if sharedMainViewModel.indexView == 2 { //TODO a changer NavigationView { @@ -809,13 +809,12 @@ struct ContentView: View { Spacer() ZStack { - /* - if historyListViewModel.missedCallsCount > 0 { + if historyListViewModel != nil && historyListViewModel!.missedCallsCount > 0 { VStack { HStack { Text( - historyListViewModel.missedCallsCount < 99 - ? String(historyListViewModel.missedCallsCount) + historyListViewModel!.missedCallsCount < 99 + ? String(historyListViewModel!.missedCallsCount) : "99+" ) .foregroundStyle(.white) @@ -829,17 +828,14 @@ struct ContentView: View { .padding(.bottom, 30) .padding(.leading, 30) } - */ Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 1) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedConversation = nil sharedMainViewModel.displayedMeeting = nil - /* - if historyListViewModel.missedCallsCount > 0 { - historyListViewModel.resetMissedCallsCount() + if historyListViewModel != nil && historyListViewModel!.missedCallsCount > 0 { + historyListViewModel!.resetMissedCallsCount() } - */ }, label: { VStack { Image("phone") @@ -886,7 +882,7 @@ struct ContentView: View { */ Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 2) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedCall = nil sharedMainViewModel.displayedMeeting = nil }, label: { @@ -913,7 +909,7 @@ struct ContentView: View { Spacer() Button(action: { sharedMainViewModel.changeIndexView(indexViewInt: 3) - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedCall = nil sharedMainViewModel.displayedConversation = nil }, label: { @@ -947,7 +943,7 @@ struct ContentView: View { } } - if sharedMainViewModel.indexDisplayedFriend != nil || sharedMainViewModel.displayedCall != nil || sharedMainViewModel.displayedConversation != nil || + if sharedMainViewModel.displayedFriend != nil || sharedMainViewModel.displayedCall != nil || sharedMainViewModel.displayedConversation != nil || sharedMainViewModel.displayedMeeting != nil { HStack(spacing: 0) { Spacer() @@ -958,7 +954,7 @@ struct ContentView: View { ? (geometry.size.width/100*40) + 75 : 0 ) - if sharedMainViewModel.indexView == 0 && sharedMainViewModel.indexDisplayedFriend != nil && contactsManager.avatarListModel.count > sharedMainViewModel.indexDisplayedFriend! { + if sharedMainViewModel.indexView == 0 && sharedMainViewModel.displayedFriend != nil { ContactFragment( isShowDeletePopup: $isShowDeleteContactPopup, isShowDismissPopup: $isShowDismissPopup, @@ -967,7 +963,7 @@ struct ContentView: View { isShowEditContactFragmentInContactDetails: $isShowEditContactFragmentInContactDetails ) .environmentObject(contactsListViewModel!) - .environmentObject(contactsManager.avatarListModel[sharedMainViewModel.indexDisplayedFriend!]) + .environmentObject(sharedMainViewModel.displayedFriend!) .frame(maxWidth: .infinity) .background(Color.gray100) .ignoresSafeArea(.keyboard) @@ -1077,12 +1073,14 @@ struct ContentView: View { if isShowEditContactFragment { EditContactFragment( isShowEditContactFragment: $isShowEditContactFragment, - isShowDismissPopup: $isShowDismissPopup + isShowDismissPopup: $isShowDismissPopup, + isShowEditContactFragmentAddress: isShowEditContactFragmentAddress ) .zIndex(3) .transition(.opacity.combined(with: .move(edge: .bottom))) .onAppear { - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil + isShowEditContactFragmentAddress = "" } } @@ -1143,35 +1141,23 @@ struct ContentView: View { */ if isShowDeleteContactPopup { - PopupView(isShowPopup: $isShowDeleteContactPopup, - title: Text(String(format: String(localized: "contact_dialog_delete_title"),contactsListViewModel!.selectedFriend != nil - ? contactsListViewModel!.selectedFriend!.name! - : (sharedMainViewModel.indexDisplayedFriend != nil - ? contactsManager.lastSearch[sharedMainViewModel.indexDisplayedFriend!].friend!.name! - : "Error Name"))), - content: Text("contact_dialog_delete_message"), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - self.isShowDeleteContactPopup.toggle()}, - titleSecondButton: Text("dialog_ok"), - actionSecondButton: { - if contactsListViewModel!.selectedFriendToDelete != nil { - if sharedMainViewModel.indexDisplayedFriend != nil { - withAnimation { - sharedMainViewModel.indexDisplayedFriend = nil - } - } - contactsListViewModel!.selectedFriendToDelete!.remove() - } else if sharedMainViewModel.indexDisplayedFriend != nil { - let tmpIndex = sharedMainViewModel.indexDisplayedFriend - withAnimation { - sharedMainViewModel.indexDisplayedFriend = nil - } - contactsManager.lastSearch[tmpIndex!].friend!.remove() - } - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - self.isShowDeleteContactPopup.toggle() + PopupView( + isShowPopup: $isShowDeleteContactPopup, + title: Text( + String( + format: String(localized: "contact_dialog_delete_title"), + contactsListViewModel!.selectedFriend?.name + ?? (SharedMainViewModel.shared.displayedFriend!.name ?? "Unknown Contact") + ) + ), + content: Text("contact_dialog_delete_message"), + titleFirstButton: Text("dialog_cancel"), + actionFirstButton: { + self.isShowDeleteContactPopup.toggle()}, + titleSecondButton: Text("dialog_ok"), + actionSecondButton: { + self.contactsListViewModel!.deleteSelectedContact() + self.isShowDeleteContactPopup.toggle() }) .background(.black.opacity(0.65)) .zIndex(3) @@ -1179,11 +1165,10 @@ struct ContentView: View { self.isShowDeleteContactPopup.toggle() } .onAppear { - contactsListViewModel!.selectedFriendToDelete = contactsListViewModel!.selectedFriend + self.contactsListViewModel!.changeSelectedFriendToDelete() } } - /* if isShowDeleteAllHistoryPopup { PopupView(isShowPopup: $isShowDeleteContactPopup, title: Text("history_dialog_delete_all_call_logs_title"), @@ -1191,11 +1176,15 @@ struct ContentView: View { titleFirstButton: Text("dialog_cancel"), actionFirstButton: { self.isShowDeleteAllHistoryPopup.toggle() - historyListViewModel.callLogsAddressToDelete = "" + if historyListViewModel != nil { + historyListViewModel!.callLogsAddressToDelete = "" + } }, titleSecondButton: Text("dialog_ok"), actionSecondButton: { - historyListViewModel.removeCallLogs() + if historyListViewModel != nil { + historyListViewModel!.removeCallLogs() + } self.isShowDeleteAllHistoryPopup.toggle() sharedMainViewModel.displayedCall = nil @@ -1208,7 +1197,6 @@ struct ContentView: View { self.isShowDeleteAllHistoryPopup.toggle() } } - */ if isShowDismissPopup { PopupView(isShowPopup: $isShowDismissPopup, @@ -1232,13 +1220,13 @@ struct ContentView: View { } } - if isShowSipAddressesPopup { + if isShowSipAddressesPopup && sharedMainViewModel.displayedFriend != nil { SipAddressesPopup( isShowSipAddressesPopup: $isShowSipAddressesPopup, isShowSipAddressesPopupType: $isShowSipAddressesPopupType ) .environmentObject(contactsListViewModel!) - .environmentObject(contactsManager.avatarListModel[sharedMainViewModel.indexDisplayedFriend != nil ? sharedMainViewModel.indexDisplayedFriend! : 0]) + .environmentObject(sharedMainViewModel.displayedFriend!) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { @@ -1253,7 +1241,7 @@ struct ContentView: View { .zIndex(3) .onDisappear { if contactsListViewModel.displayedConversation != nil { - sharedMainViewModel.indexDisplayedFriend = nil + sharedMainViewModel.displayedFriend = nil sharedMainViewModel.displayedCall = nil sharedMainViewModel.changeIndexView(indexViewInt: 2) DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { @@ -1443,7 +1431,9 @@ struct ContentView: View { } .onReceive(contactLoaded) { _ in //conversationsListViewModel.updateChatRoomsList() - //historyListViewModel.refreshHistoryAvatarModel() + if historyListViewModel != nil { + historyListViewModel!.refreshHistoryAvatarModel() + } } .onReceive(contactAdded) { address in //conversationsListViewModel.updateChatRoom(address: address) @@ -1463,7 +1453,7 @@ struct ContentView: View { } } .onRotate { newOrientation in - if (sharedMainViewModel.indexDisplayedFriend != nil || sharedMainViewModel.displayedCall != nil || sharedMainViewModel.displayedConversation != nil) && searchIsActive { + if (sharedMainViewModel.displayedFriend != nil || sharedMainViewModel.displayedCall != nil || sharedMainViewModel.displayedConversation != nil) && searchIsActive { self.focusedField = false } else if searchIsActive { self.focusedField = true @@ -1499,8 +1489,6 @@ class NavigationManager: ObservableObject { #Preview { ContentView( - //historyViewModel: HistoryViewModel(), - //historyListViewModel: HistoryListViewModel(), //startCallViewModel: StartCallViewModel(), //startConversationViewModel: StartConversationViewModel(), //callViewModel: CallViewModel(), diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift index 36b718f42..35433108a 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift @@ -349,13 +349,13 @@ struct ConversationInfoFragment: View { action: { let addressConv = participantConversationModel.address - let friendIndex = contactsManager.lastSearch.firstIndex( + let friendIndex = contactsManager.avatarListModel.first( where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) if friendIndex != nil { withAnimation { SharedMainViewModel.shared.displayedConversation = nil indexPage = 0 - SharedMainViewModel.shared.indexDisplayedFriend = friendIndex + SharedMainViewModel.shared.displayedFriend = friendIndex } } else { withAnimation { @@ -529,13 +529,13 @@ struct ConversationInfoFragment: View { let addressConv = conversationViewModel.participantConversationModel.first?.address ?? "" - let friendIndex = contactsManager.lastSearch.firstIndex( + let friendIndex = contactsManager.avatarListModel.first( where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) if friendIndex != nil { withAnimation { SharedMainViewModel.shared.displayedConversation = nil indexPage = 0 - SharedMainViewModel.shared.indexDisplayedFriend = friendIndex + SharedMainViewModel.shared.displayedFriend = friendIndex } } else { withAnimation { diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index f73365116..813cda75b 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -30,7 +30,6 @@ struct HistoryContactFragment: View { @ObservedObject private var telecomManager = TelecomManager.shared @ObservedObject var contactAvatarModel: ContactAvatarModel - @ObservedObject var historyViewModel: HistoryViewModel @ObservedObject var historyListViewModel: HistoryListViewModel @ObservedObject var contactsListViewModel: ContactsListViewModel @ObservedObject var editContactViewModel: EditContactViewModel @@ -82,14 +81,14 @@ struct HistoryContactFragment: View { let addressCall = SharedMainViewModel.shared.displayedCall!.addressFriend!.address if addressCall != nil { - let friendIndex = contactsManager.lastSearch.firstIndex( + let friendIndex = contactsManager.avatarListModel.first( where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall!.asStringUriOnly()})}) if friendIndex != nil { withAnimation { SharedMainViewModel.shared.displayedCall = nil indexPage = 0 - SharedMainViewModel.shared.indexDisplayedFriend = friendIndex + SharedMainViewModel.shared.displayedFriend = friendIndex } } } @@ -450,7 +449,6 @@ struct HistoryContactFragment: View { #Preview { HistoryContactFragment( contactAvatarModel: ContactAvatarModel(friend: nil, name: "", address: "", withPresence: false), - historyViewModel: HistoryViewModel(), historyListViewModel: HistoryListViewModel(), contactsListViewModel: ContactsListViewModel(), editContactViewModel: EditContactViewModel(), diff --git a/Linphone/UI/Main/History/Fragments/HistoryFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryFragment.swift index fc93092c0..07f672c15 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryFragment.swift @@ -22,43 +22,32 @@ import SwiftUI struct HistoryFragment: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } - @ObservedObject var historyListViewModel: HistoryListViewModel - @ObservedObject var historyViewModel: HistoryViewModel - @ObservedObject var contactsListViewModel: ContactsListViewModel - @ObservedObject var editContactViewModel: EditContactViewModel + @EnvironmentObject var historyListViewModel: HistoryListViewModel @State private var showingSheet = false - @Binding var index: Int @Binding var isShowEditContactFragment: Bool @Binding var text: String + @Binding var isShowEditContactFragmentAddress: String var body: some View { ZStack { if #available(iOS 16.0, *), idiom != .pad { - HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet, text: $text) + HistoryListFragment(showingSheet: $showingSheet, text: $text) .sheet(isPresented: $showingSheet) { HistoryListBottomSheet( - historyViewModel: historyViewModel, - contactsListViewModel: contactsListViewModel, - editContactViewModel: editContactViewModel, - historyListViewModel: historyListViewModel, showingSheet: $showingSheet, - index: $index, - isShowEditContactFragment: $isShowEditContactFragment + isShowEditContactFragment: $isShowEditContactFragment, + isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress ) .presentationDetents([.fraction(0.2)]) } } else { - HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet, text: $text) + HistoryListFragment(showingSheet: $showingSheet, text: $text) .halfSheet(showSheet: $showingSheet) { HistoryListBottomSheet( - historyViewModel: historyViewModel, - contactsListViewModel: contactsListViewModel, - editContactViewModel: editContactViewModel, - historyListViewModel: historyListViewModel, showingSheet: $showingSheet, - index: $index, - isShowEditContactFragment: $isShowEditContactFragment + isShowEditContactFragment: $isShowEditContactFragment, + isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress ) } onDismiss: {} } @@ -68,12 +57,8 @@ struct HistoryFragment: View { #Preview { HistoryFragment( - historyListViewModel: HistoryListViewModel(), - historyViewModel: HistoryViewModel(), - contactsListViewModel: ContactsListViewModel(), - editContactViewModel: EditContactViewModel(), - index: .constant(1), isShowEditContactFragment: .constant(false), - text: .constant("") + text: .constant(""), + isShowEditContactFragmentAddress: .constant("") ) } diff --git a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift index a5f64b8ad..09ff948af 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift @@ -27,17 +27,15 @@ struct HistoryListBottomSheet: View { private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } @ObservedObject var contactsManager = ContactsManager.shared + @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared - @ObservedObject var historyViewModel: HistoryViewModel - @ObservedObject var contactsListViewModel: ContactsListViewModel - @ObservedObject var editContactViewModel: EditContactViewModel - @ObservedObject var historyListViewModel: HistoryListViewModel + @EnvironmentObject var historyListViewModel: HistoryListViewModel @State private var orientation = UIDevice.current.orientation @Binding var showingSheet: Bool - @Binding var index: Int @Binding var isShowEditContactFragment: Bool + @Binding var isShowEditContactFragmentAddress: String var body: some View { VStack(alignment: .leading) { @@ -74,30 +72,28 @@ struct HistoryListBottomSheet: View { dismiss() } - index = 0 + sharedMainViewModel.changeIndexView(indexViewInt: 0) - if historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.addressFriend != nil { - let addressCall = historyViewModel.selectedCall!.address + if historyListViewModel.selectedCall != nil && historyListViewModel.selectedCall!.addressFriend != nil { + let addressCall = historyListViewModel.selectedCall!.address - let friendIndex = contactsManager.lastSearch.firstIndex(where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall})}) + let friendIndex = contactsManager.avatarListModel.first(where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall})}) if friendIndex != nil { withAnimation { - SharedMainViewModel.shared.indexDisplayedFriend = friendIndex + SharedMainViewModel.shared.displayedFriend = friendIndex } } - } else if historyViewModel.selectedCall != nil { - let addressCall = historyViewModel.selectedCall!.address + } else if historyListViewModel.selectedCall != nil { + let addressCall = historyListViewModel.selectedCall!.address withAnimation { isShowEditContactFragment.toggle() - editContactViewModel.sipAddresses.removeAll() - editContactViewModel.sipAddresses.append(String(addressCall.dropFirst(4))) - editContactViewModel.sipAddresses.append("") + isShowEditContactFragmentAddress = String(addressCall.dropFirst(4)) } } } label: { HStack { - if historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.addressFriend != nil { + if historyListViewModel.selectedCall != nil && historyListViewModel.selectedCall!.addressFriend != nil { Image("user-circle") .renderingMode(.template) .resizable() @@ -130,14 +126,14 @@ struct HistoryListBottomSheet: View { .frame(maxWidth: .infinity) Button { - if historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.isOutgoing { + if historyListViewModel.selectedCall != nil && historyListViewModel.selectedCall!.isOutgoing { UIPasteboard.general.setValue( - historyViewModel.selectedCall!.address.dropFirst(4), + historyListViewModel.selectedCall!.address.dropFirst(4), forPasteboardType: UTType.plainText.identifier ) } else { UIPasteboard.general.setValue( - historyViewModel.selectedCall!.address.dropFirst(4), + historyListViewModel.selectedCall!.address.dropFirst(4), forPasteboardType: UTType.plainText.identifier ) } @@ -180,8 +176,8 @@ struct HistoryListBottomSheet: View { .frame(maxWidth: .infinity) Button { - if historyViewModel.selectedCall != nil { - historyListViewModel.removeCallLog(historyModel: historyViewModel.selectedCall!) + if historyListViewModel.selectedCall != nil { + historyListViewModel.removeCallLog(historyModel: historyListViewModel.selectedCall!) } if #available(iOS 16.0, *) { @@ -224,12 +220,8 @@ struct HistoryListBottomSheet: View { #Preview { HistoryListBottomSheet( - historyViewModel: HistoryViewModel(), - contactsListViewModel: ContactsListViewModel(), - editContactViewModel: EditContactViewModel(), - historyListViewModel: HistoryListViewModel(), showingSheet: .constant(false), - index: .constant(1), - isShowEditContactFragment: .constant(false) + isShowEditContactFragment: .constant(false), + isShowEditContactFragmentAddress: .constant("") ) } diff --git a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift index 30d4c2daa..818886fc4 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift @@ -27,8 +27,7 @@ struct HistoryListFragment: View { @ObservedObject var contactsManager = ContactsManager.shared @ObservedObject private var telecomManager = TelecomManager.shared - @ObservedObject var historyListViewModel: HistoryListViewModel - @ObservedObject var historyViewModel: HistoryViewModel + @EnvironmentObject var historyListViewModel: HistoryListViewModel @Binding var showingSheet: Bool @Binding var text: String @@ -36,110 +35,8 @@ struct HistoryListFragment: View { var body: some View { VStack { List { - ForEach(0.. 1 - ? historyListViewModel.callLogs[index].addressName.components(separatedBy: " ")[1] - : "")) - .resizable() - .frame(width: 50, height: 50) - .clipShape(Circle()) - } else { - VStack { - Image("profil-picture-default") - .renderingMode(.template) - .resizable() - .frame(width: 28, height: 28) - .foregroundStyle(Color.grayMain2c600) - } - .frame(width: 50, height: 50) - .background(Color.grayMain2c200) - .clipShape(Circle()) - } - } - } else { - VStack { - Image("users-three-square") - .renderingMode(.template) - .resizable() - .frame(width: 28, height: 28) - .foregroundStyle(Color.grayMain2c600) - } - .frame(width: 50, height: 50) - .background(Color.grayMain2c200) - .clipShape(Circle()) - } - - VStack(spacing: 0) { - Spacer() - if !historyListViewModel.callLogs[index].isConf { - Text(historyListViewModel.callLogs[index].addressName) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - } else { - Text(historyListViewModel.callLogs[index].subject) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - } - - HStack { - Image(historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, isOutgoing: historyListViewModel.callLogs[index].isOutgoing)) - .resizable() - .frame( - width: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, isOutgoing: historyListViewModel.callLogs[index].isOutgoing).contains("rejected") ? 12 : 8, - height: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, isOutgoing: historyListViewModel.callLogs[index].isOutgoing).contains("rejected") ? 6 : 8) - Text(historyListViewModel.getCallTime(startDate: historyListViewModel.callLogs[index].startDate)) - .default_text_style_300(styleSize: 12) - .frame(maxWidth: .infinity, alignment: .leading) - - Spacer() - } - - Spacer() - } - - if !historyListViewModel.callLogs[index].isConf { - Image("phone") - .resizable() - .frame(width: 25, height: 25) - .padding(.all, 10) - .padding(.trailing, 5) - .highPriorityGesture( - TapGesture() - .onEnded { _ in - withAnimation { - doCall(index: index) - SharedMainViewModel.shared.displayedCall = nil - } - } - ) - } - } - } - .frame(height: 50) - .buttonStyle(.borderless) - .listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20)) - .listRowSeparator(.hidden) - .background(.white) - .onTapGesture { - withAnimation { - SharedMainViewModel.shared.displayedCall = historyListViewModel.callLogs[index] - } - } - .onLongPressGesture(minimumDuration: 0.2) { - historyViewModel.selectedCall = historyListViewModel.callLogs[index] - showingSheet.toggle() - } + ForEach(historyListViewModel.callLogs) { historyModel in + HistoryRow(historyModel: historyModel, showingSheet: $showingSheet) } } .safeAreaInset(edge: .top, content: { @@ -169,14 +66,131 @@ struct HistoryListFragment: View { .navigationTitle("") .navigationBarHidden(true) } +} + +struct HistoryRow: View { + @ObservedObject var contactsManager = ContactsManager.shared + @ObservedObject private var telecomManager = TelecomManager.shared - func doCall(index: Int) { - telecomManager.doCallOrJoinConf(address: historyListViewModel.callLogs[index].addressLinphone) + @EnvironmentObject var historyListViewModel: HistoryListViewModel + + @ObservedObject var historyModel: HistoryModel + + @Binding var showingSheet: Bool + + var body: some View { + HStack { + HStack { + if !historyModel.isConf { + if historyModel.avatarModel != nil { + Avatar(contactAvatarModel: historyModel.avatarModel!, avatarSize: 50) + } else { + if !historyModel.addressName.isEmpty { + Image(uiImage: contactsManager.textToImage( + firstName: historyModel.addressName, + lastName: historyModel.addressName.components(separatedBy: " ").count > 1 + ? historyModel.addressName.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 50, height: 50) + .clipShape(Circle()) + } else { + VStack { + Image("profil-picture-default") + .renderingMode(.template) + .resizable() + .frame(width: 28, height: 28) + .foregroundStyle(Color.grayMain2c600) + } + .frame(width: 50, height: 50) + .background(Color.grayMain2c200) + .clipShape(Circle()) + } + } + } else { + VStack { + Image("users-three-square") + .renderingMode(.template) + .resizable() + .frame(width: 28, height: 28) + .foregroundStyle(Color.grayMain2c600) + } + .frame(width: 50, height: 50) + .background(Color.grayMain2c200) + .clipShape(Circle()) + } + + VStack(spacing: 0) { + Spacer() + if !historyModel.isConf { + Text(historyModel.addressName) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else { + Text(historyModel.subject) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } + + HStack { + Image(historyListViewModel.getCallIconResId(callStatus: historyModel.status, isOutgoing: historyModel.isOutgoing)) + .resizable() + .frame( + width: historyListViewModel.getCallIconResId(callStatus: historyModel.status, isOutgoing: historyModel.isOutgoing).contains("rejected") ? 12 : 8, + height: historyListViewModel.getCallIconResId(callStatus: historyModel.status, isOutgoing: historyModel.isOutgoing).contains("rejected") ? 6 : 8) + Text(historyListViewModel.getCallTime(startDate: historyModel.startDate)) + .default_text_style_300(styleSize: 12) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer() + } + + Spacer() + } + + if !historyModel.isConf { + Image("phone") + .resizable() + .frame(width: 25, height: 25) + .padding(.all, 10) + .padding(.trailing, 5) + .highPriorityGesture( + TapGesture() + .onEnded { _ in + withAnimation { + doCall(historyModel: historyModel) + SharedMainViewModel.shared.displayedCall = nil + } + } + ) + } + } + } + .frame(height: 50) + .buttonStyle(.borderless) + .listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20)) + .listRowSeparator(.hidden) + .background(.white) + .onTapGesture { + withAnimation { + SharedMainViewModel.shared.displayedCall = historyModel + } + } + .onLongPressGesture(minimumDuration: 0.2) { + historyListViewModel.selectedCall = historyModel + showingSheet.toggle() + } + } + + func doCall(historyModel: HistoryModel) { + telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone) } } #Preview { - HistoryListFragment(historyListViewModel: HistoryListViewModel(), historyViewModel: HistoryViewModel(), showingSheet: .constant(false), text: .constant("")) + HistoryListFragment(showingSheet: .constant(false), text: .constant("")) } // swiftlint:enable line_length diff --git a/Linphone/UI/Main/History/HistoryView.swift b/Linphone/UI/Main/History/HistoryView.swift index 57a8ccc58..8ffa3d31a 100644 --- a/Linphone/UI/Main/History/HistoryView.swift +++ b/Linphone/UI/Main/History/HistoryView.swift @@ -22,27 +22,20 @@ import linphonesw struct HistoryView: View { - @ObservedObject var historyListViewModel: HistoryListViewModel - @ObservedObject var historyViewModel: HistoryViewModel - @ObservedObject var contactsListViewModel: ContactsListViewModel - @ObservedObject var editContactViewModel: EditContactViewModel + @EnvironmentObject var historyListViewModel: HistoryListViewModel - @Binding var index: Int @Binding var isShowStartCallFragment: Bool @Binding var isShowEditContactFragment: Bool @Binding var text: String + @Binding var isShowEditContactFragmentAddress: String var body: some View { NavigationView { ZStack(alignment: .bottomTrailing) { HistoryFragment( - historyListViewModel: historyListViewModel, - historyViewModel: historyViewModel, - contactsListViewModel: contactsListViewModel, - editContactViewModel: editContactViewModel, - index: $index, isShowEditContactFragment: $isShowEditContactFragment, - text: $text + text: $text, + isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress ) Button { @@ -72,12 +65,8 @@ struct HistoryView: View { #Preview { HistoryFragment( - historyListViewModel: HistoryListViewModel(), - historyViewModel: HistoryViewModel(), - contactsListViewModel: ContactsListViewModel(), - editContactViewModel: EditContactViewModel(), - index: .constant(1), isShowEditContactFragment: .constant(false), - text: .constant("") + text: .constant(""), + isShowEditContactFragmentAddress: .constant("") ) } diff --git a/Linphone/UI/Main/History/Model/HistoryModel.swift b/Linphone/UI/Main/History/Model/HistoryModel.swift index d7fd45c47..850e3ecef 100644 --- a/Linphone/UI/Main/History/Model/HistoryModel.swift +++ b/Linphone/UI/Main/History/Model/HistoryModel.swift @@ -20,15 +20,17 @@ import Foundation import linphonesw -class HistoryModel: ObservableObject { +class HistoryModel: ObservableObject, Identifiable { private var coreContext = CoreContext.shared static let TAG = "[History Model]" + let id = UUID() + var callLog: CallLog - var id: String + @Published var callLogId: String @Published var subject: String @Published var isConf: Bool @Published var addressLinphone: Address @@ -43,7 +45,7 @@ class HistoryModel: ObservableObject { init(callLog: CallLog) { self.callLog = callLog - self.id = "" + self.callLogId = "" self.subject = "" self.isConf = false @@ -88,7 +90,7 @@ class HistoryModel: ObservableObject { DispatchQueue.main.async { self.callLog = callLogTmp - self.id = idTmp + self.callLogId = idTmp self.subject = subjectTmp self.isConf = isConfTmp @@ -111,34 +113,30 @@ class HistoryModel: ObservableObject { } func refreshAvatarModel() { - coreContext.doOnCoreQueue { _ in - guard let address = (self.callLog.dir == .Outgoing ? self.callLog.toAddress : self.callLog.fromAddress) else { - DispatchQueue.main.async { - self.avatarModel = ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) - } - return + guard let address = (self.callLog.dir == .Outgoing ? self.callLog.toAddress : self.callLog.fromAddress) else { + DispatchQueue.main.async { + self.avatarModel = ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) } + return + } + + let addressFriendTmp = ContactsManager.shared.getFriendWithAddress(address: address) + if let addressFriendTmp = addressFriendTmp { + let addressNameTmp = self.addressName - let addressFriendTmp = ContactsManager.shared.getFriendWithAddress(address: address) - if let addressFriendTmp = addressFriendTmp { + let avatarModelTmp = ContactsManager.shared.avatarListModel.first(where: { + guard let friend = $0.friend else { return false } + return friend.name == addressFriendTmp.name && friend.address?.asStringUriOnly() == addressFriendTmp.address?.asStringUriOnly() + }) ?? ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) + + DispatchQueue.main.async { self.addressFriend = addressFriendTmp - - let addressNameTmp = self.addressName - - let avatarModelTmp = ContactsManager.shared.avatarListModel.first(where: { - guard let friend = $0.friend else { return false } - return friend.name == addressFriendTmp.name && friend.address?.asStringUriOnly() == addressFriendTmp.address?.asStringUriOnly() - }) ?? ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) - - DispatchQueue.main.async { - self.addressFriend = addressFriendTmp - self.addressName = addressFriendTmp.name ?? addressNameTmp - self.avatarModel = avatarModelTmp - } - } else { - DispatchQueue.main.async { - self.avatarModel = ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) - } + self.addressName = addressFriendTmp.name ?? addressNameTmp + self.avatarModel = avatarModelTmp + } + } else { + DispatchQueue.main.async { + self.avatarModel = ContactAvatarModel(friend: nil, name: self.addressName, address: self.address, withPresence: false) } } } diff --git a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift index 002175504..e9e9a1f01 100644 --- a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift @@ -33,6 +33,8 @@ class HistoryListViewModel: ObservableObject { @Published var missedCallsCount: Int = 0 + @Published var selectedCall: HistoryModel? + init() { computeCallLogsList() updateMissedCallsCount() @@ -247,8 +249,10 @@ class HistoryListViewModel: ObservableObject { } func refreshHistoryAvatarModel() { - callLogs.forEach { historyModel in - historyModel.refreshAvatarModel() + coreContext.doOnCoreQueue { _ in + self.callLogs.forEach { historyModel in + historyModel.refreshAvatarModel() + } } } } diff --git a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift index a741d5247..e4a6c2e22 100644 --- a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift +++ b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift @@ -95,16 +95,14 @@ class AccountProfileViewModel: ObservableObject { let accountDisplayName = CoreContext.shared.accounts[self.accountModelIndex!].account.displayName() - DispatchQueue.main.async { - CoreContext.shared.accounts[self.accountModelIndex!].avatarModel = ContactAvatarModel( - friend: nil, - name: displayNameTmp.isEmpty ? accountDisplayName : displayNameTmp, - address: contactAddressTmp, - withPresence: false - ) - - self.dialPlanValueSelected = dialPlanValueSelectedTmp - } + CoreContext.shared.accounts[self.accountModelIndex!].avatarModel = ContactAvatarModel( + friend: nil, + name: displayNameTmp.isEmpty ? accountDisplayName : displayNameTmp, + address: contactAddressTmp, + withPresence: false + ) + + self.dialPlanValueSelected = dialPlanValueSelectedTmp } } } diff --git a/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift b/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift index d72671352..bb029cdb7 100644 --- a/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift +++ b/Linphone/UI/Main/Viewmodel/SharedMainViewModel.swift @@ -30,7 +30,7 @@ class SharedMainViewModel: ObservableObject { @Published var defaultAvatar: URL? @Published var indexView: Int = 0 - @Published var indexDisplayedFriend: Int? + @Published var displayedFriend: ContactAvatarModel? @Published var displayedCall: HistoryModel? @Published var displayedConversation: ConversationModel? @Published var displayedMeeting: MeetingModel? diff --git a/Linphone/Utils/Avatar.swift b/Linphone/Utils/Avatar.swift index a87908184..137910a55 100644 --- a/Linphone/Utils/Avatar.swift +++ b/Linphone/Utils/Avatar.swift @@ -37,8 +37,11 @@ struct Avatar: View { } var body: some View { - if contactAvatarModel.friend != nil && contactAvatarModel.friend!.photo != nil { - AsyncImage(url: ContactsManager.shared.getImagePath(friendPhotoPath: contactAvatarModel.friend!.photo!)) { image in + if let photoPath = contactAvatarModel.friend?.photo { + let uniqueUrl = ContactsManager.shared.getImagePath(friendPhotoPath: photoPath) + let finalUrl = uniqueUrl.appendingQueryItem("v", value: UUID().uuidString) + + AsyncImage(url: finalUrl) { image in switch image { case .empty: ProgressView() diff --git a/Linphone/Utils/Extensions/URLExtension.swift b/Linphone/Utils/Extensions/URLExtension.swift index 8a548a0b3..5ca4ca3f5 100644 --- a/Linphone/Utils/Extensions/URLExtension.swift +++ b/Linphone/Utils/Extensions/URLExtension.swift @@ -25,8 +25,15 @@ extension URL { components?.scheme = value return components?.url } + var resourceSpecifier: String { let nrl: NSURL = self as NSURL return nrl.resourceSpecifier ?? self.absoluteString } + + func appendingQueryItem(_ name: String, value: String) -> URL { + guard var components = URLComponents(url: self, resolvingAgainstBaseURL: true) else { return self } + components.queryItems = (components.queryItems ?? []) + [URLQueryItem(name: name, value: value)] + return components.url ?? self + } } diff --git a/Linphone/Utils/MagicSearchSingleton.swift b/Linphone/Utils/MagicSearchSingleton.swift index 8db271542..35f55d7ca 100644 --- a/Linphone/Utils/MagicSearchSingleton.swift +++ b/Linphone/Utils/MagicSearchSingleton.swift @@ -91,13 +91,14 @@ final class MagicSearchSingleton: ObservableObject { } } + self.contactsManager.avatarListModel.forEach { contactAvatarModel in + contactAvatarModel.removeFriendDelegate() + } + DispatchQueue.main.async { self.contactsManager.lastSearch = sortedLastSearch self.contactsManager.lastSearchSuggestions = lastSearchSuggestions - self.contactsManager.avatarListModel.forEach { contactAvatarModel in - contactAvatarModel.removeFriendDelegate() - } self.contactsManager.avatarListModel.removeAll() self.contactsManager.avatarListModel += addedAvatarListModel diff --git a/Linphone/Utils/ShareSheetController.swift b/Linphone/Utils/ShareSheetController.swift index fded1c065..b00853cbf 100644 --- a/Linphone/Utils/ShareSheetController.swift +++ b/Linphone/Utils/ShareSheetController.swift @@ -24,7 +24,7 @@ import linphonesw struct ShareSheet: UIViewControllerRepresentable { typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void - let friendToShare: Friend + let friendToShare: ContactAvatarModel var activityItems: [Any] = [] let applicationActivities: [UIActivity]? = nil let excludedActivityTypes: [UIActivity.ActivityType]? = nil @@ -34,25 +34,23 @@ struct ShareSheet: UIViewControllerRepresentable { let directoryURL = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first if directoryURL != nil { - if friendToShare.name != nil { - let filename = friendToShare.name!.replacingOccurrences(of: " ", with: "") - - let fileURL = directoryURL! - .appendingPathComponent(filename) - .appendingPathExtension("vcf") - - if friendToShare.vcard != nil { - try? friendToShare.vcard!.asVcard4String().write(to: fileURL, atomically: false, encoding: String.Encoding.utf8) - - let controller = UIActivityViewController( - activityItems: [fileURL], - applicationActivities: applicationActivities - ) - controller.excludedActivityTypes = excludedActivityTypes - controller.completionWithItemsHandler = callback - return controller - } - } + let filename = friendToShare.name.replacingOccurrences(of: " ", with: "") + + let fileURL = directoryURL! + .appendingPathComponent(filename) + .appendingPathExtension("vcf") + + if let vCard = friendToShare.vcard { + try? vCard.asVcard4String().write(to: fileURL, atomically: false, encoding: String.Encoding.utf8) + + let controller = UIActivityViewController( + activityItems: [fileURL], + applicationActivities: applicationActivities + ) + controller.excludedActivityTypes = excludedActivityTypes + controller.completionWithItemsHandler = callback + return controller + } } let controller = UIActivityViewController( diff --git a/LinphoneApp.xcodeproj/project.pbxproj b/LinphoneApp.xcodeproj/project.pbxproj index 8df3e3e26..864729a4b 100644 --- a/LinphoneApp.xcodeproj/project.pbxproj +++ b/LinphoneApp.xcodeproj/project.pbxproj @@ -86,7 +86,6 @@ D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */; }; D71FCA832AE14D6E00D2E43E /* ContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */; }; D720E6AD2BAD822000DDFD87 /* ParticipantModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */; }; - D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250622ADE9615008FB426 /* HistoryViewModel.swift */; }; D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72250682ADFBF2D008FB426 /* SideMenu.swift */; }; D72343302ACEFEF8009AA24E /* QrCodeScannerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */; }; D72343322ACEFF58009AA24E /* QRScannerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343312ACEFF58009AA24E /* QRScannerController.swift */; }; @@ -298,7 +297,6 @@ D71FCA802AE14CFC00D2E43E /* ContactsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsListFragment.swift; sourceTree = ""; }; D71FCA822AE14D6E00D2E43E /* ContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactFragment.swift; sourceTree = ""; }; D720E6AC2BAD822000DDFD87 /* ParticipantModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ParticipantModel.swift; sourceTree = ""; }; - D72250622ADE9615008FB426 /* HistoryViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryViewModel.swift; sourceTree = ""; }; D72250682ADFBF2D008FB426 /* SideMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideMenu.swift; sourceTree = ""; }; D723432F2ACEFEF8009AA24E /* QrCodeScannerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QrCodeScannerFragment.swift; sourceTree = ""; }; D72343312ACEFF58009AA24E /* QRScannerController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRScannerController.swift; sourceTree = ""; }; @@ -685,7 +683,6 @@ D72250612ADE95E4008FB426 /* ViewModel */ = { isa = PBXGroup; children = ( - D72250622ADE9615008FB426 /* HistoryViewModel.swift */, D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */, D726E43E2B19E56F0083C415 /* StartCallViewModel.swift */, ); @@ -1241,7 +1238,6 @@ D783028F2D414847009CCB60 /* DebugFragment.swift in Sources */, 66162A202BDFC2F900DCE913 /* AddParticipantsViewModel.swift in Sources */, D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */, - D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */, D78E06302BEA6A4A00CE3783 /* ChangeLayoutBottomSheet.swift in Sources */, 66E56BC92BA4A6D7006CE56F /* MeetingsListViewModel.swift in Sources */, D726E4392B16440C0083C415 /* ContactAvatarModel.swift in Sources */,