diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index 137de0f75..2fc9d6d40 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -57,6 +57,8 @@ D70A26F22B7F5D95006CC8FC /* ConversationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D70A26F12B7F5D95006CC8FC /* ConversationFragment.swift */; }; D70C93DE2AC2D0F60063CA3B /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */; }; D714035B2BE11E00004BD8CA /* CallMediaEncryptionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */; }; + D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */; }; + D714DE622C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */; }; D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071D2AC5922E0037746F /* ColorExtension.swift */; }; D71707202AC5989C0037746F /* TextExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = D717071F2AC5989C0037746F /* TextExtension.swift */; }; D7173EBE2B7A5C0A00BCC481 /* LinphoneUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */; }; @@ -229,6 +231,8 @@ D70A26F12B7F5D95006CC8FC /* ConversationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationFragment.swift; sourceTree = ""; }; D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; D714035A2BE11E00004BD8CA /* CallMediaEncryptionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallMediaEncryptionModel.swift; sourceTree = ""; }; + D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterViewModel.swift; sourceTree = ""; }; + D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegisterCodeConfirmationFragment.swift; sourceTree = ""; }; D717071D2AC5922E0037746F /* ColorExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorExtension.swift; sourceTree = ""; }; D717071F2AC5989C0037746F /* TextExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextExtension.swift; sourceTree = ""; }; D7173EBD2B7A5C0A00BCC481 /* LinphoneUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinphoneUtils.swift; sourceTree = ""; }; @@ -578,6 +582,7 @@ D719ABCE2ABC779A00B41C10 /* AccountLoginViewModel.swift */, D72343332ACEFFC3009AA24E /* QRScanner.swift */, D72343312ACEFF58009AA24E /* QRScannerController.swift */, + D714DE5F2C1B3B34006C1F1D /* RegisterViewModel.swift */, ); path = Viewmodel; sourceTree = ""; @@ -837,6 +842,7 @@ D7FB55102AD447FD00A5AB15 /* RegisterFragment.swift */, D748BF2B2ACD82D2004844EB /* ThirdPartySipAccountLoginFragment.swift */, D748BF2D2ACD82E7004844EB /* ThirdPartySipAccountWarningFragment.swift */, + D714DE612C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift */, ); path = Fragments; sourceTree = ""; @@ -1123,9 +1129,11 @@ D726E43F2B19E56F0083C415 /* StartCallViewModel.swift in Sources */, D70A26EE2B7CF60B006CC8FC /* ConversationsListBottomSheet.swift in Sources */, D7D1698C2AE66FA500109A5C /* MagicSearchSingleton.swift in Sources */, + D714DE602C1B3B34006C1F1D /* RegisterViewModel.swift in Sources */, D74DA0122C047F0700A8561D /* HistoryModel.swift in Sources */, D72250692ADFBF2D008FB426 /* SideMenu.swift in Sources */, C6DC4E3F2C19C289009096FD /* SideMenuEntry.swift in Sources */, + D714DE622C1C4636006C1F1D /* RegisterCodeConfirmationFragment.swift in Sources */, D7CEE0352B7A210300FD79B7 /* ConversationsView.swift in Sources */, D717071E2AC5922E0037746F /* ColorExtension.swift in Sources */, D71968922B86369D00DF4459 /* ChatBubbleView.swift in Sources */, @@ -1171,6 +1179,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", + "USE_CRASHLYTICS=1", ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = msgNotificationService/Info.plist; @@ -1184,7 +1193,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1210,7 +1219,10 @@ DEVELOPMENT_TEAM = Z2V957B3D6; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "USE_CRASHLYTICS=1", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = msgNotificationService/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = msgNotificationService; @@ -1223,7 +1235,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MARKETING_VERSION = 1.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone.msgNotificationService; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1370,6 +1382,7 @@ GCC_PREPROCESSOR_DEFINITIONS = ( "$(inherited)", "DEBUG=1", + "USE_CRASHLYTICS=1", ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Linphone/Info.plist; @@ -1396,7 +1409,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 6.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; @@ -1422,7 +1435,10 @@ DEVELOPMENT_TEAM = Z2V957B3D6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; - GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)"; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "USE_CRASHLYTICS=1", + ); GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Linphone/Info.plist; INFOPLIST_KEY_NSCameraUsageDescription = "Camera usage is required for video VOIP calls"; @@ -1448,7 +1464,7 @@ "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 13.3; MARKETING_VERSION = 6.0; - OTHER_SWIFT_FLAGS = "$(inherited)"; + OTHER_SWIFT_FLAGS = "$(inherited) -DUSE_CRASHLYTICS"; PRODUCT_BUNDLE_IDENTIFIER = org.linphone.phone; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; diff --git a/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/Contents.json b/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/Contents.json new file mode 100644 index 000000000..eed2e9663 --- /dev/null +++ b/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "confirm_sms_code_illu.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/confirm_sms_code_illu.png b/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/confirm_sms_code_illu.png new file mode 100644 index 000000000..e25974c02 Binary files /dev/null and b/Linphone/Assets.xcassets/confirm_sms_code_illu.imageset/confirm_sms_code_illu.png differ diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 77a3aaae4..827ba45ed 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -207,6 +207,54 @@ }, "Appel chiffré de bout en bout" : { + }, + "assistant_account_create" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créer" + } + } + } + }, + "assistant_account_creation_sms_confirmation_explanation" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "We have sent a verification code on your phone number %@.

