diff --git a/Linphone/Assets.xcassets/question.imageset/question.svg b/Linphone/Assets.xcassets/question.imageset/question.svg index 6d8013ceb..7825afec1 100644 --- a/Linphone/Assets.xcassets/question.imageset/question.svg +++ b/Linphone/Assets.xcassets/question.imageset/question.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 5f68bafe7..a644fb4a2 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -3814,6 +3814,23 @@ } } }, + "manage_account_dialog_international_prefix_help_message" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Pick your country to allow Linphone to match your contacts." + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Choisissez votre pays pour permettre à Linphone de faire le lien avec vos contacts." + } + } + } + }, "manage_account_edit_picture" : { "localizations" : { "en" : { @@ -3830,6 +3847,23 @@ } } }, + "manage_account_international_prefix" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "International Prefix" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indicatif international" + } + } + } + }, "manage_account_remove_picture" : { "localizations" : { "en" : { diff --git a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift index 1ce788ecb..844597cc4 100644 --- a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift +++ b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift @@ -34,127 +34,183 @@ struct AccountProfileFragment: View { @State private var showPhotoPicker = false @State private var selectedImage: UIImage? @State private var removedImage = false + @State private var isShowPopup = false @FocusState var isDisplayNameFocused: Bool private let avatarSize = 100.0 var body: some View { - VStack(spacing: 1) { - Rectangle() - .foregroundColor(Color.orangeMain500) - .edgesIgnoringSafeArea(.top) - .frame(height: 0) - - HStack { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.orangeMain500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, 4) - .padding(.leading, -10) - .onTapGesture { - accountProfileViewModel.saveChangesWhenLeaving() - withAnimation { - if isShowAccountProfileFragment { - isShowAccountProfileFragment = false + ZStack { + VStack(spacing: 1) { + Rectangle() + .foregroundColor(Color.orangeMain500) + .edgesIgnoringSafeArea(.top) + .frame(height: 0) + + HStack { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, 4) + .padding(.leading, -10) + .onTapGesture { + accountProfileViewModel.saveChangesWhenLeaving() + withAnimation { + if isShowAccountProfileFragment { + isShowAccountProfileFragment = false + } } } - } + + Text("manage_account_title") + .default_text_style_orange_800(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 4) + .lineLimit(1) + + Spacer() + } + .frame(maxWidth: .infinity) + .frame(height: 50) + .padding(.horizontal) + .padding(.bottom, 4) + .background(.white) - Text("manage_account_title") - .default_text_style_orange_800(styleSize: 16) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.top, 4) - .lineLimit(1) - - Spacer() - } - .frame(maxWidth: .infinity) - .frame(height: 50) - .padding(.horizontal) - .padding(.bottom, 4) - .background(.white) - - ScrollView { - VStack(spacing: 0) { + ScrollView { VStack(spacing: 0) { - if #unavailable(iOS 16.0) { - Rectangle() - .foregroundColor(Color.gray100) - .frame(height: 7) - } - - if accountProfileViewModel.avatarModel != nil { - VStack(spacing: 0) { - if accountProfileViewModel.avatarModel != nil - && accountProfileViewModel.photoAvatarModel != nil - && !accountProfileViewModel.photoAvatarModel!.isEmpty - && selectedImage == nil && !removedImage { - - AsyncImage(url: accountProfileViewModel.getImagePath()) { image in - switch image { - case .empty: - ProgressView() - .frame(width: avatarSize, height: avatarSize) - case .success(let image): - image + VStack(spacing: 0) { + if #unavailable(iOS 16.0) { + Rectangle() + .foregroundColor(Color.gray100) + .frame(height: 7) + } + + if accountProfileViewModel.avatarModel != nil { + VStack(spacing: 0) { + if accountProfileViewModel.avatarModel != nil + && accountProfileViewModel.photoAvatarModel != nil + && !accountProfileViewModel.photoAvatarModel!.isEmpty + && selectedImage == nil && !removedImage { + + AsyncImage(url: accountProfileViewModel.getImagePath()) { image in + switch image { + case .empty: + ProgressView() + .frame(width: avatarSize, height: avatarSize) + case .success(let image): + image + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + case .failure: + Image(uiImage: contactsManager.textToImage( + firstName: accountProfileViewModel.avatarModel?.name ?? "", + lastName: "")) .resizable() - .aspectRatio(contentMode: .fill) .frame(width: avatarSize, height: avatarSize) .clipShape(Circle()) - case .failure: - Image(uiImage: contactsManager.textToImage( - firstName: accountProfileViewModel.avatarModel?.name ?? "", - lastName: "")) - .resizable() - .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - @unknown default: - EmptyView() + @unknown default: + EmptyView() + } } - } - } else if selectedImage == nil { - Image(uiImage: contactsManager.textToImage( - firstName: accountProfileViewModel.avatarModel?.name ?? "", - lastName: "")) - .resizable() - .frame(width: avatarSize, height: avatarSize) - .clipShape(Circle()) - } else { - Image(uiImage: selectedImage!) + } else if selectedImage == nil { + Image(uiImage: contactsManager.textToImage( + firstName: accountProfileViewModel.avatarModel?.name ?? "", + lastName: "")) .resizable() - .aspectRatio(contentMode: .fill) .frame(width: avatarSize, height: avatarSize) .clipShape(Circle()) - } - - if accountProfileViewModel.avatarModel != nil - && accountProfileViewModel.photoAvatarModel != nil - && !accountProfileViewModel.photoAvatarModel!.isEmpty - && (accountProfileViewModel.photoAvatarModel!.suffix(11) != "default.png" || selectedImage != nil) - && !removedImage { - HStack { - Spacer() - + } else { + Image(uiImage: selectedImage!) + .resizable() + .aspectRatio(contentMode: .fill) + .frame(width: avatarSize, height: avatarSize) + .clipShape(Circle()) + } + + if accountProfileViewModel.avatarModel != nil + && accountProfileViewModel.photoAvatarModel != nil + && !accountProfileViewModel.photoAvatarModel!.isEmpty + && (accountProfileViewModel.photoAvatarModel!.suffix(11) != "default.png" || selectedImage != nil) + && !removedImage { + HStack { + Spacer() + + Button(action: { + showPhotoPicker = true + }, label: { + HStack { + Image("pencil-simple") + .resizable() + .frame(width: 20, height: 20) + + Text("manage_account_edit_picture") + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + } + }) + .padding(.top, 10) + .padding(.trailing, 10) + .sheet(isPresented: $showPhotoPicker) { + PhotoPicker(filter: .images, limit: 1) { results in + PhotoPicker.convertToUIImageArray(fromResults: results) { imagesOrNil, errorOrNil in + if let error = errorOrNil { + print(error) + } + if let images = imagesOrNil { + if let first = images.first { + selectedImage = first + removedImage = false + saveImage() + } + } + } + } + .edgesIgnoringSafeArea(.all) + } + + Button(action: { + removedImage = true + selectedImage = nil + saveImage() + }, label: { + HStack { + Image("trash-simple") + .resizable() + .frame(width: 20, height: 20) + + Text("manage_account_remove_picture") + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + } + }) + .padding(.top, 10) + + Spacer() + } + } else { Button(action: { showPhotoPicker = true }, label: { HStack { - Image("pencil-simple") + Image("camera") .resizable() .frame(width: 20, height: 20) - Text("manage_account_edit_picture") + Text("manage_account_add_picture") .foregroundStyle(Color.grayMain2c700) .multilineTextAlignment(.center) .default_text_style(styleSize: 14) } }) .padding(.top, 10) - .padding(.trailing, 10) .sheet(isPresented: $showPhotoPicker) { PhotoPicker(filter: .images, limit: 1) { results in PhotoPicker.convertToUIImageArray(fromResults: results) { imagesOrNil, errorOrNil in @@ -165,6 +221,7 @@ struct AccountProfileFragment: View { if let first = images.first { selectedImage = first removedImage = false + showPhotoPicker = false saveImage() } } @@ -172,193 +229,171 @@ struct AccountProfileFragment: View { } .edgesIgnoringSafeArea(.all) } - - Button(action: { - removedImage = true - selectedImage = nil - saveImage() - }, label: { - HStack { - Image("trash-simple") + } + } + .frame(minHeight: 150) + .frame(maxWidth: .infinity) + .padding(.top, 10) + .padding(.bottom, 2) + .background(Color.gray100) + } + + HStack(alignment: .center) { + Text("manage_account_details_title") + .default_text_style_800(styleSize: 18) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer() + + Image(detailIsOpen ? "caret-up" : "caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + .padding(.top, 30) + .padding(.bottom, 10) + .padding(.horizontal, 20) + .background(Color.gray100) + .onTapGesture { + withAnimation { + detailIsOpen.toggle() + } + } + + if detailIsOpen { + VStack(spacing: 0) { + VStack(spacing: 30) { + HStack { + Text(String(localized: "sip_address") + ":") + .default_text_style_600(styleSize: 14) + + Text(accountProfileViewModel.avatarModel!.address) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + + Button(action: { + UIPasteboard.general.setValue( + accountProfileViewModel.avatarModel!.address, + forPasteboardType: UTType.plainText.identifier + ) + + ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" + ToastViewModel.shared.displayToast.toggle() + }, label: { + Image("copy") .resizable() .frame(width: 20, height: 20) - - Text("manage_account_remove_picture") - .foregroundStyle(Color.grayMain2c700) - .multilineTextAlignment(.center) - .default_text_style(styleSize: 14) - } - }) - .padding(.top, 10) - - Spacer() - } - } else { - Button(action: { - showPhotoPicker = true - }, label: { - HStack { - Image("camera") - .resizable() - .frame(width: 20, height: 20) - - Text("manage_account_add_picture") - .foregroundStyle(Color.grayMain2c700) - .multilineTextAlignment(.center) - .default_text_style(styleSize: 14) + }) } - }) - .padding(.top, 10) - .sheet(isPresented: $showPhotoPicker) { - PhotoPicker(filter: .images, limit: 1) { results in - PhotoPicker.convertToUIImageArray(fromResults: results) { imagesOrNil, errorOrNil in - if let error = errorOrNil { - print(error) - } - if let images = imagesOrNil { - if let first = images.first { - selectedImage = first - removedImage = false - showPhotoPicker = false - saveImage() + + VStack(alignment: .leading) { + Text("sip_address_display_name") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + TextField(accountProfileViewModel.displayName, text: $accountProfileViewModel.displayName) + .default_text_style(styleSize: 15) + .frame(height: 25) + .padding(.horizontal, 20) + .padding(.vertical, 15) + .background(.white) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) + ) + .focused($isDisplayNameFocused) + } + + VStack(alignment: .leading) { + HStack { + Text("manage_account_international_prefix") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + .lineLimit(1) + + Button(action: { + isShowPopup = true + }, label: { + Image("question") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 20, height: 20) + }) + .padding(.bottom, -5) + } + Menu { + Picker("", selection: $accountProfileViewModel.dialPlanValueSelected) { + ForEach(registerViewModel.dialPlansLabelList, id: \.self) { dialPlan in + Text(dialPlan).tag(dialPlan) } } - } - } - .edgesIgnoringSafeArea(.all) - } - } - } - .frame(minHeight: 150) - .frame(maxWidth: .infinity) - .padding(.top, 10) - .padding(.bottom, 2) - .background(Color.gray100) - } - - HStack(alignment: .center) { - Text("manage_account_details_title") - .default_text_style_800(styleSize: 18) - .frame(maxWidth: .infinity, alignment: .leading) - - Spacer() - - Image(detailIsOpen ? "caret-up" : "caret-down") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } - .padding(.top, 30) - .padding(.bottom, 10) - .padding(.horizontal, 20) - .background(Color.gray100) - .onTapGesture { - withAnimation { - detailIsOpen.toggle() - } - } - - if detailIsOpen { - VStack(spacing: 0) { - VStack(spacing: 30) { - HStack { - Text(String(localized: "sip_address") + ":") - .default_text_style_600(styleSize: 14) - - Text(accountProfileViewModel.avatarModel!.address) - .foregroundStyle(Color.grayMain2c700) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) - - Button(action: { - UIPasteboard.general.setValue( - accountProfileViewModel.avatarModel!.address, - forPasteboardType: UTType.plainText.identifier - ) - - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() - }, label: { - Image("copy") - .resizable() - .frame(width: 20, height: 20) - }) - } - - VStack(alignment: .leading) { - Text("sip_address_display_name") - .default_text_style_700(styleSize: 15) - .padding(.bottom, -5) - TextField(accountProfileViewModel.displayName, text: $accountProfileViewModel.displayName) - .default_text_style(styleSize: 15) - .frame(height: 25) - .padding(.horizontal, 20) - .padding(.vertical, 15) - .background(.white) - .cornerRadius(60) - .overlay( - RoundedRectangle(cornerRadius: 60) - .inset(by: 0.5) - .stroke(isDisplayNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) - ) - .focused($isDisplayNameFocused) - } - - VStack(alignment: .leading) { - Text("sip_address_display_name") - .default_text_style_700(styleSize: 15) - .padding(.bottom, -5) - - Menu { - Picker("", selection: $accountProfileViewModel.dialPlanValueSelected) { - ForEach(registerViewModel.dialPlansLabelList, id: \.self) { dialPlan in - Text(dialPlan).tag(dialPlan) + .onChange(of: accountProfileViewModel.dialPlanValueSelected) { newValue in + accountProfileViewModel.updateDialPlan(newDialPlan: newValue) } + } label: { + HStack { + Text(accountProfileViewModel.dialPlanValueSelected) + .default_text_style(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + + Image("caret-down") + .resizable() + .frame(width: 20, height: 20) + } + .frame(height: 25) + .padding(.horizontal, 20) + .padding(.vertical, 15) + .background(.white) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(Color.gray200, lineWidth: 1) + ) } - } label: { - HStack { - Text(accountProfileViewModel.dialPlanValueSelected) - .default_text_style(styleSize: 15) - .frame(maxWidth: .infinity, alignment: .leading) - - Image("caret-down") - .resizable() - .frame(width: 20, height: 20) - } - .frame(height: 25) - .padding(.horizontal, 20) - .padding(.vertical, 15) - .background(.white) - .cornerRadius(60) - .overlay( - RoundedRectangle(cornerRadius: 60) - .inset(by: 0.5) - .stroke(Color.gray200, lineWidth: 1) - ) } } + .padding(.vertical, 30) + .padding(.horizontal, 20) } - .padding(.vertical, 30) - .padding(.horizontal, 20) + .background(.white) + .cornerRadius(15) + .padding(.horizontal) + .zIndex(-1) + .transition(.move(edge: .top)) } - .background(.white) - .cornerRadius(15) - .padding(.horizontal) - .zIndex(-1) - .transition(.move(edge: .top)) } + .frame(maxWidth: sharedMainViewModel.maxWidth) } - .frame(maxWidth: sharedMainViewModel.maxWidth) + .frame(maxWidth: .infinity) } - .frame(maxWidth: .infinity) - .padding(.top, 2) + .background(Color.gray100) } .background(Color.gray100) + + if self.isShowPopup { + PopupView(isShowPopup: $isShowPopup, + title: Text("manage_account_international_prefix"), + content: Text("manage_account_dialog_international_prefix_help_message"), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("Ok"), + actionSecondButton: { + self.isShowPopup.toggle() + } + ) + .background(.black.opacity(0.65)) + .onTapGesture { + self.isShowPopup.toggle() + } + } } - .background(Color.gray100) } func saveImage() { diff --git a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift index 4a21f1c47..e02714a9e 100644 --- a/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift +++ b/Linphone/UI/Main/Settings/ViewModel/AccountProfileViewModel.swift @@ -54,11 +54,12 @@ class AccountProfileViewModel: ObservableObject { } } - /* - newParams?.internationalPrefix = self.dialPlanSelected?.countryCallingCode - newParams?.internationalPrefixIsoCountryCode = self.dialPlanSelected?.isoCountryCode - newParams?.useInternationalPrefixForCallsAndChats = true - */ + if self.dialPlanSelected != nil + && (self.dialPlanSelected!.countryCallingCode != newParams?.internationalPrefix || self.dialPlanSelected!.isoCountryCode != newParams?.internationalPrefixIsoCountryCode) { + newParams?.internationalPrefix = self.dialPlanSelected?.countryCallingCode + newParams?.internationalPrefixIsoCountryCode = self.dialPlanSelected?.isoCountryCode + newParams?.useInternationalPrefixForCallsAndChats = true + } core.defaultAccount!.params = newParams } @@ -105,8 +106,11 @@ class AccountProfileViewModel: ObservableObject { } } - func changeDialPlan() { - + func updateDialPlan(newDialPlan: String) { + if let dialPlan = self.dialPlansList.first(where: { newDialPlan.contains($0.isoCountryCode) }) ?? + self.dialPlansList.first(where: { newDialPlan.contains($0.countryCallingCode) }) { + self.dialPlanSelected = dialPlan + } } func saveImage(image: UIImage, name: String, prefix: String) {