From 219ee2d438bbd6d9e473658ca187df1f827d4aaf Mon Sep 17 00:00:00 2001 From: "benoit.martins" Date: Thu, 26 Oct 2023 17:00:54 +0200 Subject: [PATCH] Changes ContactsManager --- .../Contents.json | 21 + .../profil-picture-default.svg | 18 + Linphone/Contacts/ContactsManager.swift | 321 ++++--- Linphone/LinphoneApp.swift | 13 +- .../Contacts/Fragments/ContactsFragment.swift | 68 +- .../Fragments/ContactsListFragment.swift | 4 +- .../FavoriteContactsListFragment.swift | 8 +- .../Contacts/ViewModel/ContactViewModel.swift | 6 +- .../ViewModel/ContactsListViewModel.swift | 8 +- Linphone/UI/Main/ContentView.swift | 793 +++++++++--------- 10 files changed, 643 insertions(+), 617 deletions(-) create mode 100644 Linphone/Assets.xcassets/profil-picture-default.imageset/Contents.json create mode 100644 Linphone/Assets.xcassets/profil-picture-default.imageset/profil-picture-default.svg diff --git a/Linphone/Assets.xcassets/profil-picture-default.imageset/Contents.json b/Linphone/Assets.xcassets/profil-picture-default.imageset/Contents.json new file mode 100644 index 000000000..68bdd056b --- /dev/null +++ b/Linphone/Assets.xcassets/profil-picture-default.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "profil-picture-default.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/profil-picture-default.imageset/profil-picture-default.svg b/Linphone/Assets.xcassets/profil-picture-default.imageset/profil-picture-default.svg new file mode 100644 index 000000000..b2bea3b29 --- /dev/null +++ b/Linphone/Assets.xcassets/profil-picture-default.imageset/profil-picture-default.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 5de3eb683..a7a845569 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -24,213 +24,198 @@ import SwiftUI final class ContactsManager: ObservableObject { static let shared = ContactsManager() - + private var coreContext = CoreContext.shared - - @Published var contacts: [Contact] = [] + private var magicSearch = MagicSearchSingleton.shared private let nativeAddressBookFriendList = "Native address-book" let linphoneAddressBookFirendList = "Linphone address-book" + + @Published var friendList: FriendList? private init() { fetchContacts() } func fetchContacts() { - contacts.removeAll() DispatchQueue.global().async { - let store = CNContactStore() - store.requestAccess(for: .contacts) { (granted, error) in - if let error = error { - print("failed to request access", error) - return - } - if granted { - let keys = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey, - CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey, - CNContactPostalAddressesKey, CNContactIdentifierKey, - CNInstantMessageAddressUsernameKey, CNContactInstantMessageAddressesKey, - CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactOrganizationNameKey] - let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor]) - do { - try store.enumerateContacts(with: request, usingBlock: { (contact, _) in - - DispatchQueue.main.sync { - self.contacts.append( - Contact( - firstName: contact.givenName, - lastName: contact.familyName, - organizationName: contact.organizationName, - displayName: contact.nickname, - sipAddresses: contact.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" }, - phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)}, - imageData: self.saveImage( - image: - UIImage(data: contact.thumbnailImageData ?? Data()) - ?? self.textToImage(firstName: contact.givenName.isEmpty - && contact.familyName.isEmpty - && contact.phoneNumbers.first?.value.stringValue != nil - ? contact.phoneNumbers.first!.value.stringValue - : contact.givenName, lastName: contact.familyName), - name: contact.identifier) - ) - ) - } - self.contacts.sort(by: { - $0.firstName.folding( - options: .diacriticInsensitive, locale: .current - ) < $1.firstName.folding( - options: .diacriticInsensitive, locale: .current - ) - }) - }) - - } catch let error { - print("Failed to enumerate contact", error) - } - - } else { - print("access denied") - } - } - - var friends: [Friend] = [] - - self.contacts.forEach { contact in - do { - let friend = try self.coreContext.mCore.createFriend() - friend.edit() - try friend.setName(newValue: contact.firstName + " " + contact.lastName) - friend.organization = contact.organizationName - - var friendAddresses: [Address] = [] - contact.sipAddresses.forEach { sipAddress in - let address = self.coreContext.mCore.interpretUrl(url: sipAddress, applyInternationalPrefix: true) - - if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) { - friend.addAddress(address: address!) - friendAddresses.append(address!) - } - } - - var friendPhoneNumbers: [PhoneNumber] = [] - contact.phoneNumbers.forEach { phone in - do { - if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil { - let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: phone.numLabel) - friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber) - friendPhoneNumbers.append(phone) - } - } catch let error { - print("Failed to enumerate contact", error) - } - } - - let contactImage = contact.imageData.dropFirst(8) - friend.photo = "file:/" + contactImage - - friend.done() - friends.append(friend) - - } catch let error { - print("Failed to enumerate contact", error) - } - } - if self.coreContext.mCore.globalState == GlobalState.Shutdown || self.coreContext.mCore.globalState == GlobalState.Off { print("$TAG Core is being stopped or already destroyed, abort") - } else if friends.isEmpty { - print("$TAG No friend created!") - } else { + } else { print("$TAG ${friends.size} friends created") - let fetchedFriends = friends - - let nativeFriendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList) - var friendList = nativeFriendList - if friendList == nil { + self.friendList = self.coreContext.mCore.getFriendListByName(name: self.nativeAddressBookFriendList) + if self.friendList == nil { do { - friendList = try self.coreContext.mCore.createFriendList() + self.friendList = try self.coreContext.mCore.createFriendList() } catch let error { print("Failed to enumerate contact", error) } } - if friendList!.displayName == nil || friendList!.displayName!.isEmpty { + if self.friendList!.displayName == nil || self.friendList!.displayName!.isEmpty { print( "$TAG Friend list [$nativeAddressBookFriendList] didn't exist yet, let's create it" ) - friendList?.databaseStorageEnabled = false // We don't want to store local address-book in DB + self.friendList!.databaseStorageEnabled = false // We don't want to store local address-book in DB - friendList!.displayName = self.nativeAddressBookFriendList - self.coreContext.mCore.addFriendList(list: friendList!) + self.friendList!.displayName = self.nativeAddressBookFriendList + self.coreContext.mCore.addFriendList(list: self.friendList!) } else { print( "$TAG Friend list [$LINPHONE_ADDRESS_BOOK_FRIEND_LIST] found, removing existing friends if any" ) - friendList!.friends.forEach { friend in - _ = friendList!.removeFriend(linphoneFriend: friend) + self.friendList!.friends.forEach { friend in + _ = self.friendList!.removeFriend(linphoneFriend: friend) } } - - fetchedFriends.forEach { friend in - _ = friendList!.addLocalFriend(linphoneFriend: friend) - } - - friends.removeAll() - - print("$TAG Friends added") - - friendList!.updateSubscriptions() - print("$TAG Subscription(s) updated") } + + let store = CNContactStore() + store.requestAccess(for: .contacts) { (granted, error) in + if let error = error { + print("failed to request access", error) + return + } + if granted { + let keys = [CNContactEmailAddressesKey, CNContactPhoneNumbersKey, + CNContactFamilyNameKey, CNContactGivenNameKey, CNContactNicknameKey, + CNContactPostalAddressesKey, CNContactIdentifierKey, + CNInstantMessageAddressUsernameKey, CNContactInstantMessageAddressesKey, + CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactOrganizationNameKey] + let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor]) + do { + try store.enumerateContacts(with: request, usingBlock: { (contact, _) in + + DispatchQueue.main.sync { + let newContact = Contact( + firstName: contact.givenName, + lastName: contact.familyName, + organizationName: contact.organizationName, + displayName: contact.nickname, + sipAddresses: contact.instantMessageAddresses.map { $0.value.service == "SIP" ? $0.value.username : "" }, + phoneNumbers: contact.phoneNumbers.map { PhoneNumber(numLabel: $0.label ?? "", num: $0.value.stringValue)}, + imageData: "" + ) + + self.saveImage( + image: + UIImage(data: contact.thumbnailImageData ?? Data()) + ?? self.textToImage( + firstName: contact.givenName.isEmpty + && contact.familyName.isEmpty + && contact.phoneNumbers.first?.value.stringValue != nil + ? contact.phoneNumbers.first!.value.stringValue + : contact.givenName, lastName: contact.familyName), + name: contact.givenName + contact.familyName + String(Int.random(in: 1...1000)), + contact: newContact) + } + }) + + } catch let error { + print("Failed to enumerate contact", error) + } + + } else { + print("access denied") + } + } + self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) } } + + func textToImage(firstName: String, lastName: String) -> UIImage { + let lblNameInitialize = UILabel() + lblNameInitialize.frame.size = CGSize(width: 100.0, height: 100.0) + lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 40) + lblNameInitialize.textColor = UIColor(Color.grayMain2c600) + + var textToDisplay = "" + if firstName.first != nil { + textToDisplay += String(firstName.first!) + } + if lastName.first != nil { + textToDisplay += String(lastName.first!) + } + + lblNameInitialize.text = textToDisplay.uppercased() + lblNameInitialize.textAlignment = .center + lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200) + lblNameInitialize.layer.cornerRadius = 10.0 + + var IBImgViewUserProfile = UIImage() + UIGraphicsBeginImageContext(lblNameInitialize.frame.size) + lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!) + IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + + return IBImgViewUserProfile + } - func saveImage(image: UIImage, name: String) -> String { + func saveImage(image: UIImage, name: String, contact: Contact) { guard let data = image.jpegData(compressionQuality: 1) ?? image.pngData() else { - return "" - } - let directory = FileManager.default.temporaryDirectory - print("FileManagerFileManager \(directory.absoluteString)") - do { - try data.write(to: directory.appendingPathComponent(name + ".png")) - return directory.appendingPathComponent(name + ".png").absoluteString - } catch { - print(error.localizedDescription) - return "" + return } + + awaitDataWrite(data: data, name: name) { _, result in + do { + let friend = try self.coreContext.mCore.createFriend() + friend.edit() + try friend.setName(newValue: contact.firstName + " " + contact.lastName) + friend.organization = contact.organizationName + + var friendAddresses: [Address] = [] + contact.sipAddresses.forEach { sipAddress in + let address = self.coreContext.mCore.interpretUrl(url: sipAddress, applyInternationalPrefix: true) + + if address != nil && ((friendAddresses.firstIndex(where: {$0.asString() == address?.asString()})) == nil) { + friend.addAddress(address: address!) + friendAddresses.append(address!) + } + } + + var friendPhoneNumbers: [PhoneNumber] = [] + contact.phoneNumbers.forEach { phone in + do { + if (friendPhoneNumbers.firstIndex(where: {$0.numLabel == phone.numLabel})) == nil { + let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: phone.numLabel) + friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber) + friendPhoneNumbers.append(phone) + } + } catch let error { + print("Failed to enumerate contact", error) + } + } + + let contactImage = result.dropFirst(8) + friend.photo = "file:/" + contactImage + + friend.done() + + _ = self.friendList!.addLocalFriend(linphoneFriend: friend) + + self.friendList!.updateSubscriptions() + + } catch let error { + print("Failed to enumerate contact", error) + } + } } - - func textToImage(firstName: String, lastName: String) -> UIImage { - - let lblNameInitialize = UILabel() - lblNameInitialize.frame.size = CGSize(width: 100.0, height: 100.0) - lblNameInitialize.font = UIFont(name: "NotoSans-ExtraBold", size: 40) - lblNameInitialize.textColor = UIColor(Color.grayMain2c600) - - var textToDisplay = "" - if firstName.first != nil { - textToDisplay += String(firstName.first!) - } - if lastName.first != nil { - textToDisplay += String(lastName.first!) - } - - lblNameInitialize.text = textToDisplay.uppercased() - lblNameInitialize.textAlignment = .center - lblNameInitialize.backgroundColor = UIColor(Color.grayMain2c200) - lblNameInitialize.layer.cornerRadius = 10.0 - - var IBImgViewUserProfile = UIImage() - UIGraphicsBeginImageContext(lblNameInitialize.frame.size) - lblNameInitialize.layer.render(in: UIGraphicsGetCurrentContext()!) - IBImgViewUserProfile = UIGraphicsGetImageFromCurrentImageContext()! - UIGraphicsEndImageContext() - - return IBImgViewUserProfile - } + + func awaitDataWrite(data: Data, name: String, completion: @escaping ((), String) -> Void) { + let directory = FileManager.default.temporaryDirectory + + DispatchQueue.main.async { + do { + let decodedData: () = try data.write(to: directory.appendingPathComponent(name + ".png")) + completion(decodedData, directory.appendingPathComponent(name + ".png").absoluteString) // <--- here, return the results + } catch { + print("Error: ", error) // need to deal with errors + completion((), "") // <--- here, should return the error + } + } + } } struct PhoneNumber { diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index b6eba8007..0efeb7e77 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -23,13 +23,22 @@ import SwiftUI struct LinphoneApp: App { @ObservedObject private var coreContext = CoreContext.shared + @ObservedObject private var sharedMainViewModel = SharedMainViewModel() + @State private var isActive = false var body: some Scene { WindowGroup { if isActive { - ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) - .toast(isShowing: $coreContext.toastMessage) + if !sharedMainViewModel.welcomeViewDisplayed { + WelcomeView(sharedMainViewModel: sharedMainViewModel) + } else if coreContext.mCore.defaultAccount == nil || sharedMainViewModel.displayProfileMode { + AssistantView(sharedMainViewModel: sharedMainViewModel) + .toast(isShowing: $coreContext.toastMessage) + } else { + ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) + .toast(isShowing: $coreContext.toastMessage) + } } else { SplashScreen(isActive: $isActive) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift index 00e11cc3a..155004e04 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift @@ -21,6 +21,7 @@ import SwiftUI struct ContactsFragment: View { + @ObservedObject var magicSearch = MagicSearchSingleton.shared @ObservedObject var contactViewModel: ContactViewModel @State private var orientation = UIDevice.current.orientation @@ -29,42 +30,43 @@ struct ContactsFragment: View { var body: some View { VStack(alignment: .leading) { - HStack(alignment: .center) { - Text("Favourites") - .default_text_style_800(styleSize: 16) - - Spacer() - - Image(isFavoriteOpen ? "caret-up" : "caret-down") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25, alignment: .leading) - } - .padding(.top, 30) - .padding(.horizontal, 16) - .background(.white) - .onTapGesture { - withAnimation { - isFavoriteOpen.toggle() + if !magicSearch.lastSearch.filter({ $0.friend?.starred == true }).isEmpty { + HStack(alignment: .center) { + Text("Favourites") + .default_text_style_800(styleSize: 16) + + Spacer() + + Image(isFavoriteOpen ? "caret-up" : "caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + } + .padding(.top, 30) + .padding(.horizontal, 16) + .background(.white) + .onTapGesture { + withAnimation { + isFavoriteOpen.toggle() + } } - } - - if isFavoriteOpen { - FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel()) - .zIndex(-1) - .transition(.move(edge: .top)) - } - - HStack(alignment: .center) { - Text("All contacts") - .default_text_style_800(styleSize: 16) - Spacer() + if isFavoriteOpen { + FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel()) + .zIndex(-1) + .transition(.move(edge: .top)) + } + + HStack(alignment: .center) { + Text("All contacts") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.top, 10) + .padding(.horizontal, 16) } - .padding(.top, 10) - .padding(.horizontal, 16) - ContactsListFragment(contactViewModel: contactViewModel, contactsListViewModel: ContactsListViewModel()) } .navigationBarHidden(true) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift index cd1c984a1..174f00fa0 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift @@ -64,7 +64,7 @@ struct ContactsListFragment: View { .frame(width: 45, height: 45) .clipShape(Circle()) case .failure: - Image("profile-image-example") + Image("profil-picture-default") .resizable() .frame(width: 45, height: 45) .clipShape(Circle()) @@ -73,7 +73,7 @@ struct ContactsListFragment: View { } } } else { - Image("profile-image-example") + Image("profil-picture-default") .resizable() .frame(width: 45, height: 45) .clipShape(Circle()) diff --git a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift index 73275c15e..c013cbd61 100644 --- a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift @@ -29,7 +29,7 @@ struct FavoriteContactsListFragment: View { var body: some View { ScrollView(.horizontal) { HStack { - ForEach(0.. UIScreen.main.bounds.size.height { - VStack { - Group { - Spacer() - Button(action: { - self.index = 0 - }, label: { - VStack { - Image("address-book") - .renderingMode(.template) - .resizable() - .foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600) - .frame(width: 25, height: 25) - if self.index == 0 { - Text("Contacts") - .default_text_style_700(styleSize: 10) - } else { - Text("Contacts") - .default_text_style(styleSize: 10) - } - } - }) - - Spacer() - - Button(action: { - self.index = 1 - contactViewModel.contactTitle = "" - }, label: { - VStack { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600) - .frame(width: 25, height: 25) - if self.index == 1 { - Text("Calls") - .default_text_style_700(styleSize: 10) - } else { - Text("Calls") - .default_text_style(styleSize: 10) - } - } - }) - - Spacer() - } - } - .frame(width: 75) - .padding(.leading, - orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0 - ? -geometry.safeAreaInsets.leading - : 0) - } - - VStack(spacing: 0) { - if searchIsActive == false { - HStack { - Image("profile-image-example") - .resizable() - .frame(width: 45, height: 45) - .clipShape(Circle()) - .onTapGesture { - openMenu() - } - - Text(index == 0 ? "Contacts" : "Calls") - .default_text_style_white_800(styleSize: 20) - .padding(.leading, 10) - - Spacer() - - Button { - withAnimation { - searchIsActive.toggle() - } - } label: { - Image("search") - } - - 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") - } - } + + var body: some View { + GeometryReader { geometry in + ZStack { + VStack(spacing: 0) { + HStack(spacing: 0) { + if orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height { + VStack { + Group { + Spacer() + Button(action: { + self.index = 0 + }, label: { + VStack { + Image("address-book") + .renderingMode(.template) + .resizable() + .foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600) + .frame(width: 25, height: 25) + if self.index == 0 { + Text("Contacts") + .default_text_style_700(styleSize: 10) + } else { + Text("Contacts") + .default_text_style(styleSize: 10) } - - 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") - } - } - } - } label: { - Image(index == 0 ? "filtres" : "more") - } - .padding(.leading) - .onTapGesture { - isMenuOpen = true } - } - .frame(maxWidth: .infinity) - .frame(height: 50) - .padding(.horizontal) - .padding(.bottom, 5) - .background(Color.orangeMain500) - } else { - HStack { - Button { - withAnimation { - self.focusedField = false - searchIsActive.toggle() - } - - text = "" - magicSearch.currentFilter = "" + }) + + Spacer() + + Button(action: { + self.index = 1 + contactViewModel.contactTitle = "" + }, label: { + VStack { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600) + .frame(width: 25, height: 25) + if self.index == 1 { + Text("Calls") + .default_text_style_700(styleSize: 10) + } else { + Text("Calls") + .default_text_style(styleSize: 10) + } + } + }) + + Spacer() + } + } + .frame(width: 75) + .padding(.leading, + orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0 + ? -geometry.safeAreaInsets.leading + : 0) + } + + VStack(spacing: 0) { + if searchIsActive == false { + HStack { + Image("profile-image-example") + .resizable() + .frame(width: 45, height: 45) + .clipShape(Circle()) + .onTapGesture { + openMenu() + } + + Text(index == 0 ? "Contacts" : "Calls") + .default_text_style_white_800(styleSize: 20) + .padding(.leading, 10) + + Spacer() + + Button { + withAnimation { + searchIsActive.toggle() + } + } label: { + Image("search") + } + + Menu { + Button { + isMenuOpen = false + magicSearch.allContact = true magicSearch.searchForContacts( sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } label: { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 25, height: 25, alignment: .leading) - } - - if #available(iOS 16.0, *) { - TextEditor(text: Binding( - get: { - return text - }, - set: { value in - var newValue = value - if value.contains("\n") { - newValue = value.replacingOccurrences(of: "\n", with: "") - } - text = newValue - } - )) - .default_text_style_white_700(styleSize: 15) - .padding(.all, 6) - .accentColor(.white) - .scrollContentBackground(.hidden) - .focused($focusedField) - .onAppear { - self.focusedField = true - } - .onChange(of: text) { newValue in - magicSearch.currentFilter = newValue - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } - } else { - TextEditor(text: Binding( - get: { - return text - }, - set: { value in - var newValue = value - if value.contains("\n") { - newValue = value.replacingOccurrences(of: "\n", with: "") - } - text = newValue - } - )) - .default_text_style_white_700(styleSize: 15) - .padding(.all, 6) - .accentColor(.white) - .focused($focusedField) - .onAppear { - self.focusedField = true - } - .onChange(of: text) { newValue in - magicSearch.currentFilter = newValue - magicSearch.searchForContacts( - sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) - } - } - - Button { - text = "" - } label: { - Image("x") - .renderingMode(.template) - .resizable() - .foregroundStyle(.white) - .frame(width: 25, height: 25, alignment: .leading) - } - .padding(.leading) - } - .frame(maxWidth: .infinity) - .frame(height: 50) - .padding(.horizontal) - .padding(.bottom, 5) - .background(Color.orangeMain500) - } - - if self.index == 0 { - ContactsView(contactViewModel: contactViewModel, historyViewModel: historyViewModel) - } else if self.index == 1 { - HistoryView() - } - } - .frame(maxWidth: - (orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - ? geometry.size.width/100*40 - : .infinity - ) - .background( - Color.white - .shadow(color: Color.gray200, radius: 4, x: 0, y: 0) - .mask(Rectangle().padding(.horizontal, -8)) - ) - - if orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height { - Spacer() - } - } - - if !(orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) && !searchIsActive { - HStack { - Group { - Spacer() - Button(action: { - self.index = 0 - }, label: { - VStack { - Image("address-book") - .renderingMode(.template) - .resizable() - .foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600) - .frame(width: 25, height: 25) - if self.index == 0 { - Text("Contacts") - .default_text_style_700(styleSize: 10) - } else { - Text("Contacts") - .default_text_style(styleSize: 10) - } - } - }) - .padding(.top) - - Spacer() - - Button(action: { - self.index = 1 - contactViewModel.contactTitle = "" - }, label: { - VStack { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600) - .frame(width: 25, height: 25) - if self.index == 1 { - Text("Calls") - .default_text_style_700(styleSize: 10) - } else { - Text("Calls") - .default_text_style(styleSize: 10) - } - } - }) - .padding(.top) - Spacer() - } - } - .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 15) - .background( - Color.white - .shadow(color: Color.gray200, radius: 4, x: 0, y: 0) - .mask(Rectangle().padding(.top, -8)) - ) - } - } - - if !contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty { - HStack(spacing: 0) { - Spacer() - .frame(maxWidth: - (orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - ? (geometry.size.width/100*40) + 75 - : 0 - ) - if self.index == 0 { - ContactFragment(contactViewModel: contactViewModel) - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) - } else if self.index == 1 { - HistoryContactFragment() - .frame(maxWidth: .infinity) - .background(Color.gray100) - .ignoresSafeArea(.keyboard) - } - } - .onAppear { - if !(orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - && searchIsActive { - self.focusedField = false - } - } - .onDisappear { - if !(orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) - && searchIsActive { - self.focusedField = true - } - } - .padding(.leading, - orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0 - ? -geometry.safeAreaInsets.leading - : 0) - .transition(.move(edge: .trailing)) - .zIndex(1) - } - - SideMenu( - width: geometry.size.width / 5 * 4, - isOpen: self.sideMenuIsOpen, - menuClose: self.openMenu, - safeAreaInsets: geometry.safeAreaInsets - ) - .ignoresSafeArea(.all) - .zIndex(2) - } - } - .overlay { - if isMenuOpen { - Color.white.opacity(0.001) + } label: { + HStack { + Text("See all") + 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") + } + } + } + } label: { + Image(index == 0 ? "filtres" : "more") + } + .padding(.leading) + .onTapGesture { + isMenuOpen = true + } + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 5) + .background(Color.orangeMain500) + } else { + HStack { + Button { + withAnimation { + self.focusedField = false + searchIsActive.toggle() + } + + text = "" + magicSearch.currentFilter = "" + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } label: { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 25, height: 25, alignment: .leading) + } + + if #available(iOS 16.0, *) { + TextEditor(text: Binding( + get: { + return text + }, + set: { value in + var newValue = value + if value.contains("\n") { + newValue = value.replacingOccurrences(of: "\n", with: "") + } + text = newValue + } + )) + .default_text_style_white_700(styleSize: 15) + .padding(.all, 6) + .accentColor(.white) + .scrollContentBackground(.hidden) + .focused($focusedField) + .onAppear { + self.focusedField = true + } + .onChange(of: text) { newValue in + magicSearch.currentFilter = newValue + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } + } else { + TextEditor(text: Binding( + get: { + return text + }, + set: { value in + var newValue = value + if value.contains("\n") { + newValue = value.replacingOccurrences(of: "\n", with: "") + } + text = newValue + } + )) + .default_text_style_white_700(styleSize: 15) + .padding(.all, 6) + .accentColor(.white) + .focused($focusedField) + .onAppear { + self.focusedField = true + } + .onChange(of: text) { newValue in + magicSearch.currentFilter = newValue + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + } + } + + Button { + text = "" + } label: { + Image("x") + .renderingMode(.template) + .resizable() + .foregroundStyle(.white) + .frame(width: 25, height: 25, alignment: .leading) + } + .padding(.leading) + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 5) + .background(Color.orangeMain500) + } + + if self.index == 0 { + ContactsView(contactViewModel: contactViewModel, historyViewModel: historyViewModel) + } else if self.index == 1 { + HistoryView() + } + } + .frame(maxWidth: + (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + ? geometry.size.width/100*40 + : .infinity + ) + .background( + Color.white + .shadow(color: Color.gray200, radius: 4, x: 0, y: 0) + .mask(Rectangle().padding(.horizontal, -8)) + ) + + if orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height { + Spacer() + } + } + + if !(orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) && !searchIsActive { + HStack { + Group { + Spacer() + Button(action: { + self.index = 0 + }, label: { + VStack { + Image("address-book") + .renderingMode(.template) + .resizable() + .foregroundStyle(self.index == 0 ? Color.orangeMain500 : Color.grayMain2c600) + .frame(width: 25, height: 25) + if self.index == 0 { + Text("Contacts") + .default_text_style_700(styleSize: 10) + } else { + Text("Contacts") + .default_text_style(styleSize: 10) + } + } + }) + .padding(.top) + + Spacer() + + Button(action: { + self.index = 1 + contactViewModel.contactTitle = "" + }, label: { + VStack { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(self.index == 1 ? Color.orangeMain500 : Color.grayMain2c600) + .frame(width: 25, height: 25) + if self.index == 1 { + Text("Calls") + .default_text_style_700(styleSize: 10) + } else { + Text("Calls") + .default_text_style(styleSize: 10) + } + } + }) + .padding(.top) + Spacer() + } + } + .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 15) + .background( + Color.white + .shadow(color: Color.gray200, radius: 4, x: 0, y: 0) + .mask(Rectangle().padding(.top, -8)) + ) + } + } + + if !contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty { + HStack(spacing: 0) { + Spacer() + .frame(maxWidth: + (orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + ? (geometry.size.width/100*40) + 75 + : 0 + ) + if self.index == 0 { + ContactFragment(contactViewModel: contactViewModel) + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) + } else if self.index == 1 { + HistoryContactFragment() + .frame(maxWidth: .infinity) + .background(Color.gray100) + .ignoresSafeArea(.keyboard) + } + } + .onAppear { + if !(orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + && searchIsActive { + self.focusedField = false + } + } + .onDisappear { + if !(orientation == .landscapeLeft + || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) + && searchIsActive { + self.focusedField = true + } + } + .padding(.leading, + orientation == .landscapeRight && geometry.safeAreaInsets.bottom > 0 + ? -geometry.safeAreaInsets.leading + : 0) + .transition(.move(edge: .trailing)) + .zIndex(1) + } + + SideMenu( + width: geometry.size.width / 5 * 4, + isOpen: self.sideMenuIsOpen, + menuClose: self.openMenu, + safeAreaInsets: geometry.safeAreaInsets + ) + .ignoresSafeArea(.all) + .zIndex(2) + } + } + .overlay { + if isMenuOpen { + Color.white.opacity(0.001) .ignoresSafeArea() .frame(maxWidth: .infinity, maxHeight: .infinity) .onTapGesture { isMenuOpen = false } - } } - .onRotate { newOrientation in - if (!contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty) && searchIsActive { - self.focusedField = false - } else if searchIsActive { - self.focusedField = true - } - orientation = newOrientation - } - } - } - - func openMenu() { - withAnimation { - self.sideMenuIsOpen.toggle() - } - } + } + .onRotate { newOrientation in + if (!contactViewModel.contactTitle.isEmpty || !historyViewModel.historyTitle.isEmpty) && searchIsActive { + self.focusedField = false + } else if searchIsActive { + self.focusedField = true + } + orientation = newOrientation + } + } + + func openMenu() { + withAnimation { + self.sideMenuIsOpen.toggle() + } + } } #Preview { - ContentView(sharedMainViewModel: SharedMainViewModel(), contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) + ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) }