diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 83de29f47..5e3cbc295 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -45,6 +45,8 @@ D7A03FC02ACC2E390081A588 /* HistoryView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FBF2ACC2E390081A588 /* HistoryView.swift */; }; D7A03FC62ACC458A0081A588 /* SplashScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A03FC52ACC458A0081A588 /* SplashScreen.swift */; }; D7A2EDD62AC18115005D90FC /* SharedMainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */; }; + D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */; }; + D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */; }; D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */; }; D7D24D132AC1B4E800C6F35B /* NotoSans-Medium.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */; }; D7D24D142AC1B4E800C6F35B /* NotoSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */; }; @@ -106,6 +108,8 @@ D7A03FC52ACC458A0081A588 /* SplashScreen.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashScreen.swift; sourceTree = ""; }; D7A2EDD52AC18115005D90FC /* SharedMainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedMainViewModel.swift; sourceTree = ""; }; D7A2EDDA2AC19EEC005D90FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactInnerFragment.swift; sourceTree = ""; }; + D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactListBottomSheet.swift; sourceTree = ""; }; D7D1698B2AE66FA500109A5C /* MagicSearchSingleton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MagicSearchSingleton.swift; sourceTree = ""; }; D7D24D0D2AC1B4E800C6F35B /* NotoSans-Medium.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Medium.ttf"; sourceTree = ""; }; D7D24D0E2AC1B4E800C6F35B /* NotoSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "NotoSans-Regular.ttf"; sourceTree = ""; }; @@ -325,6 +329,8 @@ D7E6D0482AE933AD00A57AAF /* FavoriteContactsListFragment.swift */, D7E6D0502AEBDBD500A57AAF /* ContactsListBottomSheet.swift */, D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */, + D7B5066C2AEFA9B900CEB4E9 /* ContactInnerFragment.swift */, + D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */, ); path = Fragments; sourceTree = ""; @@ -548,6 +554,7 @@ D719ABCF2ABC779A00B41C10 /* AccountLoginViewModel.swift in Sources */, D78290BB2ADD40B2004AA85C /* ContactViewModel.swift in Sources */, D72992392ADD7F68003AF125 /* HistoryContactFragment.swift in Sources */, + D7B5066D2AEFA9B900CEB4E9 /* ContactInnerFragment.swift in Sources */, D7E6D04D2AEBD77600A57AAF /* CustomBottomSheet.swift in Sources */, D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */, D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */, @@ -575,6 +582,7 @@ D7DA67642ACCB31700E95002 /* ProfileModeFragment.swift in Sources */, D74C9CFC2ACACF370021626A /* WelcomePage3Fragment.swift in Sources */, D719ABCC2ABC769C00B41C10 /* AssistantView.swift in Sources */, + D7C365082AEFAB7F00FE6142 /* ContactListBottomSheet.swift in Sources */, D7E6D04B2AE9347D00A57AAF /* FavoriteContactsListViewModel.swift in Sources */, D74C9CFA2ACACF2D0021626A /* WelcomePage2Fragment.swift in Sources */, D74C9CFF2ACAEC5E0021626A /* PopupView.swift in Sources */, diff --git a/Linphone/Assets.xcassets/bell-ringing_slash.imageset/Contents.json b/Linphone/Assets.xcassets/bell-ringing_slash.imageset/Contents.json new file mode 100644 index 000000000..8ebcc98af --- /dev/null +++ b/Linphone/Assets.xcassets/bell-ringing_slash.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "bell-ringing_slash.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/bell-ringing_slash.imageset/bell-ringing_slash.svg b/Linphone/Assets.xcassets/bell-ringing_slash.imageset/bell-ringing_slash.svg new file mode 100644 index 000000000..16e263982 --- /dev/null +++ b/Linphone/Assets.xcassets/bell-ringing_slash.imageset/bell-ringing_slash.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/Assets.xcassets/chat-teardrop-text.imageset/Contents.json b/Linphone/Assets.xcassets/chat-teardrop-text.imageset/Contents.json new file mode 100644 index 000000000..0a273d5fb --- /dev/null +++ b/Linphone/Assets.xcassets/chat-teardrop-text.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "chat-teardrop-text.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/chat-teardrop-text.imageset/chat-teardrop-text.svg b/Linphone/Assets.xcassets/chat-teardrop-text.imageset/chat-teardrop-text.svg new file mode 100644 index 000000000..8f7d61055 --- /dev/null +++ b/Linphone/Assets.xcassets/chat-teardrop-text.imageset/chat-teardrop-text.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Assets.xcassets/copy.imageset/Contents.json b/Linphone/Assets.xcassets/copy.imageset/Contents.json new file mode 100644 index 000000000..be85778ce --- /dev/null +++ b/Linphone/Assets.xcassets/copy.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "copy.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/copy.imageset/copy.svg b/Linphone/Assets.xcassets/copy.imageset/copy.svg new file mode 100644 index 000000000..1b1334c76 --- /dev/null +++ b/Linphone/Assets.xcassets/copy.imageset/copy.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Assets.xcassets/empty.imageset/Contents.json b/Linphone/Assets.xcassets/empty.imageset/Contents.json new file mode 100644 index 000000000..f14fd78f2 --- /dev/null +++ b/Linphone/Assets.xcassets/empty.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "empty.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/empty.imageset/empty.svg b/Linphone/Assets.xcassets/empty.imageset/empty.svg new file mode 100644 index 000000000..3de6a876d --- /dev/null +++ b/Linphone/Assets.xcassets/empty.imageset/empty.svg @@ -0,0 +1,3 @@ + + + diff --git a/Linphone/Assets.xcassets/envelope-simple-open.imageset/Contents.json b/Linphone/Assets.xcassets/envelope-simple-open.imageset/Contents.json new file mode 100644 index 000000000..0524ea689 --- /dev/null +++ b/Linphone/Assets.xcassets/envelope-simple-open.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "envelope-simple-open.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/envelope-simple-open.imageset/envelope-simple-open.svg b/Linphone/Assets.xcassets/envelope-simple-open.imageset/envelope-simple-open.svg new file mode 100644 index 000000000..42d30e72f --- /dev/null +++ b/Linphone/Assets.xcassets/envelope-simple-open.imageset/envelope-simple-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Assets.xcassets/pencil-simple.imageset/Contents.json b/Linphone/Assets.xcassets/pencil-simple.imageset/Contents.json new file mode 100644 index 000000000..fb2e28212 --- /dev/null +++ b/Linphone/Assets.xcassets/pencil-simple.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "pencil-simple.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/pencil-simple.imageset/pencil-simple.svg b/Linphone/Assets.xcassets/pencil-simple.imageset/pencil-simple.svg new file mode 100644 index 000000000..ceb292bbf --- /dev/null +++ b/Linphone/Assets.xcassets/pencil-simple.imageset/pencil-simple.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index a7a845569..1e31e8716 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -179,7 +179,8 @@ final class ContactsManager: ObservableObject { 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) + let labelDrop = String(phone.numLabel.dropFirst(4).dropLast(4)) + let phoneNumber = try Factory.Instance.createFriendPhoneNumber(phoneNumber: phone.num, label: labelDrop) friend.addPhoneNumberWithLabel(phoneNumber: phoneNumber) friendPhoneNumbers.append(phone) } @@ -190,6 +191,8 @@ final class ContactsManager: ObservableObject { let contactImage = result.dropFirst(8) friend.photo = "file:/" + contactImage + + friend.organization = contact.organizationName friend.done() diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index ab4385540..79915ed4f 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -27,6 +27,9 @@ }, "**Camera** : Pour capturer votre vidéo lors des appels vidéo et conférence." : { + }, + "**Company :** %@" : { + }, "**Contacts** : Pour vous afficher vos contacts et retrouver qui utilise Linphone." : { @@ -98,6 +101,9 @@ }, "All contacts" : { + }, + "Appel" : { + }, "assistant_account_login" : { "extractionState" : "manual", @@ -115,9 +121,21 @@ } } } + }, + "Block" : { + + }, + "Block the address" : { + + }, + "Block the number" : { + }, "Calls" : { + }, + "Cancel" : { + }, "Ce mode vous permet d’être interopérable avec d’autres services SIP.\nVos communications seront chiffrées de point à point. " : { @@ -136,6 +154,12 @@ }, "Continue" : { + }, + "Copy address" : { + + }, + "Copy number" : { + }, "D'accord" : { @@ -148,6 +172,12 @@ }, "Delete" : { + }, + "Delete %@?" : { + + }, + "Delete this contact" : { + }, "Demande d’autorisations" : { @@ -160,12 +190,21 @@ }, "Domain" : { + }, + "Edit" : { + }, "En continuant, vous acceptez ces conditions, " : { + }, + "En ligne" : { + }, "Error" : { + }, + "Error Name" : { + }, "Favourites" : { @@ -178,6 +217,9 @@ }, "I understand" : { + }, + "Information" : { + }, "Interoperable" : { @@ -190,6 +232,9 @@ }, "Invalide URI" : { + }, + "Invitation" : { + }, "Linphone" : { @@ -199,6 +244,12 @@ }, "Logout" : { + }, + "Message" : { + + }, + "Mute" : { + }, "My Profile" : { @@ -214,12 +265,18 @@ }, "Not account yet?" : { + }, + "Ok" : { + }, "Open source" : { }, "Opération en cours..." : { + }, + "Other actions" : { + }, "password" : { "extractionState" : "manual", @@ -240,6 +297,9 @@ }, "Personnalize your profil mode" : { + }, + "Phone (%@) :" : { + }, "Plus tard" : { @@ -273,6 +333,9 @@ }, "Share" : { + }, + "SIP address :" : { + }, "sip.linphone.org" : { @@ -288,6 +351,9 @@ }, "The user name or password is incorrects" : { + }, + "This contact will be deleted definitively." : { + }, "TLS" : { @@ -329,6 +395,9 @@ } } } + }, + "Video Call" : { + }, "Vos communications sont en sécurité grâce aux **Chiffrement de bout en bout**." : { diff --git a/Linphone/UI/Assistant/Fragments/PermissionsFragment.swift b/Linphone/UI/Assistant/Fragments/PermissionsFragment.swift index 979428da3..efb732cd4 100644 --- a/Linphone/UI/Assistant/Fragments/PermissionsFragment.swift +++ b/Linphone/UI/Assistant/Fragments/PermissionsFragment.swift @@ -79,11 +79,6 @@ struct PermissionsFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) @@ -102,11 +97,6 @@ struct PermissionsFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) @@ -125,11 +115,6 @@ struct PermissionsFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) @@ -148,11 +133,6 @@ struct PermissionsFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) diff --git a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift index 4ce290e27..85d3f32a1 100644 --- a/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift +++ b/Linphone/UI/Assistant/Fragments/ThirdPartySipAccountWarningFragment.swift @@ -77,11 +77,6 @@ struct ThirdPartySipAccountWarningFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) @@ -94,11 +89,6 @@ struct ThirdPartySipAccountWarningFragment: View { .resizable() .foregroundStyle(Color.grayMain2c500) .frame(width: 20, height: 20, alignment: .leading) - .onTapGesture { - withAnimation { - dismiss() - } - } } .padding(16) .background(Color.grayMain2c200) diff --git a/Linphone/UI/Main/Contacts/ContactsView.swift b/Linphone/UI/Main/Contacts/ContactsView.swift index 74098595a..2b9bde65a 100644 --- a/Linphone/UI/Main/Contacts/ContactsView.swift +++ b/Linphone/UI/Main/Contacts/ContactsView.swift @@ -23,12 +23,14 @@ struct ContactsView: View { @ObservedObject var contactViewModel: ContactViewModel @ObservedObject var historyViewModel: HistoryViewModel - + + @Binding var isShowDeletePopup: Bool + var body: some View { NavigationView { ZStack(alignment: .bottomTrailing) { - ContactsFragment(contactViewModel: contactViewModel) + ContactsFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup) Button { // Action @@ -48,5 +50,5 @@ struct ContactsView: View { } #Preview { - ContactsView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) + ContactsView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel(), isShowDeletePopup: .constant(false)) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift index c3684a141..042169629 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift @@ -20,50 +20,30 @@ import SwiftUI struct ContactFragment: View { - - @ObservedObject var contactViewModel: ContactViewModel - - @State private var orientation = UIDevice.current.orientation - - var body: some View { - VStack(alignment: .leading) { - - if !(orientation == .landscapeLeft - || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - HStack { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.top, 20) - .onTapGesture { - withAnimation { - contactViewModel.contactTitle = "" - } - } - - Spacer() - } - .padding(.leading) - } - - Spacer() - - Text("Contact Fragment " + contactViewModel.contactTitle) - .frame(maxWidth: .infinity) - - Spacer() - } - .navigationBarHidden(true) - .onRotate { newOrientation in - orientation = newOrientation - } - - } + + @ObservedObject var contactViewModel: ContactViewModel + + @Binding var isShowDeletePopup: Bool + + @State private var showingSheet = false + + var body: some View { + if #available(iOS 16.0, *) { + ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet) + .sheet(isPresented: $showingSheet) { + ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) + .presentationDetents([.fraction(0.2)]) + } + } else { + ContactInnerFragment(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet) + .halfSheet(showSheet: $showingSheet) { + ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) + } onDismiss: {} + } + + } } #Preview { - ContactFragment(contactViewModel: ContactViewModel()) + ContactFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false)) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift new file mode 100644 index 000000000..fab0b5d73 --- /dev/null +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -0,0 +1,564 @@ +/* + * 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 ContactInnerFragment: View { + + @ObservedObject var contactViewModel: ContactViewModel + + @State private var orientation = UIDevice.current.orientation + + @State private var informationIsOpen = true + + @Binding var isShowDeletePopup: Bool + + @Binding var showingSheet: Bool + + var body: some View { + 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(.top, 2) + .onTapGesture { + withAnimation { + contactViewModel.displayedFriend = nil + } + } + } + + Spacer() + + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.top, 2) + .onTapGesture { + withAnimation { + + } + } + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 4) + .background(.white) + + ScrollView { + VStack(spacing: 0) { + VStack(spacing: 0) { + if contactViewModel.displayedFriend != nil + && contactViewModel.displayedFriend!.photo != nil + && !contactViewModel.displayedFriend!.photo!.isEmpty { + AsyncImage(url: URL(string: contactViewModel.displayedFriend!.photo!)) { image in + switch image { + case .empty: + ProgressView() + .frame(width: 100, height: 100) + case .success(let image): + image + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + case .failure: + Image("profil-picture-default") + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + @unknown default: + EmptyView() + } + } + } else if contactViewModel.displayedFriend != nil { + Image("profil-picture-default") + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + if contactViewModel.displayedFriend != nil && contactViewModel.displayedFriend?.name != nil { + Text((contactViewModel.displayedFriend?.name)!) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity) + .padding(.top, 10) + + Text("En ligne") + .foregroundStyle(Color.greenSuccess500) + .multilineTextAlignment(.center) + .default_text_style_300(styleSize: 12) + .frame(maxWidth: .infinity) + } + + } + .frame(minHeight: 150) + .frame(maxWidth: .infinity) + .padding(.top, 10) + .background(Color.gray100) + + HStack { + Spacer() + + Button(action: { + + }, label: { + VStack { + HStack(alignment: .center) { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .onTapGesture { + withAnimation { + + } + } + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Appel") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + + Button(action: { + + }, label: { + VStack { + HStack(alignment: .center) { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .onTapGesture { + withAnimation { + + } + } + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Message") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + + Button(action: { + + }, label: { + VStack { + HStack(alignment: .center) { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .onTapGesture { + withAnimation { + + } + } + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Video Call") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + } + .padding(.top, 20) + .frame(maxWidth: .infinity) + .background(Color.gray100) + + HStack(alignment: .center) { + Text("Information") + .default_text_style_800(styleSize: 16) + + Spacer() + + Image(informationIsOpen ? "caret-up" : "caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + } + .padding(.top, 30) + .padding(.bottom, 10) + .padding(.horizontal, 16) + .background(Color.gray100) + .onTapGesture { + withAnimation { + informationIsOpen.toggle() + } + } + + if informationIsOpen { + VStack(spacing: 0) { + if contactViewModel.displayedFriend != nil { + ForEach(0... + */ + +import SwiftUI +import UniformTypeIdentifiers + +struct ContactListBottomSheet: View { + + @ObservedObject var magicSearch = MagicSearchSingleton.shared + + @ObservedObject var contactViewModel: ContactViewModel + + @State private var orientation = UIDevice.current.orientation + + @Environment(\.dismiss) var dismiss + + @Binding var showingSheet: Bool + + var body: some View { + VStack(alignment: .leading) { + if 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 { + UIPasteboard.general.setValue( + contactViewModel.stringToCopy.prefix(4) == "sip:" + ? contactViewModel.stringToCopy.dropFirst(4) + : contactViewModel.stringToCopy, + forPasteboardType: UTType.plainText.identifier) + + if #available(iOS 16.0, *) { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } label: { + HStack { + Image("copy") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text(contactViewModel.stringToCopy.prefix(4) == "sip:" + ? "Copy address" : "Copy number") + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + VStack { + Divider() + } + .frame(maxWidth: .infinity) + + if contactViewModel.stringToCopy.prefix(4) != "sip:" { + Button { + if #available(iOS 16.0, *) { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } label: { + HStack { + Image("envelope-simple-open") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text("Invitation") + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + VStack { + Divider() + } + .frame(maxWidth: .infinity) + } + + Button { + if #available(iOS 16.0, *) { + showingSheet.toggle() + } else { + showingSheet.toggle() + dismiss() + } + } label: { + HStack { + Image("empty") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + Text(contactViewModel.stringToCopy.prefix(4) == "sip:" + ? "Block the address" : "Block the number") + .default_text_style(styleSize: 16) + Spacer() + } + .frame(maxHeight: .infinity) + } + .padding(.horizontal, 30) + .background(Color.gray100) + + } + .onRotate { newOrientation in + orientation = newOrientation + } + .background(Color.gray100) + .frame(maxWidth: .infinity) + } +} + +#Preview { + ContactListBottomSheet(contactViewModel: ContactViewModel(), showingSheet: .constant(false)) +} diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift index 5c0b65bb0..9196f23cc 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsFragment.swift @@ -22,26 +22,29 @@ import SwiftUI struct ContactsFragment: View { @ObservedObject var contactViewModel: ContactViewModel + + @Binding var isShowDeletePopup: Bool @State private var showingSheet = false var body: some View { - if #available(iOS 16.0, *) { - ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet) - .sheet(isPresented: $showingSheet) { - ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) - .presentationDetents([.fraction(0.2)]) - } - } else { - ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet) - .halfSheet(showSheet: $showingSheet) { - ContactsListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) - } onDismiss: {} - } - + ZStack { + if #available(iOS 16.0, *) { + ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet) + .sheet(isPresented: $showingSheet) { + ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet) + .presentationDetents([.fraction(0.2)]) + } + } else { + ContactsInnerFragment(contactViewModel: contactViewModel, showingSheet: $showingSheet) + .halfSheet(showSheet: $showingSheet) { + ContactsListBottomSheet(contactViewModel: contactViewModel, isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet) + } onDismiss: {} + } + } } } #Preview { - ContactsFragment(contactViewModel: ContactViewModel()) + ContactsFragment(contactViewModel: ContactViewModel(), isShowDeletePopup: .constant(false)) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift index a04b3bc68..abfd51b4d 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift @@ -53,9 +53,12 @@ struct ContactsInnerFragment: View { } if isFavoriteOpen { - FavoriteContactsListFragment(contactViewModel: contactViewModel, favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: $showingSheet) - .zIndex(-1) - .transition(.move(edge: .top)) + FavoriteContactsListFragment( + contactViewModel: contactViewModel, + favoriteContactsListViewModel: FavoriteContactsListViewModel(), + showingSheet: $showingSheet) + .zIndex(-1) + .transition(.move(edge: .top)) } HStack(alignment: .center) { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift index a55a0b96b..cd0dbd455 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListBottomSheet.swift @@ -29,6 +29,8 @@ struct ContactsListBottomSheet: View { @State private var orientation = UIDevice.current.orientation @Environment(\.dismiss) var dismiss + + @Binding var isShowDeletePopup: Bool @Binding var showingSheet: Bool @@ -118,9 +120,8 @@ struct ContactsListBottomSheet: View { Button { if contactViewModel.selectedFriend != nil { - contactViewModel.selectedFriend!.remove() + isShowDeletePopup.toggle() } - self.magicSearch.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) if #available(iOS 16.0, *) { showingSheet.toggle() diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift index 527488c95..600d7f179 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsListFragment.swift @@ -36,9 +36,21 @@ struct ContactsListFragment: View { Button { } label: { HStack { - if index == 0 || magicSearch.lastSearch[index].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first != - magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding(options: .diacriticInsensitive, locale: .current).first { - Text(String((magicSearch.lastSearch[index].friend?.name!.uppercased().folding(options: .diacriticInsensitive, locale: .current).first)!)) + if index == 0 + || magicSearch.lastSearch[index].friend?.name!.lowercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first + != magicSearch.lastSearch[index-1].friend?.name!.lowercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first { + Text( + String( + (magicSearch.lastSearch[index].friend?.name!.uppercased().folding( + options: .diacriticInsensitive, + locale: .current + ).first)!)) .contact_text_style_500(styleSize: 20) .frame(width: 18) .padding(.leading, -5) @@ -79,7 +91,7 @@ struct ContactsListFragment: View { } Text((magicSearch.lastSearch[index].friend?.name)!) .default_text_style(styleSize: 16) - .frame( maxWidth: .infinity, alignment: .leading) + .frame(maxWidth: .infinity, alignment: .leading) .foregroundStyle(Color.orangeMain500) } } @@ -94,7 +106,7 @@ struct ContactsListFragment: View { TapGesture() .onEnded { _ in withAnimation { - contactViewModel.contactTitle = (magicSearch.lastSearch[index].friend?.name)! + contactViewModel.displayedFriend = magicSearch.lastSearch[index].friend } } ) diff --git a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift index b917a2a38..dc4434c64 100644 --- a/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/FavoriteContactsListFragment.swift @@ -38,7 +38,9 @@ struct FavoriteContactsListFragment: View { VStack { if magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo != nil && !magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!.isEmpty { - AsyncImage(url: URL(string: magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!)) { image in + AsyncImage( + url: URL(string: magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend!.photo!) + ) { image in switch image { case .empty: ProgressView() @@ -79,7 +81,9 @@ struct FavoriteContactsListFragment: View { TapGesture() .onEnded { _ in withAnimation { - contactViewModel.contactTitle = (magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend?.name)! + contactViewModel.displayedFriend = ( + magicSearch.lastSearch.filter({ $0.friend?.starred == true })[index].friend + )! } } ) @@ -92,5 +96,8 @@ struct FavoriteContactsListFragment: View { } #Preview { - FavoriteContactsListFragment(contactViewModel: ContactViewModel(), favoriteContactsListViewModel: FavoriteContactsListViewModel(), showingSheet: .constant(false)) + FavoriteContactsListFragment( + contactViewModel: ContactViewModel(), + favoriteContactsListViewModel: FavoriteContactsListViewModel(), + showingSheet: .constant(false)) } diff --git a/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift b/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift index 4074721ba..7cca97313 100644 --- a/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift +++ b/Linphone/UI/Main/Contacts/ViewModel/ContactViewModel.swift @@ -21,7 +21,8 @@ import linphonesw class ContactViewModel: ObservableObject { - @Published var contactTitle: String = "" + @Published var displayedFriend: Friend? + var stringToCopy: String = "" var selectedFriend: Friend? diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index a6fc69720..386304205 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -21,413 +21,451 @@ import SwiftUI import linphonesw struct ContentView: View { - - var contactManager = ContactsManager.shared - var magicSearch = MagicSearchSingleton.shared - - @ObservedObject var contactViewModel: ContactViewModel - @ObservedObject var historyViewModel: HistoryViewModel - @ObservedObject private var coreContext = CoreContext.shared - - @State var index = 0 - @State private var orientation = UIDevice.current.orientation - @State var sideMenuIsOpen: Bool = false - - @State private var searchIsActive = false - @State private var text = "" - @FocusState private var focusedField: Bool - @State var isMenuOpen: Bool = false - - 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) - } - } - }) - - 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") - } - } - } - - 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() - } - } + + var contactManager = ContactsManager.shared + var magicSearch = MagicSearchSingleton.shared + + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var historyViewModel: HistoryViewModel + @ObservedObject private var coreContext = CoreContext.shared + + @State var index = 0 + @State private var orientation = UIDevice.current.orientation + @State var sideMenuIsOpen: Bool = false + + @State private var searchIsActive = false + @State private var text = "" + @FocusState private var focusedField: Bool + @State var isMenuOpen: Bool = false + @State var isShowDeletePopup = false + + 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) + } + } + }) + + Spacer() + + Button(action: { + self.index = 1 + contactViewModel.displayedFriend = nil + }, 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") + } + } + } + + 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, isShowDeletePopup: $isShowDeletePopup) + } 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.displayedFriend = nil + }, 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.displayedFriend != nil || !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, isShowDeletePopup: $isShowDeletePopup) + .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) + + if isShowDeletePopup { + PopupView(sharedMainViewModel: SharedMainViewModel(), isShowPopup: $isShowDeletePopup, + title: Text( + contactViewModel.selectedFriend != nil + ? "Delete \(contactViewModel.selectedFriend!.name!)?" + : (contactViewModel.displayedFriend != nil + ? "Delete \(contactViewModel.displayedFriend!.name!)?" + : "Error Name")), + content: Text("This contact will be deleted definitively."), + titleFirstButton: Text("Cancel"), + actionFirstButton: {self.isShowDeletePopup.toggle()}, + titleSecondButton: Text("Ok"), + actionSecondButton: { + if contactViewModel.selectedFriend != nil { + contactViewModel.selectedFriend!.remove() + if contactViewModel.displayedFriend != nil && contactViewModel.selectedFriend!.name == contactViewModel.displayedFriend!.name { + withAnimation { + contactViewModel.displayedFriend = nil + } + } + } else if contactViewModel.displayedFriend != nil { + contactViewModel.displayedFriend!.remove() + withAnimation { + contactViewModel.displayedFriend = nil + } + } + magicSearch.searchForContacts( + sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) + self.isShowDeletePopup.toggle() + }) + .background(.black.opacity(0.65)) + .zIndex(3) + .onTapGesture { + self.isShowDeletePopup.toggle() + } + } + } + } + .overlay { + if isMenuOpen { + Color.white.opacity(0.001) + .ignoresSafeArea() + .frame(maxWidth: .infinity, maxHeight: .infinity) + .onTapGesture { + isMenuOpen = false + } + } + } + .onRotate { newOrientation in + if (contactViewModel.displayedFriend != nil || !historyViewModel.historyTitle.isEmpty) && searchIsActive { + self.focusedField = false + } else if searchIsActive { + self.focusedField = true + } + orientation = newOrientation + } + } + + func openMenu() { + withAnimation { + self.sideMenuIsOpen.toggle() + } + } } #Preview { - ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) + ContentView(contactViewModel: ContactViewModel(), historyViewModel: HistoryViewModel()) } diff --git a/Linphone/UI/Main/Fragments/CustomBottomSheet.swift b/Linphone/UI/Main/Fragments/CustomBottomSheet.swift index cff7bb1d3..18683b01f 100644 --- a/Linphone/UI/Main/Fragments/CustomBottomSheet.swift +++ b/Linphone/UI/Main/Fragments/CustomBottomSheet.swift @@ -20,7 +20,6 @@ import SwiftUI extension View { - //binding show bariable... func halfSheet( showSheet: Binding, @ViewBuilder content: @escaping () -> Content, @@ -33,7 +32,6 @@ extension View { } } -// UIKit integration struct HalfSheetHelper: UIViewControllerRepresentable { var sheetView: Content @@ -58,7 +56,6 @@ struct HalfSheetHelper: UIViewControllerRepresentable { } } - //on dismiss... final class Coordinator: NSObject, UISheetPresentationControllerDelegate { var parent: HalfSheetHelper @@ -73,7 +70,6 @@ struct HalfSheetHelper: UIViewControllerRepresentable { } } -// Custom UIHostingController for halfSheet... final class CustomHostingController: UIHostingController { override func viewDidLoad() { view.backgroundColor = .clear @@ -82,16 +78,11 @@ final class CustomHostingController: UIHostingController .medium() ] - //MARK: - sheet grabber visbility - presentationController.prefersGrabberVisible = false // i wanted to design my own grabber hehehe + presentationController.prefersGrabberVisible = false - // this allows you to scroll even during medium detent presentationController.prefersScrollingExpandsWhenScrolledToEdge = false - //MARK: - sheet corner radius presentationController.preferredCornerRadius = 30 - - // for more sheet customisation check out this great article https://sarunw.com/posts/bottom-sheet-in-ios-15-with-uisheetpresentationcontroller/#scrolling } } }