From 06847bc82ebbda030dd266e4a386a6cce43b747c Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Mon, 9 Jun 2025 15:15:59 +0200 Subject: [PATCH] Refactored HistoryView (Part 2) --- Linphone/Contacts/ContactsManager.swift | 8 +- .../Fragments/EditContactFragment.swift | 9 +- Linphone/UI/Main/ContentView.swift | 55 +- .../Fragments/ConversationInfoFragment.swift | 4 +- .../Fragments/HistoryContactFragment.swift | 739 +++++++++--------- .../Fragments/HistoryListBottomSheet.swift | 14 +- .../Fragments/HistoryListFragment.swift | 4 +- .../UI/Main/History/Model/HistoryModel.swift | 18 +- 8 files changed, 413 insertions(+), 438 deletions(-) diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 11bc99d04..43e1fd5ed 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -248,12 +248,10 @@ final class ContactsManager: ObservableObject { if linphoneFriend && existingFriend == nil { if let linphoneFL = self.linphoneFriendList { _ = linphoneFL.addFriend(linphoneFriend: resultFriend!) - linphoneFL.updateSubscriptions() } } else if existingFriend == nil { if let friendListTmp = self.friendList { _ = friendListTmp.addLocalFriend(linphoneFriend: resultFriend!) - friendListTmp.updateSubscriptions() } } } @@ -521,6 +519,12 @@ final class ContactsManager: ObservableObject { .store(in: &cancellables) } } + + func updateSubscriptionsLinphoneList() { + if let linphoneFL = self.linphoneFriendList { + linphoneFL.updateSubscriptions() + } + } } struct PhoneNumber { diff --git a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift index 8b2542e76..3bc1d03db 100644 --- a/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/EditContactFragment.swift @@ -530,10 +530,10 @@ struct EditContactFragment: View { withPresence: SharedMainViewModel.shared.displayedFriend?.withPresence ) } - + let friendIsNil = editContactViewModel.selectedEditFriend?.friend == nil DispatchQueue.main.async { delayColorDismiss() - if editContactViewModel.selectedEditFriend?.friend == nil { + if friendIsNil { withAnimation { isShowEditContactFragment.toggle() } @@ -565,11 +565,14 @@ struct EditContactFragment: View { ) } else { MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + ContactsManager.shared.updateSubscriptionsLinphoneList() } + let friendIsNil = editContactViewModel.selectedEditFriend?.friend == nil + DispatchQueue.main.async { delayColorDismiss() - if editContactViewModel.selectedEditFriend?.friend == nil { + if friendIsNil { withAnimation { isShowEditContactFragment.toggle() } diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 8692d930a..5b81b022a 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -954,37 +954,30 @@ struct ContentView: View { ? (geometry.size.width/100*40) + 75 : 0 ) - if sharedMainViewModel.indexView == 0 && sharedMainViewModel.displayedFriend != nil { - ContactFragment( - isShowDeletePopup: $isShowDeleteContactPopup, - isShowDismissPopup: $isShowDismissPopup, - isShowSipAddressesPopup: $isShowSipAddressesPopup, - isShowSipAddressesPopupType: $isShowSipAddressesPopupType, - isShowEditContactFragmentInContactDetails: $isShowEditContactFragmentInContactDetails - ) - .environmentObject(contactsListViewModel!) - .environmentObject(sharedMainViewModel.displayedFriend!) - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) - } else if sharedMainViewModel.indexView == 1 { - /* - if sharedMainViewModel.displayedCall != nil && sharedMainViewModel.displayedCall!.avatarModel != nil { - HistoryContactFragment( - contactAvatarModel: sharedMainViewModel.displayedCall!.avatarModel!, - historyViewModel: historyViewModel, - historyListViewModel: historyListViewModel, - contactsListViewModel: contactsListViewModel, - editContactViewModel: editContactViewModel, - isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup, - isShowEditContactFragment: $isShowEditContactFragment, - indexPage: $index - ) - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) - } - */ + if sharedMainViewModel.indexView == 0 && sharedMainViewModel.displayedFriend != nil && contactsListViewModel != nil { + ContactFragment( + isShowDeletePopup: $isShowDeleteContactPopup, + isShowDismissPopup: $isShowDismissPopup, + isShowSipAddressesPopup: $isShowSipAddressesPopup, + isShowSipAddressesPopupType: $isShowSipAddressesPopupType, + isShowEditContactFragmentInContactDetails: $isShowEditContactFragmentInContactDetails + ) + .environmentObject(contactsListViewModel!) + .environmentObject(sharedMainViewModel.displayedFriend!) + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) + } else if sharedMainViewModel.indexView == 1 && sharedMainViewModel.displayedCall != nil && historyListViewModel != nil { + HistoryContactFragment( + isShowDeleteAllHistoryPopup: $isShowDeleteAllHistoryPopup, + isShowEditContactFragment: $isShowEditContactFragment, + isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress + ) + .environmentObject(historyListViewModel!) + .environmentObject(sharedMainViewModel.displayedCall!) + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) } else if sharedMainViewModel.indexView == 2 { /* ConversationFragment( diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift index 35433108a..4a6332380 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift @@ -350,7 +350,7 @@ struct ConversationInfoFragment: View { let addressConv = participantConversationModel.address let friendIndex = contactsManager.avatarListModel.first( - where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) + where: {$0.addresses.contains(where: {$0 == addressConv})}) if friendIndex != nil { withAnimation { SharedMainViewModel.shared.displayedConversation = nil @@ -530,7 +530,7 @@ struct ConversationInfoFragment: View { let addressConv = conversationViewModel.participantConversationModel.first?.address ?? "" let friendIndex = contactsManager.avatarListModel.first( - where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) + where: {$0.addresses.contains(where: {$0 == addressConv})}) if friendIndex != nil { withAnimation { SharedMainViewModel.shared.displayedConversation = nil diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index 813cda75b..09b57b713 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -29,417 +29,390 @@ struct HistoryContactFragment: View { @ObservedObject var contactsManager = ContactsManager.shared @ObservedObject private var telecomManager = TelecomManager.shared - @ObservedObject var contactAvatarModel: ContactAvatarModel - @ObservedObject var historyListViewModel: HistoryListViewModel - @ObservedObject var contactsListViewModel: ContactsListViewModel - @ObservedObject var editContactViewModel: EditContactViewModel + @EnvironmentObject var historyModel: HistoryModel + @EnvironmentObject var historyListViewModel: HistoryListViewModel @State var isMenuOpen = false @Binding var isShowDeleteAllHistoryPopup: Bool @Binding var isShowEditContactFragment: Bool - @Binding var indexPage: Int + @Binding var isShowEditContactFragmentAddress: String var body: some View { NavigationView { - if SharedMainViewModel.shared.displayedCall != nil { - VStack(spacing: 1) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - - HStack { - if !(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 2) - .padding(.leading, -10) - .onTapGesture { - withAnimation { - SharedMainViewModel.shared.displayedCall = nil - } - } - } - - Text("history_title") - .default_text_style_orange_800(styleSize: 20) - - Spacer() - - Menu { - if SharedMainViewModel.shared.displayedCall != nil && !SharedMainViewModel.shared.displayedCall!.isConf { - Button { - isMenuOpen = false - - if SharedMainViewModel.shared.displayedCall != nil && SharedMainViewModel.shared.displayedCall!.addressFriend != nil { - let addressCall = SharedMainViewModel.shared.displayedCall!.addressFriend!.address - - if addressCall != nil { - 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.displayedFriend = friendIndex - } - } - } - } else { - withAnimation { - SharedMainViewModel.shared.displayedCall = nil - indexPage = 0 - - isShowEditContactFragment.toggle() - editContactViewModel.sipAddresses.removeAll() - editContactViewModel.sipAddresses.append(String(SharedMainViewModel.shared.displayedCall?.address.dropFirst(4) ?? "")) - editContactViewModel.sipAddresses.append("") - } - } - - } label: { - HStack { - Text(SharedMainViewModel.shared.displayedCall!.addressFriend != nil ? "menu_see_existing_contact" : "menu_add_address_to_contacts") - Spacer() - Image(SharedMainViewModel.shared.displayedCall!.addressFriend != nil ? "user-circle" : "plus-circle") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } + VStack(spacing: 1) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) + + HStack { + if !(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 2) + .padding(.leading, -10) + .onTapGesture { + withAnimation { + SharedMainViewModel.shared.displayedCall = nil } } - + } + + Text("history_title") + .default_text_style_orange_800(styleSize: 20) + + Spacer() + + Menu { + if !historyModel.isConf { Button { isMenuOpen = false - if SharedMainViewModel.shared.displayedCall != nil && SharedMainViewModel.shared.displayedCall!.isOutgoing { - UIPasteboard.general.setValue( - SharedMainViewModel.shared.displayedCall!.address.dropFirst(4), - forPasteboardType: UTType.plainText.identifier - ) + SharedMainViewModel.shared.displayedCall = nil + SharedMainViewModel.shared.changeIndexView(indexViewInt: 0) + + if historyModel.isFriend { + let friendIndex = contactsManager.avatarListModel.first(where: {$0.addresses.contains(where: {$0 == historyModel.address})}) + if friendIndex != nil { + withAnimation { + SharedMainViewModel.shared.displayedFriend = friendIndex + } + } } else { - UIPasteboard.general.setValue( - SharedMainViewModel.shared.displayedCall!.address.dropFirst(4), - forPasteboardType: UTType.plainText.identifier - ) + withAnimation { + isShowEditContactFragment.toggle() + isShowEditContactFragmentAddress = String(historyModel.address.dropFirst(4)) + } } - - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() - } label: { HStack { - Text("menu_copy_sip_address") + Text(historyModel.isFriend ? "menu_see_existing_contact" : "menu_add_address_to_contacts") Spacer() - Image("copy") + Image(historyModel.isFriend ? "user-circle" : "plus-circle") .resizable() .frame(width: 25, height: 25, alignment: .leading) .padding(.all, 10) } } + } + + Button { + isMenuOpen = false + + if historyModel.isOutgoing { + UIPasteboard.general.setValue( + historyModel.address.dropFirst(4), + forPasteboardType: UTType.plainText.identifier + ) + } else { + UIPasteboard.general.setValue( + historyModel.address.dropFirst(4), + forPasteboardType: UTType.plainText.identifier + ) + } - Button(role: .destructive) { - isMenuOpen = false - - if SharedMainViewModel.shared.displayedCall != nil && SharedMainViewModel.shared.displayedCall!.isOutgoing { - historyListViewModel.callLogsAddressToDelete = SharedMainViewModel.shared.displayedCall!.address + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" + ToastViewModel.shared.displayToast.toggle() + + } label: { + HStack { + Text("menu_copy_sip_address") + Spacer() + Image("copy") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + } + + Button(role: .destructive) { + isMenuOpen = false + historyListViewModel.callLogsAddressToDelete = historyModel.address + isShowDeleteAllHistoryPopup.toggle() + + } label: { + HStack { + Text("menu_delete_history") + Spacer() + Image("trash-simple-red") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + } + } label: { + Image("dots-three-vertical") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + .padding(.leading) + .onTapGesture { + isMenuOpen = true + } + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 4) + .background(.white) + + ScrollView { + VStack(spacing: 0) { + VStack(spacing: 0) { + if #unavailable(iOS 16.0) { + Rectangle() + .foregroundColor(Color.gray100) + .frame(height: 7) + } + + VStack(spacing: 0) { + if !historyModel.isConf { + if let avatarModel = historyModel.avatarModel { + Avatar(contactAvatarModel: avatarModel, avatarSize: 100) + } + + Text(historyModel.addressName) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity) + .padding(.top, 10) + + Text(historyModel.address) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity) + .padding(.top, 5) + + if let avatarModel = historyModel.avatarModel { + Text(avatarModel.lastPresenceInfo) + .foregroundStyle(avatarModel.lastPresenceInfo == "Online" ? Color.greenSuccess500 : Color.orangeWarning600) + .multilineTextAlignment(.center) + .default_text_style_300(styleSize: 12) + .frame(maxWidth: .infinity) + .frame(height: 20) + .padding(.top, 5) + } else { + Text("") + .multilineTextAlignment(.center) + .default_text_style_300(styleSize: 12) + .frame(maxWidth: .infinity) + .frame(height: 20) + } } else { - historyListViewModel.callLogsAddressToDelete = SharedMainViewModel.shared.displayedCall!.address - } - - isShowDeleteAllHistoryPopup.toggle() - - } label: { - HStack { - Text("menu_delete_history") - Spacer() - Image("trash-simple-red") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) + VStack { + Image("users-three-square") + .renderingMode(.template) + .resizable() + .frame(width: 60, height: 60) + .foregroundStyle(Color.grayMain2c600) + } + .frame(width: 100, height: 100) + .background(Color.grayMain2c200) + .clipShape(Circle()) + + Text(historyModel.subject) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity) + .padding(.top, 10) } } - } label: { - Image("dots-three-vertical") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } - .padding(.leading) - .onTapGesture { - isMenuOpen = true + .frame(minHeight: 150) + .frame(maxWidth: .infinity) + .padding(.top, 10) + .padding(.bottom, 2) + .background(Color.gray100) + + HStack { + Spacer() + + if !historyModel.isConf { + Button(action: { + telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone) + }, label: { + VStack { + HStack(alignment: .center) { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("contact_call_action") + .default_text_style(styleSize: 14) + .frame(minWidth: 80) + } + }) + + Spacer() + + Button(action: { + //contactsListViewModel.createOneToOneChatRoomWith(remote: historyModel.addressLinphone) + }, label: { + VStack { + HStack(alignment: .center) { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("contact_message_action") + .default_text_style(styleSize: 14) + .frame(minWidth: 80) + } + }) + + Spacer() + + Button(action: { + telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone, isVideo: true) + }, label: { + VStack { + HStack(alignment: .center) { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("contact_video_call_action") + .default_text_style(styleSize: 14) + .frame(minWidth: 80) + } + }) + } else { + Button(action: { + withAnimation { + if historyModel.address.hasPrefix("sip:conference-focus@sip.linphone.org") { + do { + let meetingAddress = try Factory.Instance.createAddress(addr: historyModel.address) + + telecomManager.meetingWaitingRoomDisplayed = true + telecomManager.meetingWaitingRoomSelected = meetingAddress + } catch {} + } else { + telecomManager.doCallOrJoinConf(address: historyModel.addressLinphone) + } + } + }, label: { + VStack { + HStack(alignment: .center) { + Image("users-three-square") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("meeting_waiting_room_join") + .default_text_style(styleSize: 14) + .frame(minWidth: 80) + } + }) + } + + Spacer() + } + .padding(.top, 20) + .padding(.bottom, 10) + .frame(maxWidth: .infinity) + .background(Color.gray100) + + VStack(spacing: 0) { + let callLogsFilter = historyListViewModel.callLogs.filter({ $0.address == historyModel.address }) + + ForEach(0..