Please enter the verification code below:" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "On vous a envoyé un code de vérification par SMS au numéro %@.

Merci de le saisir ci-dessous :" + } + } + } + }, + "assistant_account_creation_wrong_phone_number" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Wrong number?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Mauvais numéro ?" + } + } + } }, "assistant_account_login" : { "extractionState" : "manual", @@ -225,6 +273,87 @@ } } }, + "assistant_account_register" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Register" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "S’inscrire" + } + } + } + }, + "assistant_already_have_an_account" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Already have an account?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Vous avez déjà un compte ?" + } + } + } + }, + "assistant_create_account_using_email_on_our_web_platform" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create an account with your email on:" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Créez un compte avec votre email ici :" + } + } + } + }, + "assistant_dialog_confirm_phone_number_message" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Are you sure you want to use %@ phone number?" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Êtes-vous sûr de vouloir utiliser le numéro de téléphone %@ ?" + } + } + } + }, + "assistant_dialog_confirm_phone_number_title" : { + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirm phone number" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Confirmez votre numéro de téléphone" + } + } + } + }, "Attended transfer" : { }, @@ -265,7 +394,20 @@ }, "Cancel" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cancel" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Annuler" + } + } + } }, "Ce mode vous permet d’être interopérable avec d’autres services SIP.\nVos communications seront chiffrées de point à point. " : { @@ -304,7 +446,20 @@ }, "Continue" : { - + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continue" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Continuer" + } + } + } }, "Conversations" : { @@ -642,6 +797,9 @@ }, "Log out" : { + }, + "Login" : { + }, "Logout" : { @@ -807,7 +965,14 @@ } }, "Register" : { - + "localizations" : { + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "S’inscrire" + } + } + } }, "Rejoindre" : { @@ -910,6 +1075,9 @@ }, "Subject" : { + }, + "subscribe.linphone.org" : { + }, "Suggestions" : { diff --git a/Linphone/Ressources/assistant_linphone_default_values b/Linphone/Ressources/assistant_linphone_default_values index c1e26073c..54453d542 100644 --- a/Linphone/Ressources/assistant_linphone_default_values +++ b/Linphone/Ressources/assistant_linphone_default_values @@ -29,7 +29,7 @@
zrtp - 1 + 1
1 diff --git a/Linphone/Ressources/assistant_third_party_default_values b/Linphone/Ressources/assistant_third_party_default_values index 78927cf4e..bd9c9b79f 100644 --- a/Linphone/Ressources/assistant_third_party_default_values +++ b/Linphone/Ressources/assistant_third_party_default_values @@ -22,4 +22,12 @@ 0
+
+ stun.linphone.org + stun,ice +
+
+ srtp + 0 +
diff --git a/Linphone/Ressources/linphonerc-default b/Linphone/Ressources/linphonerc-default index 8126cf1cc..80f2f4736 100644 --- a/Linphone/Ressources/linphonerc-default +++ b/Linphone/Ressources/linphonerc-default @@ -25,9 +25,9 @@ automatically_accept_direction=2 #receive only [app] tunnel=disabled -auto_start=1 -record_aware=1 -disable_chat_feature=0 +auto_download_incoming_voice_recordings=1 +auto_download_incoming_icalendars=1 + [tunnel] host= diff --git a/Linphone/Ressources/linphonerc-factory b/Linphone/Ressources/linphonerc-factory index eb16b25f2..eb87047ef 100644 --- a/Linphone/Ressources/linphonerc-factory +++ b/Linphone/Ressources/linphonerc-factory @@ -42,16 +42,11 @@ notify_each_friend_individually_when_presence_received=0 store_friends=0 [app] -activation_code_length=4 -prefer_basic_chat_room=1 record_aware=1 -disable_chat_feature=0 [account_creator] -backend=1 -# 1 means FlexiAPI, 0 is XMLRPC -url=https://subscribe.linphone.org/api/ -# replace above URL by https://staging-subscribe.linphone.org/api/ for testing +url=https://flexisip-staging-master.linphone.org/login # For testing +# url=https://subscribe.linphone.org/api/ [lime] lime_update_threshold=86400 @@ -59,9 +54,4 @@ lime_update_threshold=86400 [alerts] alerts_enabled=1 -[assistant] -algorithm=SHA-256 -password_min_length=6 -username_regex=^[a-z0-9+_.\-]*$ - ## End of factory rc diff --git a/Linphone/UI/Assistant/Fragments/LoginFragment.swift b/Linphone/UI/Assistant/Fragments/LoginFragment.swift index 35c629ede..a53421ab8 100644 --- a/Linphone/UI/Assistant/Fragments/LoginFragment.swift +++ b/Linphone/UI/Assistant/Fragments/LoginFragment.swift @@ -259,19 +259,15 @@ struct LoginFragment: View { .foregroundStyle(Color.grayMain2c700) .padding(.horizontal, 10) - NavigationLink(destination: RegisterFragment(), isActive: $isLinkREGActive, label: {Text("Register") - .default_text_style_orange_600(styleSize: 20) + NavigationLink(destination: RegisterFragment(registerViewModel: RegisterViewModel()), isActive: $isLinkREGActive, label: {Text("Register") + .default_text_style_white_600(styleSize: 20) .frame(height: 35) }) .disabled(!sharedMainViewModel.generalTermsAccepted) .padding(.horizontal, 20) .padding(.vertical, 10) + .background(Color.orangeMain500) .cornerRadius(60) - .overlay( - RoundedRectangle(cornerRadius: 60) - .inset(by: 0.5) - .stroke(Color.orangeMain500, lineWidth: 1) - ) .padding(.horizontal, 10) .simultaneousGesture( TapGesture().onEnded { diff --git a/Linphone/UI/Assistant/Fragments/RegisterCodeConfirmationFragment.swift b/Linphone/UI/Assistant/Fragments/RegisterCodeConfirmationFragment.swift new file mode 100644 index 000000000..2a0107edc --- /dev/null +++ b/Linphone/UI/Assistant/Fragments/RegisterCodeConfirmationFragment.swift @@ -0,0 +1,228 @@ +/* + * 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 RegisterCodeConfirmationFragment: View { + @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared + @ObservedObject var registerViewModel: RegisterViewModel + + @Environment(\.dismiss) var dismiss + + @StateObject var viewModel = ViewModel() + @FocusState var isFocused: Bool + + let textLimit = 4 + let textBoxWidth = UIScreen.main.bounds.width / 5 + let textBoxHeight = (UIScreen.main.bounds.width / 5) + 20 + let spaceBetweenBoxes: CGFloat = 10 + let paddingOfBox: CGFloat = 1 + var textFieldOriginalWidth: CGFloat { + (textBoxWidth*4)+(spaceBetweenBoxes*3)+((paddingOfBox*2)*3) + } + + var body: some View { + NavigationView { + GeometryReader { geometry in + ScrollView(.vertical) { + VStack { + ZStack { + Image("mountain") + .resizable() + .scaledToFill() + .frame(width: geometry.size.width, height: 100) + .clipped() + + VStack(alignment: .leading) { + HStack { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, -75) + .padding(.leading, -10) + .onTapGesture { + withAnimation { + dismiss() + } + } + + Spacer() + } + .padding(.leading) + } + .frame(width: geometry.size.width) + + Text("assistant_account_register") + .default_text_style_white_800(styleSize: 20) + .padding(.top, 20) + } + .padding(.top, 35) + .padding(.bottom, 10) + + ZStack { + VStack { + Spacer() + HStack { + Spacer() + Image("confirm_sms_code_illu") + .padding(.bottom, -geometry.safeAreaInsets.bottom) + } + } + VStack(alignment: .center) { + Spacer() + + Text(String(format: NSLocalizedString("assistant_account_creation_sms_confirmation_explanation", comment: ""), registerViewModel.phoneNumber)) + .default_text_style(styleSize: 15) + .foregroundStyle(Color.grayMain2c700) + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, alignment: .center) + .multilineTextAlignment(.center) + + VStack { + ZStack { + + HStack(spacing: spaceBetweenBoxes) { + otpText(text: viewModel.otp1, focused: viewModel.otpField.isEmpty) + otpText(text: viewModel.otp2, focused: viewModel.otpField.count == 1) + otpText(text: viewModel.otp3, focused: viewModel.otpField.count == 2) + otpText(text: viewModel.otp4, focused: viewModel.otpField.count == 3) + } + + TextField("", text: $viewModel.otpField) + .default_text_style_600(styleSize: 80) + .frame(width: isFocused ? 0 : textFieldOriginalWidth, height: textBoxHeight) + .textContentType(.oneTimeCode) + .foregroundColor(.clear) + .accentColor(.clear) + .background(.clear) + .keyboardType(.numberPad) + .focused($isFocused) + .onChange(of: viewModel.otpField) { _ in + limitText(textLimit) + } + } + } + .frame(maxWidth: .infinity, alignment: .center) + .padding(.vertical, 20) + + Button(action: { + dismiss() + }, label: { + Text("assistant_account_creation_wrong_phone_number") + .default_text_style_orange_600(styleSize: 15) + .frame(height: 35) + }) + .padding(.horizontal, 15) + .padding(.vertical, 5) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(Color.orangeMain500, lineWidth: 1) + ) + .padding(.bottom) + .frame(maxWidth: .infinity) + + Spacer() + Spacer() + } + .frame(maxWidth: sharedMainViewModel.maxWidth) + .padding(.horizontal, 20) + } + } + .frame(minHeight: geometry.size.height) + } + } + .navigationTitle("") + .navigationBarHidden(true) + } + .navigationViewStyle(StackNavigationViewStyle()) + .navigationTitle("") + .navigationBarHidden(true) + } + + private func otpText(text: String, focused: Bool) -> some View { + + return Text(text) + .foregroundStyle(isFocused && focused ? Color.orangeMain500 : Color.grayMain2c600) + .default_text_style_600(styleSize: 40) + .frame(width: textBoxWidth, height: textBoxHeight) + .overlay( + RoundedRectangle(cornerRadius: 20) + .inset(by: 0.5) + .stroke(isFocused && focused ? Color.orangeMain500 : Color.grayMain2c600, lineWidth: 1) + ) + .padding(paddingOfBox) + } + + func limitText(_ upper: Int) { + if viewModel.otpField.count > upper { + viewModel.otpField = String(viewModel.otpField.prefix(upper)) + } + } +} + +class ViewModel: ObservableObject { + + @Published var otpField = "" { + didSet { + guard otpField.count <= 5, + otpField.last?.isNumber ?? true else { + otpField = oldValue + return + } + } + } + var otp1: String { + guard otpField.count >= 1 else { + return "" + } + return String(Array(otpField)[0]) + } + var otp2: String { + guard otpField.count >= 2 else { + return "" + } + return String(Array(otpField)[1]) + } + var otp3: String { + guard otpField.count >= 3 else { + return "" + } + return String(Array(otpField)[2]) + } + var otp4: String { + guard otpField.count >= 4 else { + return "" + } + return String(Array(otpField)[3]) + } + + @Published var borderColor: Color = .black + var successCompletionHandler: (() -> Void)? + @Published var showResendText = false + +} + +#Preview { + RegisterCodeConfirmationFragment(registerViewModel: RegisterViewModel()) +} diff --git a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift index 5b8178f30..2a9510835 100644 --- a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift +++ b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift @@ -20,51 +20,278 @@ import SwiftUI struct RegisterFragment: View { + @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared + @ObservedObject var registerViewModel: RegisterViewModel @Environment(\.dismiss) var dismiss + @State private var isSecured: Bool = true + + @FocusState var isNameFocused: Bool + @FocusState var isPhoneNumberFocused: Bool + @FocusState var isPasswordFocused: Bool + + @State private var isLinkActive = false + @State private var isShowPopup = false + var body: some View { NavigationView { GeometryReader { geometry in - ScrollView(.vertical) { - VStack { - ZStack { - Image("mountain") - .resizable() - .scaledToFill() - .frame(width: geometry.size.width, height: 100) - .clipped() + ZStack { + ScrollView(.vertical) { + VStack { + ZStack { + Image("mountain") + .resizable() + .scaledToFill() + .frame(width: geometry.size.width, height: 100) + .clipped() + + VStack(alignment: .leading) { + HStack { + Image("caret-left") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + .padding(.top, -75) + .padding(.leading, -10) + .onTapGesture { + withAnimation { + dismiss() + } + } + + Spacer() + } + .padding(.leading) + } + .frame(width: geometry.size.width) + + Text("assistant_account_register") + .default_text_style_white_800(styleSize: 20) + .padding(.top, 20) + } + .padding(.top, 35) + .padding(.bottom, 10) VStack(alignment: .leading) { + Text(String(localized: "username")+"*") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + + TextField("username", text: $registerViewModel.username) + .default_text_style(styleSize: 15) + .disableAutocorrection(true) + .autocapitalization(.none) + .frame(height: 25) + .padding(.horizontal, 20) + .padding(.vertical, 15) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(isNameFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) + ) + .padding(.bottom) + .focused($isNameFocused) + + Text(String(localized: "Phone number")+"*") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + HStack { - Image("caret-left") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - .padding(.top, -75) - .padding(.leading, -10) - .onTapGesture { - withAnimation { - dismiss() + Menu { + Picker("", selection: $registerViewModel.dialPlanSelected) { + ForEach(Array(registerViewModel.dialPlansLabelList.enumerated()), id: \.offset) { index, dialPlan in + Text(dialPlan).tag(registerViewModel.dialPlansShortLabelList[index]) } } + } label: { + HStack { + Text(registerViewModel.dialPlanSelected) + + Image("caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.blue) + .frame(width: 15, height: 15) + } + } + .padding(.trailing, 5) + + Divider() + + TextField("Phone number", text: $registerViewModel.phoneNumber) + .default_text_style(styleSize: 15) + .disableAutocorrection(true) + .autocapitalization(.none) + .padding(.leading, 5) + .keyboardType(.numberPad) + } + .frame(height: 25) + .padding(.horizontal, 20) + .padding(.vertical, 15) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(isPhoneNumberFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) + ) + .padding(.bottom) + .focused($isPhoneNumberFocused) + + Text(String(localized: "password")+"*") + .default_text_style_700(styleSize: 15) + .padding(.bottom, -5) + + ZStack(alignment: .trailing) { + Group { + if isSecured { + SecureField("password", text: $registerViewModel.passwd) + .default_text_style(styleSize: 15) + .frame(height: 25) + .focused($isPasswordFocused) + } else { + TextField("password", text: $registerViewModel.passwd) + .default_text_style(styleSize: 15) + .disableAutocorrection(true) + .autocapitalization(.none) + .frame(height: 25) + .focused($isPasswordFocused) + } + } + + Button(action: { + isSecured.toggle() + }, label: { + Image(self.isSecured ? "eye-slash" : "eye") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 20, height: 20) + }) + } + .padding(.horizontal, 20) + .padding(.vertical, 15) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(isPasswordFocused ? Color.orangeMain500 : Color.gray200, lineWidth: 1) + ) + .padding(.bottom) + + NavigationLink(isActive: $isLinkActive, destination: { + RegisterCodeConfirmationFragment(registerViewModel: registerViewModel) + }, label: { + Text("assistant_account_create") + .default_text_style_white_600(styleSize: 20) + .frame(height: 35) + .frame(maxWidth: .infinity) + + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background((registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) ? Color.orangeMain100 : Color.orangeMain500) + .cornerRadius(60) + .disabled(!isLinkActive) + .padding(.bottom) + .simultaneousGesture( + TapGesture().onEnded { + if !(registerViewModel.username.isEmpty || registerViewModel.phoneNumber.isEmpty || registerViewModel.passwd.isEmpty) { + withAnimation { + self.isShowPopup = true + } + } + } + ) + + Spacer() + + Text("assistant_create_account_using_email_on_our_web_platform") + .default_text_style(styleSize: 15) + .foregroundStyle(Color.grayMain2c700) + .padding(.horizontal, 10) + .frame(maxWidth: .infinity, alignment: .center) + + Button(action: { + UIApplication.shared.open(URL(string: "https://subscribe.linphone.org/register/email")!) + }, label: { + Text("subscribe.linphone.org") + .default_text_style_orange_600(styleSize: 15) + .frame(height: 35) + }) + .padding(.horizontal, 15) + .padding(.vertical, 5) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(Color.orangeMain500, lineWidth: 1) + ) + .padding(.bottom) + .frame(maxWidth: .infinity) + + Spacer() + + HStack(alignment: .center) { + + Spacer() + + Text("assistant_already_have_an_account") + .default_text_style(styleSize: 15) + .foregroundStyle(Color.grayMain2c700) + .padding(.horizontal, 10) + + Button(action: { + dismiss() + }, label: { + Text("Login") + .default_text_style_white_600(styleSize: 20) + .frame(height: 35) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color.orangeMain500) + .cornerRadius(60) + .padding(.horizontal, 10) Spacer() } - .padding(.leading) + .padding(.bottom) } - .frame(width: geometry.size.width) - - Text("Register") - .default_text_style_white_800(styleSize: 20) - .padding(.top, 20) + .frame(maxWidth: sharedMainViewModel.maxWidth) + .padding(.horizontal, 20) } - .padding(.top, 35) - .padding(.bottom, 10) - + .frame(minHeight: geometry.size.height) } + + if self.isShowPopup { + let titlePopup = Text("assistant_dialog_confirm_phone_number_title") + let contentPopup = Text(String(format: NSLocalizedString("assistant_dialog_confirm_phone_number_message", comment: ""), registerViewModel.phoneNumber)) + + PopupView( + isShowPopup: $isShowPopup, + title: titlePopup, + content: contentPopup, + titleFirstButton: Text("Cancel"), + actionFirstButton: { + self.isShowPopup = false + }, + titleSecondButton: Text("Continue"), + actionSecondButton: { + self.isShowPopup = false + self.isLinkActive = true + } + ) + .background(.black.opacity(0.65)) + .onTapGesture { + self.isShowPopup = false + } + } + } } .navigationTitle("") @@ -77,5 +304,5 @@ struct RegisterFragment: View { } #Preview { - RegisterFragment() + RegisterFragment(registerViewModel: RegisterViewModel()) } diff --git a/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift b/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift new file mode 100644 index 000000000..bebc94fa4 --- /dev/null +++ b/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift @@ -0,0 +1,57 @@ +/* + * 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 Foundation +import linphonesw + +class RegisterViewModel: ObservableObject { + + private var coreContext = CoreContext.shared + + @Published var username: String = "" + @Published var phoneNumber: String = "" + @Published var passwd: String = "" + @Published var domain: String = "sip.linphone.org" + @Published var displayName: String = "" + @Published var transportType: String = "TLS" + + @Published var dialPlanSelected: String = "🇫🇷 +33" + @Published var dialPlansList: [DialPlan] = [] + @Published var dialPlansLabelList: [String] = [] + @Published var dialPlansShortLabelList: [String] = [] + + init() { + getDialPlansList() + } + + func getDialPlansList() { + coreContext.doOnCoreQueue { core in + let dialPlans = Factory.Instance.dialPlans + dialPlans.forEach { dialPlan in + self.dialPlansList.append(dialPlan) + self.dialPlansLabelList.append( + "\(dialPlan.flag) \(dialPlan.country) | +\(dialPlan.countryCallingCode)" + ) + self.dialPlansShortLabelList.append( + "\(dialPlan.flag) +\(dialPlan.countryCallingCode)" + ) + } + } + } +}