diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 0c42611c7..c7eb30245 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -159,6 +159,7 @@ D7E6D0552AEBFCCE00A57AAF /* ContactsInnerFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */; }; D7EAACCF2AD6ED8000AA6A8A /* PermissionsFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */; }; D7F4D9CB2B5FD27200CDCD76 /* CallsListFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */; }; + D7F5F6412C359F3B007FCF2F /* SipAddressesPopup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */; }; D7FB55112AD447FD00A5AB15 /* RegisterFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */; }; /* End PBXBuildFile section */ @@ -337,6 +338,7 @@ D7E6D0542AEBFCCE00A57AAF /* ContactsInnerFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactsInnerFragment.swift; sourceTree = ""; }; D7EAACCE2AD6ED8000AA6A8A /* PermissionsFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsFragment.swift; sourceTree = ""; }; D7F4D9CA2B5FD27200CDCD76 /* CallsListFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallsListFragment.swift; sourceTree = ""; }; + D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SipAddressesPopup.swift; sourceTree = ""; }; D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterFragment.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -711,6 +713,7 @@ D7C365072AEFAB7F00FE6142 /* ContactListBottomSheet.swift */, D7C365092AF001C300FE6142 /* EditContactFragment.swift */, D7C48DF52AFCDF4700D938CB /* ContactInnerActionsFragment.swift */, + D7F5F6402C359F3B007FCF2F /* SipAddressesPopup.swift */, ); path = Fragments; sourceTree = ""; @@ -1125,6 +1128,7 @@ 662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */, D72343342ACEFFC3009AA24E /* QRScanner.swift in Sources */, 66C491FB2B24D32600CEA16D /* CoreExtension.swift in Sources */, + D7F5F6412C359F3B007FCF2F /* SipAddressesPopup.swift in Sources */, D72A9A052B9750A1000DC093 /* UIList.swift in Sources */, D726E43D2B19E4FE0083C415 /* StartCallFragment.swift in Sources */, 66E56BCC2BA9A1E0006CE56F /* MeetingsListItemModel.swift in Sources */, diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 39a593c52..cd6131cc6 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -792,6 +792,23 @@ }, "Connexion à la réunion" : { + }, + "contact_dialog_pick_phone_number_or_sip_address_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choose a number or a SIP address" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez un numéro ou adresse SIP" + } + } + } }, "Contacts" : { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift index 67fefca97..0d7139532 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactFragment.swift @@ -29,6 +29,7 @@ struct ContactFragment: View { @Binding var isShowDeletePopup: Bool @Binding var isShowDismissPopup: Bool + @Binding var isShowSipAddressesPopup: Bool @State private var showingSheet = false @State private var showShareSheet = false @@ -45,7 +46,8 @@ struct ContactFragment: View { isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet, showShareSheet: $showShareSheet, - isShowDismissPopup: $isShowDismissPopup + isShowDismissPopup: $isShowDismissPopup, + isShowSipAddressesPopup: $isShowSipAddressesPopup ) .sheet(isPresented: $showingSheet) { ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) @@ -65,7 +67,8 @@ struct ContactFragment: View { isShowDeletePopup: $isShowDeletePopup, showingSheet: $showingSheet, showShareSheet: $showShareSheet, - isShowDismissPopup: $isShowDismissPopup + isShowDismissPopup: $isShowDismissPopup, + isShowSipAddressesPopup: $isShowSipAddressesPopup ) .halfSheet(showSheet: $showingSheet) { ContactListBottomSheet(contactViewModel: contactViewModel, showingSheet: $showingSheet) @@ -84,6 +87,7 @@ struct ContactFragment: View { contactViewModel: ContactViewModel(), editContactViewModel: EditContactViewModel(), isShowDeletePopup: .constant(false), - isShowDismissPopup: .constant(false) + isShowDismissPopup: .constant(false), + isShowSipAddressesPopup: .constant(false) ) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index 0f47f37df..01104363c 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -34,60 +34,48 @@ struct ContactInnerFragment: View { @State private var orientation = UIDevice.current.orientation - @State private var presentingEditContact = false @State var cnContact: CNContact? + @State private var presentingEditContact = false @Binding var isShowDeletePopup: Bool @Binding var showingSheet: Bool @Binding var showShareSheet: Bool @Binding var isShowDismissPopup: Bool + @Binding var isShowSipAddressesPopup: Bool var body: some View { NavigationView { - VStack(spacing: 1) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - - HStack { - if !(orientation == .landscapeLeft || orientation == .landscapeRight - || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 2) - .padding(.leading, -10) - .onTapGesture { - withAnimation { - contactViewModel.indexDisplayedFriend = nil - } - } - } + ZStack { + VStack(spacing: 1) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) - Spacer() - if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count - && !contactAvatarModel.nativeUri.isEmpty { - Button(action: { - editNativeContact() - }, label: { - Image("pencil-simple") + HStack { + if !(orientation == .landscapeLeft || orientation == .landscapeRight + || UIScreen.main.bounds.size.width > UIScreen.main.bounds.size.height) { + Image("caret-left") .renderingMode(.template) .resizable() .foregroundStyle(Color.orangeMain500) .frame(width: 25, height: 25, alignment: .leading) .padding(.all, 10) .padding(.top, 2) - }) - } else { - NavigationLink(destination: EditContactFragment( - editContactViewModel: editContactViewModel, - contactViewModel: contactViewModel, - isShowEditContactFragment: .constant(false), - isShowDismissPopup: $isShowDismissPopup)) { + .padding(.leading, -10) + .onTapGesture { + withAnimation { + contactViewModel.indexDisplayedFriend = nil + } + } + } + + Spacer() + if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count + && !contactAvatarModel.nativeUri.isEmpty { + Button(action: { + editNativeContact() + }, label: { Image("pencil-simple") .renderingMode(.template) .resizable() @@ -95,175 +83,197 @@ struct ContactInnerFragment: View { .frame(width: 25, height: 25, alignment: .leading) .padding(.all, 10) .padding(.top, 2) - } - .simultaneousGesture( - TapGesture().onEnded { - editContactViewModel.selectedEditFriend = contactAvatarModel.friend - editContactViewModel.resetValues() - } - ) - } - } - .frame(maxWidth: .infinity) - .frame(height: 50) - .padding(.horizontal) - .padding(.bottom, 4) - .background(.white) - - ScrollView { - VStack(spacing: 0) { - VStack(spacing: 0) { - VStack(spacing: 0) { - if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count { - Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100) - } else if contactViewModel.indexDisplayedFriend != nil - && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count - && contactAvatarModel != nil { - Image("profil-picture-default") - .resizable() - .frame(width: 100, height: 100) - .clipShape(Circle()) - } - if contactViewModel.indexDisplayedFriend != nil - && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count { - Text(contactAvatarModel.name) - .foregroundStyle(Color.grayMain2c700) - .multilineTextAlignment(.center) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity) - .padding(.top, 10) - - Text(contactAvatarModel.lastPresenceInfo) - .foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online" - ? Color.greenSuccess500 - : Color.orangeWarning600) - .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: { - do { - let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address) - telecomManager.doCallOrJoinConf(address: address) - } catch { - Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") - } - }, label: { - VStack { - HStack(alignment: .center) { - Image("phone") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25) - } - .padding(16) - .background(Color.grayMain2c200) - .cornerRadius(40) - - Text("Appel") - .default_text_style(styleSize: 14) - } - }) - - Spacer() - - Button(action: { - - }, label: { - VStack { - HStack(alignment: .center) { - Image("chat-teardrop-text") - .renderingMode(.template) - .resizable() - //.foregroundStyle(Color.grayMain2c600) - .foregroundStyle(Color.grayMain2c300) - .frame(width: 25, height: 25) - .onTapGesture { - withAnimation { - - } - } - } - .padding(16) - .background(Color.grayMain2c200) - .cornerRadius(40) - - Text("Message") - .default_text_style(styleSize: 14) - } - }) - - Spacer() - - Button(action: { - do { - let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address) - telecomManager.doCallOrJoinConf(address: address, isVideo: true) - } catch { - Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") - } - }, label: { - VStack { - HStack(alignment: .center) { - Image("video-camera") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25) - } - .padding(16) - .background(Color.grayMain2c200) - .cornerRadius(40) - - Text("Video Call") - .default_text_style(styleSize: 14) - } - }) - - Spacer() - } - .padding(.top, 20) - .frame(maxWidth: .infinity) - .background(Color.gray100) - - ContactInnerActionsFragment( - contactViewModel: contactViewModel, + }) + } else { + NavigationLink(destination: EditContactFragment( editContactViewModel: editContactViewModel, - contactAvatarModel: contactAvatarModel, showingSheet: $showingSheet, - showShareSheet: $showShareSheet, - isShowDeletePopup: $isShowDeletePopup, - isShowDismissPopup: $isShowDismissPopup, - actionEditButton: editNativeContact - ) + contactViewModel: contactViewModel, + isShowEditContactFragment: .constant(false), + isShowDismissPopup: $isShowDismissPopup)) { + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 2) + } + .simultaneousGesture( + TapGesture().onEnded { + editContactViewModel.selectedEditFriend = contactAvatarModel.friend + editContactViewModel.resetValues() + } + ) } - .frame(maxWidth: sharedMainViewModel.maxWidth) } .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 4) + .background(.white) + + ScrollView { + VStack(spacing: 0) { + VStack(spacing: 0) { + VStack(spacing: 0) { + if contactViewModel.indexDisplayedFriend != nil && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count { + Avatar(contactAvatarModel: contactAvatarModel, avatarSize: 100) + } else if contactViewModel.indexDisplayedFriend != nil + && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count { + Image("profil-picture-default") + .resizable() + .frame(width: 100, height: 100) + .clipShape(Circle()) + } + if contactViewModel.indexDisplayedFriend != nil + && contactViewModel.indexDisplayedFriend! < contactsManager.lastSearch.count { + Text(contactAvatarModel.name) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity) + .padding(.top, 10) + + Text(contactAvatarModel.lastPresenceInfo) + .foregroundStyle(contactAvatarModel.lastPresenceInfo == "Online" + ? Color.greenSuccess500 + : Color.orangeWarning600) + .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: { + if contactAvatarModel.addresses.count <= 1 { + do { + let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address) + telecomManager.doCallOrJoinConf(address: address, isVideo: false) + } catch { + Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") + } + } else { + isShowSipAddressesPopup = true + } + }, label: { + VStack { + HStack(alignment: .center) { + Image("phone") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Appel") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + + Button(action: { + + }, label: { + VStack { + HStack(alignment: .center) { + Image("chat-teardrop-text") + .renderingMode(.template) + .resizable() + //.foregroundStyle(Color.grayMain2c600) + .foregroundStyle(Color.grayMain2c300) + .frame(width: 25, height: 25) + .onTapGesture { + withAnimation { + + } + } + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Message") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + + Button(action: { + if contactAvatarModel.addresses.count <= 1 { + do { + let address = try Factory.Instance.createAddress(addr: contactAvatarModel.address) + telecomManager.doCallOrJoinConf(address: address, isVideo: true) + } catch { + Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") + } + } else { + isShowSipAddressesPopup = true + } + }, label: { + VStack { + HStack(alignment: .center) { + Image("video-camera") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + } + .padding(16) + .background(Color.grayMain2c200) + .cornerRadius(40) + + Text("Video Call") + .default_text_style(styleSize: 14) + } + }) + + Spacer() + } + .padding(.top, 20) + .frame(maxWidth: .infinity) + .background(Color.gray100) + + ContactInnerActionsFragment( + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, + contactAvatarModel: contactAvatarModel, showingSheet: $showingSheet, + showShareSheet: $showShareSheet, + isShowDeletePopup: $isShowDeletePopup, + isShowDismissPopup: $isShowDismissPopup, + actionEditButton: editNativeContact + ) + } + .frame(maxWidth: sharedMainViewModel.maxWidth) + } + .frame(maxWidth: .infinity) + } + .background(Color.gray100) } - .background(Color.gray100) - } - .background(.white) - .navigationBarHidden(true) - .onRotate { newOrientation in - orientation = newOrientation - } - .fullScreenCover(isPresented: $presentingEditContact) { - NavigationView { - EditContactView(contact: $cnContact) - .navigationBarTitle("Edit Contact") - .navigationBarTitleDisplayMode(.inline) - .edgesIgnoringSafeArea(.vertical) + .background(.white) + .navigationBarHidden(true) + .onRotate { newOrientation in + orientation = newOrientation + } + .fullScreenCover(isPresented: $presentingEditContact) { + NavigationView { + EditContactView(contact: $cnContact) + .navigationBarTitle("Edit Contact") + .navigationBarTitleDisplayMode(.inline) + .edgesIgnoringSafeArea(.vertical) + } } } } @@ -286,6 +296,69 @@ struct ContactInnerFragment: View { print(error) } } + + var sipAddressesPopup: some View { + GeometryReader { geometry in + VStack(alignment: .leading) { + HStack { + Text("contact_dialog_pick_phone_number_or_sip_address_title") + .default_text_style_800(styleSize: 16) + .background(.red) + .padding(.bottom, 2) + + Spacer() + + Image("x") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + .padding(.all, 10) + } + .frame(maxWidth: .infinity) + + ForEach(0..