From ce9f6c454c9f4cafdc69364cbe811a3cf14bc334 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Wed, 15 Nov 2023 16:18:06 +0100 Subject: [PATCH] Add history call list --- Linphone.xcodeproj/project.pbxproj | 16 ++ .../trash-simple-red.imageset/Contents.json | 21 ++ .../trash-simple-red.svg | 3 + Linphone/Contacts/ContactsManager.swift | 14 +- Linphone/LinphoneApp.swift | 9 +- Linphone/Localizable.xcstrings | 20 +- .../ContactInnerActionsFragment.swift | 24 +- .../Contacts/Fragments/ContactsFragment.swift | 4 +- .../Fragments/ContactsInnerFragment.swift | 15 +- Linphone/UI/Main/ContentView.swift | 189 +++++++++----- .../History/Fragments/HistoryFragment.swift | 92 +++++++ .../Fragments/HistoryListBottomSheet.swift | 240 ++++++++++++++++++ .../Fragments/HistoryListFragment.swift | 232 +++++++++++++++++ Linphone/UI/Main/History/HistoryView.swift | 50 +++- .../ViewModel/HistoryListViewModel.swift | 142 +++++++++++ .../History/ViewModel/HistoryViewModel.swift | 5 +- Linphone/Utils/EditContactController.swift | 2 +- 17 files changed, 978 insertions(+), 100 deletions(-) create mode 100644 Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json create mode 100644 Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg create mode 100644 Linphone/UI/Main/History/Fragments/HistoryFragment.swift create mode 100644 Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift create mode 100644 Linphone/UI/Main/History/Fragments/HistoryListFragment.swift create mode 100644 Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 64f20df16..1279fcc32 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -29,6 +29,10 @@ D72343362AD037AF009AA24E /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72343352AD037AF009AA24E /* ToastView.swift */; }; D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */; }; D732A9092AFD235500DB42BA /* ShareSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9082AFD235500DB42BA /* ShareSheetController.swift */; }; + D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */; }; + D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */; }; + D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */; }; + D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */; }; D748BF2C2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */; }; D748BF2E2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */; }; D74C9CF82ACACECE0021626A /* WelcomePage1Fragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */; }; @@ -95,6 +99,10 @@ D72343352AD037AF009AA24E /* ToastView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = ""; }; D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryContactFragment.swift; sourceTree = ""; }; D732A9082AFD235500DB42BA /* ShareSheetController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareSheetController.swift; sourceTree = ""; }; + D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFragment.swift; sourceTree = ""; }; + D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListFragment.swift; sourceTree = ""; }; + D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListViewModel.swift; sourceTree = ""; }; + D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryListBottomSheet.swift; sourceTree = ""; }; D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountLoginFragment.swift; sourceTree = ""; }; D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThirdPartySipAccountWarningFragment.swift; sourceTree = ""; }; D74C9CF72ACACECE0021626A /* WelcomePage1Fragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomePage1Fragment.swift; sourceTree = ""; }; @@ -267,6 +275,7 @@ isa = PBXGroup; children = ( D72250622ADE9615008FB426 /* HistoryViewModel.swift */, + D732A9142B04C7FE00DB42BA /* HistoryListViewModel.swift */, ); path = ViewModel; sourceTree = ""; @@ -275,6 +284,9 @@ isa = PBXGroup; children = ( D72992382ADD7F68003AF125 /* HistoryContactFragment.swift */, + D732A90E2B04C3B400DB42BA /* HistoryFragment.swift */, + D732A9122B04C7A300DB42BA /* HistoryListFragment.swift */, + D732A91A2B061BD900DB42BA /* HistoryListBottomSheet.swift */, ); path = Fragments; sourceTree = ""; @@ -510,12 +522,14 @@ D750D3392AD3E6EE00EC99C5 /* PopupLoadingView.swift in Sources */, D7E6D0492AE933AD00A57AAF /* FavoriteContactsListFragment.swift in Sources */, D706BA822ADD72D100278F45 /* DeviceRotationViewModifier.swift in Sources */, + D732A9132B04C7A300DB42BA /* HistoryListFragment.swift in Sources */, D719ABC92ABC6FD700B41C10 /* CoreContext.swift in Sources */, D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */, D777DBB32AE12C5900565A99 /* ContactsManager.swift in Sources */, D7C3650A2AF001C300FE6142 /* EditContactFragment.swift in Sources */, D7A03FBD2ACC2DB60081A588 /* ContactsView.swift in Sources */, D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */, + D732A90F2B04C3B400DB42BA /* HistoryFragment.swift in Sources */, D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */, D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */, D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */, @@ -523,9 +537,11 @@ D7C48DF42AFA66F900D938CB /* EditContactController.swift in Sources */, D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */, D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */, + D732A9152B04C7FE00DB42BA /* HistoryListViewModel.swift in Sources */, D71FCA7F2AE1397200D2E43E /* ContactsListViewModel.swift in Sources */, D71FCA812AE14CFC00D2E43E /* ContactsListFragment.swift in Sources */, D719ABB72ABC67BF00B41C10 /* LinphoneApp.swift in Sources */, + D732A91B2B061BD900DB42BA /* HistoryListBottomSheet.swift in Sources */, D72250632ADE9615008FB426 /* HistoryViewModel.swift in Sources */, D7E6D0512AEBDBD500A57AAF /* ContactsListBottomSheet.swift in Sources */, D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */, diff --git a/Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json b/Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json new file mode 100644 index 000000000..02ba54e4e --- /dev/null +++ b/Linphone/Assets.xcassets/trash-simple-red.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "trash-simple-red.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg b/Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg new file mode 100644 index 000000000..ea7d36f6a --- /dev/null +++ b/Linphone/Assets.xcassets/trash-simple-red.imageset/trash-simple-red.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index fa9843527..9db3527c0 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -271,7 +271,7 @@ final class ContactsManager { } } - func getFriend(contact: Contact) -> Friend? { + func getFriendWithContact(contact: Contact) -> Friend? { if friendList != nil { let friend = friendList!.friends.first(where: {$0.nativeUri == contact.identifier}) return friend @@ -279,6 +279,18 @@ final class ContactsManager { return nil } } + + func getFriendWithAddress(address: Address) -> Friend? { + if friendList != nil { + var friend = friendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == address.asStringUriOnly()})}) + if friend == nil { + friend = linphoneFriendList!.friends.first(where: {$0.addresses.contains(where: {$0.asStringUriOnly() == address.asStringUriOnly()})}) + } + return friend + } else { + return nil + } + } } struct PhoneNumber { diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 85f14b81c..35b3a538e 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -36,8 +36,13 @@ struct LinphoneApp: App { AssistantView(sharedMainViewModel: sharedMainViewModel) .toast(isShowing: $coreContext.toastMessage) } else if coreContext.defaultAccount != nil { - ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel()) - .toast(isShowing: $coreContext.toastMessage) + ContentView( + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), + historyViewModel: HistoryViewModel(), + historyListViewModel: HistoryListViewModel() + ) + .toast(isShowing: $coreContext.toastMessage) } } else { SplashScreen(isActive: $isActive) diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 0062a8ce1..695ac7218 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -104,9 +104,15 @@ }, "Add a picture" : { + }, + "Add the contact" : { + }, "Add to favourites" : { + }, + "All calls will be removed from the history." : { + }, "All contacts" : { @@ -172,6 +178,9 @@ }, "Copy number" : { + }, + "Copy SIP address" : { + }, "D'accord" : { @@ -187,6 +196,9 @@ }, "Delete %@?" : { + }, + "Delete all history" : { + }, "Delete this contact" : { @@ -199,6 +211,9 @@ }, "Display Name" : { + }, + "Do you really want to delete all calls history?" : { + }, "Domain" : { @@ -293,7 +308,7 @@ "Next" : { }, - "No calls for the moment..." : { + "No call for the moment..." : { }, "No contacts for the moment..." : { @@ -372,6 +387,9 @@ }, "See all" : { + }, + "See contact" : { + }, "See Linphone contact" : { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index ff8e3e2e7..8714c25e3 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -1,9 +1,21 @@ -// -// ContactInnerActionsFragment.swift -// Linphone -// -// Created by Benoît Martins on 09/11/2023. -// +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ import SwiftUI diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift index ef48b5e84..8382da399 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift @@ -42,10 +42,10 @@ struct ContactsFragment: View { showingSheet: $showingSheet, showShareSheet: $showShareSheet ) - .presentationDetents([.fraction(0.2)]) + .presentationDetents([.fraction(0.2)]) } .sheet(isPresented: $showShareSheet) { - ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!) + ShareSheet(friendToShare: contactViewModel.selectedFriendToShare!) .presentationDetents([.medium]) .edgesIgnoringSafeArea(.bottom) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift index 8e09e519f..4f779b294 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift @@ -20,10 +20,7 @@ import SwiftUI import linphonesw -struct ContactsInnerFragment: View { - - @Environment(\.scenePhase) var scenePhase - +struct ContactsInnerFragment: View { @ObservedObject var magicSearch = MagicSearchSingleton.shared @ObservedObject var contactViewModel: ContactViewModel @@ -76,16 +73,6 @@ struct ContactsInnerFragment: View { ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel(), showingSheet: $showingSheet) } .navigationBarHidden(true) - .onChange(of: scenePhase) { newPhase in - if newPhase == .active { - ContactsManager.shared.fetchContacts() - print("Active") - } else if newPhase == .inactive { - print("Inactive") - } else if newPhase == .background { - print("Background") - } - } } } diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index d9a8307c1..6c677de8a 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -17,18 +17,23 @@ * along with this program. If not, see . */ +// swiftlint:disable type_body_length import SwiftUI import linphonesw struct ContentView: View { + @Environment(\.scenePhase) var scenePhase + + @ObservedObject private var coreContext = CoreContext.shared + var contactManager = ContactsManager.shared var magicSearch = MagicSearchSingleton.shared @ObservedObject var contactViewModel: ContactViewModel @ObservedObject var editContactViewModel: EditContactViewModel @ObservedObject var historyViewModel: HistoryViewModel - @ObservedObject private var coreContext = CoreContext.shared + @ObservedObject var historyListViewModel: HistoryListViewModel @State var index = 0 @State private var orientation = UIDevice.current.orientation @@ -38,7 +43,8 @@ struct ContentView: View { @State private var text = "" @FocusState private var focusedField: Bool @State var isMenuOpen = false - @State var isShowDeletePopup = false + @State var isShowDeleteContactPopup = false + @State var isShowDeleteAllHistoryPopup = false @State var isShowEditContactFragment = false @State var isShowDismissPopup = false @@ -134,34 +140,50 @@ struct ContentView: View { } Menu { - Button { - isMenuOpen = false - magicSearch.allContact = true - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } label: { - HStack { - Text("See all") - Spacer() - if magicSearch.allContact { - Image("green-check") - .resizable() - .frame(width: 25, height: 25, alignment: .leading) + if index == 0 { + Button { + isMenuOpen = false + magicSearch.allContact = true + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } label: { + HStack { + Text("See all") + Spacer() + if magicSearch.allContact { + Image("green-check") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + } } } - } - - Button { - isMenuOpen = false - magicSearch.allContact = false - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } label: { - HStack { - Text("See Linphone contact") - Spacer() - if !magicSearch.allContact { - Image("green-check") + + Button { + isMenuOpen = false + magicSearch.allContact = false + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } label: { + HStack { + Text("See Linphone contact") + Spacer() + if !magicSearch.allContact { + Image("green-check") + .resizable() + .frame(width: 25, height: 25, alignment: .leading) + } + } + } + } else { + Button(role: .destructive) { + isMenuOpen = false + isShowDeleteAllHistoryPopup.toggle() + //historyListViewModel.removeCallLogs() + } label: { + HStack { + Text("Delete all history") + Spacer() + Image("trash-simple-red") .resizable() .frame(width: 25, height: 25, alignment: .leading) } @@ -193,9 +215,14 @@ struct ContentView: View { } text = "" - magicSearch.currentFilter = "" - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + + if index == 0 { + magicSearch.currentFilter = "" + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } else { + historyListViewModel.resetFilterCallLogs() + } } label: { Image("caret-left") .renderingMode(.template) @@ -226,9 +253,13 @@ struct ContentView: View { self.focusedField = true } .onChange(of: text) { newValue in - magicSearch.currentFilter = newValue - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + if index == 0 { + magicSearch.currentFilter = newValue + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } else { + historyListViewModel.filterCallLogs(filter: text) + } } } else { TextEditor(text: Binding( @@ -281,10 +312,17 @@ struct ContentView: View { historyViewModel: historyViewModel, editContactViewModel: editContactViewModel, isShowEditContactFragment: $isShowEditContactFragment, - isShowDeletePopup: $isShowDeletePopup + isShowDeletePopup: $isShowDeleteContactPopup ) } else if self.index == 1 { - HistoryView() + HistoryView( + historyListViewModel: historyListViewModel, + historyViewModel: historyViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + index: $index, + isShowEditContactFragment: $isShowEditContactFragment + ) } } .frame(maxWidth: @@ -367,7 +405,7 @@ struct ContentView: View { } } - if contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty { + if contactViewModel.indexDisplayedFriend != nil || historyViewModel.indexDisplayedCall != nil { HStack(spacing: 0) { Spacer() .frame(maxWidth: @@ -381,12 +419,12 @@ struct ContentView: View { ContactFragment( contactViewModel: contactViewModel, editContactViewModel: editContactViewModel, - isShowDeletePopup: $isShowDeletePopup, + isShowDeletePopup: $isShowDeleteContactPopup, isShowDismissPopup: $isShowDismissPopup ) - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) } else if self.index == 1 { HistoryContactFragment() .frame(maxWidth: .infinity) @@ -433,25 +471,25 @@ struct ContentView: View { isShowEditContactFragment: $isShowEditContactFragment, isShowDismissPopup: $isShowDismissPopup ) - .zIndex(3) - .transition(.move(edge: .bottom)) - .onAppear { - contactViewModel.indexDisplayedFriend = nil - } + .zIndex(3) + .transition(.move(edge: .bottom)) + .onAppear { + contactViewModel.indexDisplayedFriend = nil + } } - if isShowDeletePopup { - PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeletePopup, + if isShowDeleteContactPopup { + PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeleteContactPopup, title: Text( - contactViewModel.selectedFriend != nil - ? "Delete \(contactViewModel.selectedFriend!.name!)?" - : (contactViewModel.indexDisplayedFriend != nil - ? "Delete \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.name!)?" - : "Error Name")), + contactViewModel.selectedFriend != nil + ? "Delete \(contactViewModel.selectedFriend!.name!)?" + : (contactViewModel.indexDisplayedFriend != nil + ? "Delete \(magicSearch.lastSearch[contactViewModel.indexDisplayedFriend!].friend!.name!)?" + : "Error Name")), content: Text("This contact will be deleted definitively."), titleFirstButton: Text("Cancel"), actionFirstButton: { - self.isShowDeletePopup.toggle()}, + self.isShowDeleteContactPopup.toggle()}, titleSecondButton: Text("Ok"), actionSecondButton: { if contactViewModel.selectedFriendToDelete != nil { @@ -470,18 +508,37 @@ struct ContentView: View { } magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - self.isShowDeletePopup.toggle() + self.isShowDeleteContactPopup.toggle() }) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { - self.isShowDeletePopup.toggle() + self.isShowDeleteContactPopup.toggle() } .onAppear { contactViewModel.selectedFriendToDelete = contactViewModel.selectedFriend } } + if isShowDeleteAllHistoryPopup { + PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeleteContactPopup, + title: Text("Do you really want to delete all calls history?"), + content: Text("All calls will be removed from the history."), + titleFirstButton: Text("Cancel"), + actionFirstButton: { + self.isShowDeleteAllHistoryPopup.toggle()}, + titleSecondButton: Text("Ok"), + actionSecondButton: { + historyListViewModel.removeCallLogs() + self.isShowDeleteAllHistoryPopup.toggle() + }) + .background(.black.opacity(0.65)) + .zIndex(3) + .onTapGesture { + self.isShowDeleteAllHistoryPopup.toggle() + } + } + if isShowDismissPopup { PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDismissPopup, title: Text("Don’t save modifications?"), @@ -524,13 +581,23 @@ struct ContentView: View { } } .onRotate { newOrientation in - if (contactViewModel.indexDisplayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive { + if (contactViewModel.indexDisplayedFriend != nil || historyViewModel.indexDisplayedCall != nil) && searchIsActive { self.focusedField = false } else if searchIsActive { self.focusedField = true } orientation = newOrientation } + .onChange(of: scenePhase) { newPhase in + if newPhase == .active { + ContactsManager.shared.fetchContacts() + print("Active") + } else if newPhase == .inactive { + print("Inactive") + } else if newPhase == .background { + print("Background") + } + } } func openMenu() { @@ -541,5 +608,11 @@ struct ContentView: View { } #Preview { - ContentView(contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), historyViewModel: HistoryViewModel()) + ContentView( + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), + historyViewModel: HistoryViewModel(), + historyListViewModel: HistoryListViewModel() + ) } +// swiftlint:enable type_body_length diff --git a/Linphone/UI/Main/History/Fragments/HistoryFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryFragment.swift new file mode 100644 index 000000000..36a39e7b6 --- /dev/null +++ b/Linphone/UI/Main/History/Fragments/HistoryFragment.swift @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import SwiftUI + +struct HistoryFragment: View { + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + + @ObservedObject var historyListViewModel: HistoryListViewModel + @ObservedObject var historyViewModel: HistoryViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel + + @State private var showingSheet = false + @Binding var index: Int + @Binding var isShowEditContactFragment: Bool + + var body: some View { + ZStack { + if #available(iOS 16.0, *) { + if idiom != .pad { + HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet) + .sheet(isPresented: $showingSheet) { + HistoryListBottomSheet( + historyViewModel: historyViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + historyListViewModel: historyListViewModel, + showingSheet: $showingSheet, + index: $index, + isShowEditContactFragment: $isShowEditContactFragment + ) + .presentationDetents([.fraction(0.2)]) + } + } else { + HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet) + .halfSheet(showSheet: $showingSheet) { + HistoryListBottomSheet( + historyViewModel: historyViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + historyListViewModel: historyListViewModel, + showingSheet: $showingSheet, + index: $index, + isShowEditContactFragment: $isShowEditContactFragment + ) + } onDismiss: {} + } + } else { + HistoryListFragment(historyListViewModel: historyListViewModel, historyViewModel: historyViewModel, showingSheet: $showingSheet) + .halfSheet(showSheet: $showingSheet) { + HistoryListBottomSheet( + historyViewModel: historyViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + historyListViewModel: historyListViewModel, + showingSheet: $showingSheet, + index: $index, + isShowEditContactFragment: $isShowEditContactFragment + ) + } onDismiss: {} + } + } + } +} + +#Preview { + HistoryFragment( + historyListViewModel: HistoryListViewModel(), + historyViewModel: HistoryViewModel(), + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), + index: .constant(1), + isShowEditContactFragment: .constant(false) + ) +} diff --git a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift new file mode 100644 index 000000000..a9c9092c1 --- /dev/null +++ b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import SwiftUI +import UniformTypeIdentifiers + +struct HistoryListBottomSheet: View { + + @Environment(\.dismiss) var dismiss + + private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + + @ObservedObject var historyViewModel: HistoryViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel + @ObservedObject var historyListViewModel: HistoryListViewModel + + @State private var orientation = UIDevice.current.orientation + + @Binding var showingSheet: Bool + @Binding var index: Int + @Binding var isShowEditContactFragment: Bool + + var body: some View { + VStack(alignment: .leading) { + if idiom != .pad && (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Spacer() + HStack { + Spacer() + Button("Close") { + if #available(iOS 16.0, *) { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } + } + .padding(.trailing) + } + + Spacer() + Button { + + if #available(iOS 16.0, *) { + if idiom != .pad { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } else { + showingSheet.toggle() + dismiss() + } + + index = 0 + + if ContactsManager.shared.getFriendWithAddress( + address: historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing + ? historyViewModel.selectedCall!.toAddress! + : historyViewModel.selectedCall!.fromAddress! + ) != nil { + let addressCall = historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing + ? historyViewModel.selectedCall!.toAddress! + : historyViewModel.selectedCall!.fromAddress! + + let friendIndex = MagicSearchSingleton.shared.lastSearch.firstIndex(where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall.asStringUriOnly()})}) + if friendIndex != nil { + withAnimation { + contactViewModel.indexDisplayedFriend = friendIndex + } + } + } else { + let addressCall = historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing + ? historyViewModel.selectedCall!.toAddress! + : historyViewModel.selectedCall!.fromAddress! + + withAnimation { + isShowEditContactFragment.toggle() + editContactViewModel.sipAddresses.removeAll() + editContactViewModel.sipAddresses.append(String(addressCall.asStringUriOnly().dropFirst(4))) + editContactViewModel.sipAddresses.append("") + } + } + } label: { + HStack { + if ContactsManager.shared.getFriendWithAddress( + address: historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing + ? historyViewModel.selectedCall!.toAddress! + : historyViewModel.selectedCall!.fromAddress! + ) != nil { + Image("user-circle") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text("See contact") + .default_text_style(styleSize: 16) + Spacer() + } else { + Image("plus-circle") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text("Add the contact") + .default_text_style(styleSize: 16) + Spacer() + } + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + VStack { + Divider() + } + .frame(maxWidth: .infinity) + + Button { + if historyViewModel.selectedCall != nil && historyViewModel.selectedCall!.dir == .Outgoing { + UIPasteboard.general.setValue( + historyViewModel.selectedCall!.toAddress!.asStringUriOnly().dropFirst(4), + forPasteboardType: UTType.plainText.identifier + ) + } else { + UIPasteboard.general.setValue( + historyViewModel.selectedCall!.fromAddress!.asStringUriOnly().dropFirst(4), + forPasteboardType: UTType.plainText.identifier + ) + } + + if #available(iOS 16.0, *) { + if idiom != .pad { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } else { + showingSheet.toggle() + dismiss() + } + } label: { + HStack { + Image("copy") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text("Copy SIP address") + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + VStack { + Divider() + } + .frame(maxWidth: .infinity) + + Button { + CoreContext.shared.doOnCoreQueue { core in + if historyViewModel.selectedCall != nil { + core.removeCallLog(callLog: historyViewModel.selectedCall!) + historyListViewModel.removeCallLog(callLog: historyViewModel.selectedCall!) + } + } + + if #available(iOS 16.0, *) { + if idiom != .pad { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } else { + showingSheet.toggle() + dismiss() + } + } label: { + HStack { + Image("trash-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.redDanger500) + .frame(width: 25, height: 25, alignment: .leading) + Text("Delete") + .foregroundStyle(Color.redDanger500) + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + } + .background(Color.gray100) + .frame(maxWidth: .infinity) + .onRotate { newOrientation in + orientation = newOrientation + } + } +} + +#Preview { + HistoryListBottomSheet( + historyViewModel: HistoryViewModel(), + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), + historyListViewModel: HistoryListViewModel(), + showingSheet: .constant(false), + index: .constant(1), + isShowEditContactFragment: .constant(false) + ) +} diff --git a/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift new file mode 100644 index 000000000..f370c3352 --- /dev/null +++ b/Linphone/UI/Main/History/Fragments/HistoryListFragment.swift @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import SwiftUI +import linphonesw + +struct HistoryListFragment: View { + + @ObservedObject var historyListViewModel: HistoryListViewModel + @ObservedObject var historyViewModel: HistoryViewModel + + @Binding var showingSheet: Bool + + var body: some View { + VStack { + List { + ForEach(0.. 1 + ? historyListViewModel.callLogs[index].toAddress!.displayName!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + + } else { + Image(uiImage: ContactsManager.shared.textToImage( + firstName: historyListViewModel.callLogs[index].toAddress!.username ?? "Username Error", + lastName: historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ").count > 1 + ? historyListViewModel.callLogs[index].toAddress!.username!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + } + + } else if historyListViewModel.callLogs[index].fromAddress != nil { + if historyListViewModel.callLogs[index].fromAddress!.displayName != nil { + Image(uiImage: ContactsManager.shared.textToImage( + firstName: historyListViewModel.callLogs[index].fromAddress!.displayName!, + lastName: historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ").count > 1 + ? historyListViewModel.callLogs[index].fromAddress!.displayName!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + } else { + Image(uiImage: ContactsManager.shared.textToImage( + firstName: historyListViewModel.callLogs[index].fromAddress!.username ?? "Username Error", + lastName: historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ").count > 1 + ? historyListViewModel.callLogs[index].fromAddress!.username!.components(separatedBy: " ")[1] + : "")) + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + } + } + } + + VStack(spacing: 0) { + Spacer() + + let fromAddressFriend = ContactsManager.shared.getFriendWithAddress(address: historyListViewModel.callLogs[index].fromAddress!) + let toAddressFriend = ContactsManager.shared.getFriendWithAddress(address: historyListViewModel.callLogs[index].toAddress!) + + if historyListViewModel.callLogs[index].dir == .Incoming && fromAddressFriend != nil { + Text(fromAddressFriend!.name!) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else if historyListViewModel.callLogs[index].dir == .Outgoing && toAddressFriend != nil { + Text(toAddressFriend!.name!) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else { + if historyListViewModel.callLogs[index].dir == .Outgoing && historyListViewModel.callLogs[index].toAddress != nil { + Text(historyListViewModel.callLogs[index].toAddress!.displayName != nil + ? historyListViewModel.callLogs[index].toAddress!.displayName! + : historyListViewModel.callLogs[index].toAddress!.username!) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else if historyListViewModel.callLogs[index].fromAddress != nil { + Text(historyListViewModel.callLogs[index].fromAddress!.displayName != nil + ? historyListViewModel.callLogs[index].fromAddress!.displayName! + : historyListViewModel.callLogs[index].fromAddress!.username!) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } + } + HStack { + Image(historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir)) + .resizable() + .frame( + width: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).contains("rejected") ? 12 : 8, + height: historyListViewModel.getCallIconResId(callStatus: historyListViewModel.callLogs[index].status, callDir: historyListViewModel.callLogs[index].dir).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() + } + + Image("phone") + .resizable() + .frame(width: 25, height: 25) + .padding(.trailing, 5) + } + } + .buttonStyle(.borderless) + .listRowInsets(EdgeInsets(top: 5, leading: 20, bottom: 5, trailing: 20)) + .listRowSeparator(.hidden) + .simultaneousGesture( + LongPressGesture() + .onEnded { _ in + historyViewModel.selectedCall = historyListViewModel.callLogs[index] + showingSheet.toggle() + } + ) + .highPriorityGesture( + TapGesture() + .onEnded { _ in + withAnimation { + //historyViewModel.indexDisplayedCall = index + } + } + ) + } + } + .listStyle(.plain) + .overlay( + VStack { + if historyListViewModel.callLogs.isEmpty { + Spacer() + Image("illus-belledonne") + .resizable() + .scaledToFit() + .clipped() + .padding(.all) + Text("No call for the moment...") + .default_text_style_800(styleSize: 16) + Spacer() + Spacer() + } + } + .padding(.all) + ) + } + } +} + +#Preview { + HistoryListFragment(historyListViewModel: HistoryListViewModel(), historyViewModel: HistoryViewModel(), showingSheet: .constant(false)) +} diff --git a/Linphone/UI/Main/History/HistoryView.swift b/Linphone/UI/Main/History/HistoryView.swift index 25d6b0b1e..a308d4688 100644 --- a/Linphone/UI/Main/History/HistoryView.swift +++ b/Linphone/UI/Main/History/HistoryView.swift @@ -21,22 +21,37 @@ import SwiftUI struct HistoryView: View { + @ObservedObject var historyListViewModel: HistoryListViewModel + @ObservedObject var historyViewModel: HistoryViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel + + @Binding var index: Int + @Binding var isShowEditContactFragment: Bool + var body: some View { NavigationView { - VStack(spacing: 0) { - VStack { - Spacer() - Image("illus-belledonne") - .resizable() - .scaledToFit() - .clipped() - .padding(.all) - Text("No calls for the moment...") - .default_text_style_800(styleSize: 16) - Spacer() - Spacer() + ZStack(alignment: .bottomTrailing) { + HistoryFragment( + historyListViewModel: historyListViewModel, + historyViewModel: historyViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + index: $index, + isShowEditContactFragment: $isShowEditContactFragment + ) + + Button { + + } label: { + Image("phone-plus") + .padding() + .background(.white) + .clipShape(Circle()) + .shadow(color: .black.opacity(0.2), radius: 4) + } - .padding(.all) + .padding() } } .navigationViewStyle(.stack) @@ -44,5 +59,12 @@ struct HistoryView: View { } #Preview { - HistoryView() + HistoryFragment( + historyListViewModel: HistoryListViewModel(), + historyViewModel: HistoryViewModel(), + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), + index: .constant(1), + isShowEditContactFragment: .constant(false) + ) } diff --git a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift new file mode 100644 index 000000000..94d57a8d6 --- /dev/null +++ b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2010-2023 Belledonne Communications SARL. + * + * This file is part of linphone-iphone + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +import linphonesw + +class HistoryListViewModel: ObservableObject { + + private var coreContext = CoreContext.shared + + @Published var callLogs: [CallLog] = [] + var callLogsTmp: [CallLog] = [] + + init() { + computeCallLogsList() + } + + func computeCallLogsList() { + coreContext.doOnCoreQueue { core in + let account = core.defaultAccount + let logs = account?.callLogs != nil ? account!.callLogs : core.callLogs + + self.callLogs.removeAll() + self.callLogsTmp.removeAll() + + DispatchQueue.main.async { + logs.forEach { log in + self.callLogs.append(log) + self.callLogsTmp.append(log) + } + } + } + } + + func getCallIconResId(callStatus: Call.Status, callDir: Call.Dir) -> String { + switch callStatus { + case Call.Status.Missed: + if callDir == .Outgoing { + "outgoing-call-missed" + } else { + "incoming-call-missed" + } + + case Call.Status.Success: + if callDir == .Outgoing { + "outgoing-call" + } else { + "incoming-call" + } + + default: + if callDir == .Outgoing { + "outgoing-call-rejected" + } else { + "incoming-call-rejected" + } + } + } + + func getCallTime(startDate: time_t) -> String { + let timeInterval = TimeInterval(startDate) + + let myNSDate = Date(timeIntervalSince1970: timeInterval) + + if Calendar.current.isDateInToday(myNSDate) { + let formatter = DateFormatter() + formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a" + return formatter.string(from: myNSDate) + } else if Calendar.current.isDateInYesterday(myNSDate) { + let formatter = DateFormatter() + formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "HH:mm" : "h:mm a" + return "Yesterday " + formatter.string(from: myNSDate) + } else if Calendar.current.isDate(myNSDate, equalTo: .now, toGranularity: .year) { + let formatter = DateFormatter() + formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM | HH:mm" : "MM/dd | h:mm a" + return formatter.string(from: myNSDate) + } else { + let formatter = DateFormatter() + formatter.dateFormat = Locale.current.identifier == "fr_FR" ? "dd/MM/yy | HH:mm" : "MM/dd/yy | h:mm a" + return formatter.string(from: myNSDate) + } + } + + func filterCallLogs(filter: String) { + callLogs.removeAll() + callLogsTmp.forEach { callLog in + if callLog.dir == .Outgoing && callLog.toAddress != nil { + if callLog.toAddress!.username != nil && callLog.toAddress!.username!.contains(filter) { + callLogs.append(callLog) + } else if callLog.toAddress!.displayName != nil && callLog.toAddress!.displayName!.contains(filter) { + callLogs.append(callLog) + } + } else if callLog.fromAddress != nil { + if callLog.fromAddress!.username != nil && callLog.fromAddress!.username!.contains(filter) { + callLogs.append(callLog) + } else if callLog.fromAddress!.displayName != nil && callLog.fromAddress!.displayName!.contains(filter) { + callLogs.append(callLog) + } + } + } + } + + func resetFilterCallLogs() { + callLogs = callLogsTmp + } + + func removeCallLogs() { + coreContext.doOnCoreQueue { core in + let account = core.defaultAccount + if account != nil { + account!.clearCallLogs() + } else { + core.clearCallLogs() + } + self.callLogs.removeAll() + self.callLogsTmp.removeAll() + } + } + + func removeCallLog(callLog: CallLog) { + let index = self.callLogs.firstIndex(where: {$0.callId == callLog.callId}) + self.callLogs.remove(at: index!) + + let indexTmp = self.callLogsTmp.firstIndex(where: {$0.callId == callLog.callId}) + self.callLogsTmp.remove(at: index!) + } +} diff --git a/Linphone/UI/Main/History/ViewModel/HistoryViewModel.swift b/Linphone/UI/Main/History/ViewModel/HistoryViewModel.swift index 3222b6a0f..850a2f2ab 100644 --- a/Linphone/UI/Main/History/ViewModel/HistoryViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/HistoryViewModel.swift @@ -18,10 +18,13 @@ */ import Foundation +import linphonesw class HistoryViewModel: ObservableObject { - @Published var historyTitle: String = "" + @Published var indexDisplayedCall: Int? + + var selectedCall: CallLog? init() {} } diff --git a/Linphone/Utils/EditContactController.swift b/Linphone/Utils/EditContactController.swift index b3a9d250e..b29718a4e 100644 --- a/Linphone/Utils/EditContactController.swift +++ b/Linphone/Utils/EditContactController.swift @@ -53,7 +53,7 @@ struct EditContactView: UIViewControllerRepresentable { name: cnc.givenName + cnc.familyName + String(Int.random(in: 1...1000)) + ((imageThumbnail == nil) ? "-default" : ""), contact: newContact, linphoneFriend: false, - existingFriend: ContactsManager.shared.getFriend(contact: newContact)) + existingFriend: ContactsManager.shared.getFriendWithContact(contact: newContact)) MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) }