From ace3af17856c150f9eeeb6bd290db8e71f12cef0 Mon Sep 17 00:00:00 2001 From: tzw Date: Tue, 14 Jan 2025 17:10:10 +0630 Subject: [PATCH] add update phone number and recovery email --- assets/local/localization_en.json | 20 ++ assets/local/localization_mu.json | 20 ++ lib/app.dart | 3 + lib/constants.dart | 1 + lib/data/provider/auth_fb.dart | 10 +- lib/data/services/auth_imp.dart | 6 +- lib/data/services/auth_service.dart | 3 +- lib/domain/entities/auth_result.dart | 7 +- lib/domain/entities/user.dart | 33 +-- lib/helpers/theme.dart | 1 + lib/pages/customer/invitation_create.dart | 118 +++------ lib/pages/main/model/main_model.dart | 6 +- lib/pages/main/util.dart | 9 + lib/pages/profile/add_recovery_email.dart | 114 +++++++++ lib/pages/profile/change_phone_number.dart | 142 +++++++++++ lib/pages/profile/confirm_phone_number.dart | 232 ++++++++++++++++++ lib/pages/profile/confirm_recovery_email.dart | 138 +++++++++++ lib/pages/profile/profile_page.dart | 121 +++++---- lib/pages/signin/signin_page.dart | 99 +++----- lib/pages/signin/sms_code_page.dart | 35 ++- lib/pages/widgets/input_phone.dart | 188 ++++++++++++++ pubspec.yaml | 2 +- 22 files changed, 1087 insertions(+), 221 deletions(-) create mode 100644 lib/pages/profile/add_recovery_email.dart create mode 100644 lib/pages/profile/change_phone_number.dart create mode 100644 lib/pages/profile/confirm_phone_number.dart create mode 100644 lib/pages/profile/confirm_recovery_email.dart create mode 100644 lib/pages/widgets/input_phone.dart diff --git a/assets/local/localization_en.json b/assets/local/localization_en.json index 74fa593..4fa9b95 100644 --- a/assets/local/localization_en.json +++ b/assets/local/localization_en.json @@ -19,6 +19,7 @@ "btn.clear":"Clear Filter", "btn.filter":"Filter", "btn.exit_confirm":"Are you sure you want to exit?", + "btn.confirm":"Confirm", "Buttons End ================================================================":"", "Offline Start ================================================================":"", @@ -76,6 +77,8 @@ "Login Start ================================================================":"", "login.title":"Sign in to FCS", "login.phone":"Enter phone number", + "login.phone_number":"Phone Number", + "login.phone_empty":"Enter phone number", "Login End ================================================================":"", "SMS Start ================================================================":"", @@ -83,6 +86,7 @@ "sms.six.digit":"Enter 6 digit sms code sent to", "sms.resend":"Resend", "sms.resend.seconds":"Resend again in {0} seconds", + "sms.code":"SMS Code", "SMS End ================================================================":"", "FAQ Start ================================================================":"", @@ -132,6 +136,7 @@ "customer.list.title":"Customers", "customer.name":"Name", "customer.phone":"Phone number", + "customer.phone_empty":"Please insert phone number", "customer.status":"Status", "customer.fcs.id":"FCS ID", "customer.invitation.request.confirm":"Accept customer", @@ -183,6 +188,21 @@ "profile.email":"Email", "profile.privileges":"Privileges", "profile.default.delivery.address":"Default delivery address", + "profile.recovery.email":"Recovery Email", + "profile.change_phone.title":"Change Phone Number", + "profile.current_phone":"Current Phone Number", + "profile.new_phone":"New Phone Number", + "profile.new_phone_empty":"Enter new phone number", + "profile.send_sms":"Send SMS Code", + "profile.confirm_new_phone.title":"Confirm New Phone Number", + "profile.change_phone.btn":"Change", + "profile.recovery_email.title":"Recovery Email", + "profile.email.empty":"Enter recovery email", + "profile.email.mismatch_message":"Email format is incorrect", + "profile.email_instruction":"Your recovery email is used to help you get back into your account when you can’t sign in.", + "profile.comfirm_email.title":"Confirm Your Email", + "profile.confirm_email.send_to":"Sent email to", + "profile.confirm_email.insruction":"Please open the email to confirm recovery email", "Profile End ================================================================":"", "Package Start ================================================================":"", diff --git a/assets/local/localization_mu.json b/assets/local/localization_mu.json index befe69c..a2647be 100644 --- a/assets/local/localization_mu.json +++ b/assets/local/localization_mu.json @@ -18,6 +18,7 @@ "btn.clear":"Clear Filter", "btn.filter":"Filter", "btn.exit_confirm":"Are you sure you want to exit?", + "btn.confirm":"အတည်ပြုမည်", "Buttons End ================================================================":"", "Offline Start ================================================================":"", @@ -75,6 +76,8 @@ "Login Start ================================================================":"", "login.title":"FCS သို့အကောင့်ဒ်၀င်ပါ", "login.phone":"ဖုန်းနံပါတ်ထည့်ပါ", + "login.phone_number":"ဖုန်းနံပါတ်", + "login.phone_empty":"ဖုန်းနံပါတ်ထည့်ပါ", "Login End ================================================================":"", "SMS Start ================================================================":"", @@ -82,6 +85,7 @@ "sms.six.digit":"SMS ဂဏန်း ခြောက်လုံး ကိုရိုက်ထဲ့ပါ", "sms.resend":"ပြန်ပို့ရန်", "sms.resend.seconds":"SMS ပြန်ပို့ရန် {0} စက္ကန့် စောင့်ပါ", + "sms.code":"SMS Code", "SMS End ================================================================":"", "FAQ Start ================================================================":"", @@ -132,6 +136,7 @@ "customer.list.title":"ဝယ်ယူသူများ", "customer.name":"နာမည်", "customer.phone":"ဖုန်းနံပါတ်", + "customer.phone_empty":"Please insert phone number", "customer.status":"အခြေအနေ", "customer.fcs.id":"FCS ID", "customer.invitation.request.confirm":"လက်ခံ လိုက်ပါ", @@ -184,6 +189,21 @@ "profile.email":"အီးမေးလ်", "profile.privileges":"လုပ်ပိုင်ခွင့်များ", "profile.default.delivery.address":"အမြဲတမ်း ပို့ဆောင်ရမည့်လိပ်စာ", + "profile.recovery.email":"အီးမေးလ်", + "profile.change_phone.title":"ဖုန်းနံပါတ်ပြောင်းခြင်း", + "profile.current_phone":"လက်ရှိဖုန်းနံပါတ်", + "profile.new_phone":"ဖုန်းနံပါတ်အသစ်", + "profile.new_phone_empty":"ဖုန်းနံပါတ်အသစ်ထည့်ပါ", + "profile.send_sms":"SMS ကုဒ်ပို့မည်", + "profile.confirm_new_phone.title":"ဖုန်းနံပါတ်အသစ်ကို အတည်ပြုခြင်း", + "profile.change_phone.btn":"ပြောင်းမည်", + "profile.recovery_email.title":"အီးမေးလ်ထည့်ခြင်း", + "profile.email.empty":"အီးမေးလ်ထည့်ပါ", + "profile.email.mismatch_message":"အီးမေးလ်မမှန်ပါ", + "profile.email_instruction":"အကောင့်ဝင်လို့မရသောအခါတွင် သင့်အကောင့်ထဲသို့ ပြန်လည်ရောက်ရှိစေရန်အတွက် သင်၏အီးမေးလ်ကို အသုံးပြုပါသည်။", + "profile.comfirm_email.title":"သင့်အီးမေးလ်ကိုအတည်ပြုခြင်း", + "profile.confirm_email.send_to":"သင့်အီးမေးလ်သို့ ပေးပို့ခဲ့သည်", + "profile.confirm_email.insruction":"အီးမေးလ်ကို အတည်ပြုရန် သင့်အီးမေးလ်ကိုဖွင့်ပါ", "Profile End ================================================================":"", "Package Start ================================================================":"", diff --git a/lib/app.dart b/lib/app.dart index 779f703..7b739ba 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -29,6 +29,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; +import 'helpers/theme.dart'; import 'pages/carton/model/carton_selection_model.dart'; import 'pages/carton/model/consignee_selection_model.dart'; import 'pages/carton/model/package_selection_model.dart'; @@ -165,6 +166,8 @@ class _AppState extends State { debugShowCheckedModeBanner: false, theme: ThemeData( // useMaterial3: false, + textSelectionTheme: + const TextSelectionThemeData(cursorColor: primaryColor), dividerTheme: DividerThemeData(color: Colors.grey.shade200), colorScheme: ColorScheme.light(primary: Colors.white), dialogTheme: DialogTheme( diff --git a/lib/constants.dart b/lib/constants.dart index 5592bcb..2e662cf 100644 --- a/lib/constants.dart +++ b/lib/constants.dart @@ -1,5 +1,6 @@ const uploadPhotoLimit = 10; const shipmentCountForCartonFilter = 10; +const resendCountSec = 30; const config_collection = "configs"; const user_collection = "users"; diff --git a/lib/data/provider/auth_fb.dart b/lib/data/provider/auth_fb.dart index c55da89..1cb918b 100644 --- a/lib/data/provider/auth_fb.dart +++ b/lib/data/provider/auth_fb.dart @@ -29,7 +29,8 @@ class AuthFb { StreamSubscription? userListener; StreamSubscription? userAuthListener; - Future sendSmsCodeToPhoneNumber(String phoneNumber) { + Future sendSmsCodeToPhoneNumber(String phoneNumber, + {String? forceResendingToken}) { Completer completer = Completer(); bool codeSentCompleted = false; @@ -66,7 +67,9 @@ class AuthFb { print("codeSent " + phoneNumber); codeSentCompleted = true; if (!completer.isCompleted) - completer.complete(fcs.AuthResult(authStatus: AuthStatus.SMS_SENT)); + completer.complete(fcs.AuthResult( + authStatus: AuthStatus.SMS_SENT, + forceResendingToken: forceResendingToken?.toString())); }; final fb.PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout = @@ -83,6 +86,9 @@ class AuthFb { }; _fb.verifyPhoneNumber( + forceResendingToken: forceResendingToken != null + ? int.tryParse(forceResendingToken) + : null, phoneNumber: phoneNumber, timeout: const Duration(seconds: 0), verificationCompleted: verificationCompleted, diff --git a/lib/data/services/auth_imp.dart b/lib/data/services/auth_imp.dart index a923101..f8d49f3 100644 --- a/lib/data/services/auth_imp.dart +++ b/lib/data/services/auth_imp.dart @@ -16,8 +16,10 @@ class AuthServiceImp implements AuthService { final AuthFb authFb; @override - Future sendSmsCodeToPhoneNumber(String phoneNumber) { - return authFb.sendSmsCodeToPhoneNumber(phoneNumber); + Future sendSmsCodeToPhoneNumber(String phoneNumber, + {String? forceResendingToken}) { + return authFb.sendSmsCodeToPhoneNumber(phoneNumber, + forceResendingToken: forceResendingToken); } @override diff --git a/lib/data/services/auth_service.dart b/lib/data/services/auth_service.dart index 90017a3..0913e43 100644 --- a/lib/data/services/auth_service.dart +++ b/lib/data/services/auth_service.dart @@ -3,7 +3,8 @@ import 'package:fcs/domain/entities/setting.dart'; import 'package:fcs/domain/entities/user.dart'; abstract class AuthService { - Future sendSmsCodeToPhoneNumber(String phoneNumber); + Future sendSmsCodeToPhoneNumber(String phoneNumber, + {String? forceResendingToken}); Future signInWithSmsCode(String smsCode); Future signoutStart(); Future signoutEnd(); diff --git a/lib/domain/entities/auth_result.dart b/lib/domain/entities/auth_result.dart index 5467733..9c54fed 100644 --- a/lib/domain/entities/auth_result.dart +++ b/lib/domain/entities/auth_result.dart @@ -4,6 +4,11 @@ class AuthResult { AuthStatus? authStatus; String? authErrorCode; String? authErrorMsg; + String? forceResendingToken; - AuthResult({this.authStatus, this.authErrorCode, this.authErrorMsg}); + AuthResult( + {this.authStatus, + this.authErrorCode, + this.authErrorMsg, + this.forceResendingToken}); } diff --git a/lib/domain/entities/user.dart b/lib/domain/entities/user.dart index 6751e72..fe0eabf 100644 --- a/lib/domain/entities/user.dart +++ b/lib/domain/entities/user.dart @@ -22,6 +22,7 @@ class User { bool enablePinLogin; String? pinDigit; List privileges = []; + String? recoveryEmail; String get initial => name != null && name != "" ? name!.substring(0, 1) : "?"; @@ -51,7 +52,7 @@ class User { // for pin login String? pinToken; - bool get isPinLogin => pinToken!=null; + bool get isPinLogin => pinToken != null; String get phone => phoneNumber != null && phoneNumber!.startsWith("959") ? "0${phoneNumber!.substring(2)}" @@ -62,21 +63,21 @@ class User { bool get disabled => status != null && status == user_disabled_status; String get share => "Your phone number:$phoneNumber"; - User({ - this.id, - this.name, - this.phoneNumber, - this.fcsID, - this.status, - this.privileges = const [], - this.lastMessage, - this.lastMessageTime, - this.userUnseenCount = 0, - this.fcsUnseenCount = 0, - this.preferCurrency, - this.enablePinLogin = false, - this.pinDigit, - }); + User( + {this.id, + this.name, + this.phoneNumber, + this.fcsID, + this.status, + this.privileges = const [], + this.lastMessage, + this.lastMessageTime, + this.userUnseenCount = 0, + this.fcsUnseenCount = 0, + this.preferCurrency, + this.enablePinLogin = false, + this.pinDigit, + this.recoveryEmail}); factory User.fromJson(Map json) { return User( diff --git a/lib/helpers/theme.dart b/lib/helpers/theme.dart index dff3cea..d5c8907 100644 --- a/lib/helpers/theme.dart +++ b/lib/helpers/theme.dart @@ -9,6 +9,7 @@ const buttonBkColor = const Color(0xFF268944); const labelColor = const Color(0xFF757575); var dividerColor = Colors.grey.shade400; const dangerColor = const Color(0xffff0606); +const hintTextColor = Color.fromARGB(255, 187, 187, 187); const TextStyle labelStyle = TextStyle(fontSize: 20, color: labelColor, fontWeight: FontWeight.w500); diff --git a/lib/pages/customer/invitation_create.dart b/lib/pages/customer/invitation_create.dart index f937e7c..fcc99af 100644 --- a/lib/pages/customer/invitation_create.dart +++ b/lib/pages/customer/invitation_create.dart @@ -1,4 +1,4 @@ -import 'package:country_code_picker/country_code_picker.dart'; +import 'package:country_picker/country_picker.dart'; import 'package:fcs/helpers/theme.dart'; import 'package:fcs/pages/customer/model/customer_model.dart'; import 'package:fcs/pages/main/util.dart'; @@ -8,6 +8,8 @@ import 'package:fcs/pages/widgets/progress.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import '../widgets/input_phone.dart'; + class InvitationCreate extends StatefulWidget { @override _InvitationCreateState createState() => _InvitationCreateState(); @@ -18,13 +20,23 @@ class _InvitationCreateState extends State { TextEditingController _phoneController = new TextEditingController(); bool _isLoading = false; - late String dialCode; final _inviteFormKey = GlobalKey(); + late Country _selectedCountry; @override void initState() { super.initState(); - dialCode = "+95"; + _selectedCountry = Country( + name: 'Myanmar', + countryCode: 'MM', + displayName: 'Myanmar(MM)', + displayNameNoCountryCode: 'Myanmar', + e164Key: '', + e164Sc: -1, + example: '', + geographic: false, + level: -1, + phoneCode: '95'); } @override @@ -47,6 +59,18 @@ class _InvitationCreateState extends State { }, ); + final phoneBox = InputPhone( + lableKey: "customer.phone", + validationLabel: "customer.phone_empty", + phoneCtl: _phoneController, + selectedCountry: _selectedCountry, + onValueChange: (country) { + setState(() { + _selectedCountry = country; + }); + }, + ); + return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -73,76 +97,8 @@ class _InvitationCreateState extends State { children: [ nameBox, SizedBox(height: 10), - Row( - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Icon( - Icons.phone, - color: primaryColor, - ), - ), - Container( - decoration: BoxDecoration( - border: - Border.all(color: Colors.grey.shade400, width: 1), - borderRadius: - BorderRadius.all(Radius.circular(12.0))), - child: CountryCodePicker( - onChanged: _countryChange, - initialSelection: dialCode, - countryFilter: ['mm', 'us'], - showCountryOnly: false, - showOnlyCountryWhenClosed: false, - alignLeft: false, - textStyle: - TextStyle(fontSize: 16, color: Colors.black87), - searchDecoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.black, width: 1.0))), - ), - ), - SizedBox( - width: 10, - ), - Flexible( - child: Container( - padding: EdgeInsets.only(top: 10, bottom: 10), - child: TextFormField( - controller: _phoneController, - cursorColor: primaryColor, - textAlign: TextAlign.left, - keyboardType: TextInputType.phone, - style: TextStyle( - fontSize: 18, - ), - decoration: InputDecoration( - fillColor: Colors.white, - labelText: - getLocalString(context, "customer.phone"), - labelStyle: - TextStyle(fontSize: 16, color: Colors.grey), - filled: true, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: Colors.grey, width: 1.0)), - errorStyle: TextStyle( - color: dangerColor, - )), - validator: (value) { - if (value == null || value.isEmpty) { - return "Please insert phone no"; - } - return null; - }, - autovalidateMode: AutovalidateMode.onUserInteraction, - ), - ), - ), - ], - ), - SizedBox(height: 20), + phoneBox, + SizedBox(height: 30), fcsButton(context, getLocalString(context, "invite.btn"), callack: _invite), ], @@ -153,23 +109,21 @@ class _InvitationCreateState extends State { ); } - _countryChange(CountryCode countryCode) { - setState(() { - dialCode = countryCode.dialCode!; - }); - } - _invite() async { - String userName = _nameController.text; - String phoneNumber = dialCode + _phoneController.text; - if (!_inviteFormKey.currentState!.validate()) { return null; } + setState(() { _isLoading = true; }); try { + String userName = _nameController.text; + + bool isMyanmar = _selectedCountry.phoneCode == "95"; + String dialCode = isMyanmar ? "+959" : "+${_selectedCountry.phoneCode}"; + String phoneNumber = dialCode + _phoneController.text; + CustomerModel customerModel = Provider.of(context, listen: false); await customerModel.inviteUser(userName, phoneNumber); diff --git a/lib/pages/main/model/main_model.dart b/lib/pages/main/model/main_model.dart index 8c07754..6494085 100644 --- a/lib/pages/main/model/main_model.dart +++ b/lib/pages/main/model/main_model.dart @@ -153,8 +153,10 @@ class MainModel extends ChangeNotifier { return int.parse(packageInfo!.buildNumber) >= setting!.supportBuildNum; } - Future sendSms(String phoneNumber) { - return Services.instance.authService.sendSmsCodeToPhoneNumber(phoneNumber); + Future sendSms(String phoneNumber, + {String? forceResendingToken}) { + return Services.instance.authService.sendSmsCodeToPhoneNumber(phoneNumber, + forceResendingToken: forceResendingToken); } Future signin(String smsCode) async { diff --git a/lib/pages/main/util.dart b/lib/pages/main/util.dart index a4fc2bb..24791f8 100644 --- a/lib/pages/main/util.dart +++ b/lib/pages/main/util.dart @@ -403,3 +403,12 @@ String removeTrailingZeros(double number) { : result; return result; } + + +bool isValidEmail(String email) { + // Define a regular expression for validating an email + final emailRegex = RegExp(r'^[^@\s]+@[^@\s]+\.[^@\s]+$'); + + // Check if the email matches the pattern + return emailRegex.hasMatch(email); +} \ No newline at end of file diff --git a/lib/pages/profile/add_recovery_email.dart b/lib/pages/profile/add_recovery_email.dart new file mode 100644 index 0000000..a7a15b5 --- /dev/null +++ b/lib/pages/profile/add_recovery_email.dart @@ -0,0 +1,114 @@ +// ignore_for_file: use_build_context_synchronously + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import '../../domain/entities/user.dart'; +import '../../helpers/theme.dart'; +import '../../localization/app_translations.dart'; +import '../main/util.dart'; +import '../widgets/input_text.dart'; +import '../widgets/local_app_bar.dart'; +import '../widgets/local_text.dart'; +import '../widgets/progress.dart'; +import 'confirm_recovery_email.dart'; + +class AddRecoveryEmail extends StatefulWidget { + final User user; + const AddRecoveryEmail({super.key, required this.user}); + + @override + State createState() => _AddRecoveryEmailState(); +} + +class _AddRecoveryEmailState extends State { + final _formKey = GlobalKey(); + bool _isLoading = false; + final TextEditingController _emailCtl = TextEditingController(); + + @override + void initState() { + _emailCtl.text = widget.user.recoveryEmail ?? ""; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final emailBox = InputText( + labelTextKey: "profile.email", + iconData: Icons.email_outlined, + controller: _emailCtl, + validator: (value) { + if (value == null || value.isEmpty) { + return AppTranslations.of(context)!.text('profile.email.empty'); + } + + if (!isValidEmail(value)) { + return AppTranslations.of(context)! + .text('profile.email.mismatch_message'); + } + return null; + }, + ); + + final continueBtn = Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: fcsButton( + context, + getLocalString(context, 'btn.continue'), + callack: _continue, + ), + ); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: const LocalAppBar( + labelKey: 'profile.recovery_email.title', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor), + body: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.only(left: 15, right: 15, top: 10), + children: [ + LocalText(context, 'profile.email_instruction', + fontSize: 16, color: Colors.black), + const SizedBox(height: 15), + emailBox, + const SizedBox(height: 30), + continueBtn + ], + )), + ), + ); + } + + Future _continue() async { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + return; + } + + setState(() { + _isLoading = true; + }); + + try { + bool? updated = await Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => + ConfirmRecoveryEmail(email: _emailCtl.text))); + + if (updated ?? false) { + Navigator.pop(context, true); + } + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/profile/change_phone_number.dart b/lib/pages/profile/change_phone_number.dart new file mode 100644 index 0000000..91fc59c --- /dev/null +++ b/lib/pages/profile/change_phone_number.dart @@ -0,0 +1,142 @@ +// ignore_for_file: use_build_context_synchronously +import 'package:country_picker/country_picker.dart'; +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +import 'package:provider/provider.dart'; + +import '../../domain/entities/auth_result.dart'; +import '../../domain/entities/auth_status.dart'; +import '../../domain/entities/user.dart'; +import '../main/model/main_model.dart'; +import '../main/util.dart'; +import '../widgets/display_text.dart'; +import '../widgets/input_phone.dart'; +import '../widgets/local_app_bar.dart'; +import '../widgets/progress.dart'; +import 'confirm_phone_number.dart'; + +class ChangePhoneNumber extends StatefulWidget { + final User user; + const ChangePhoneNumber({super.key, required this.user}); + + @override + State createState() => _ChangePhoneNumberState(); +} + +class _ChangePhoneNumberState extends State { + bool _isLoading = false; + TextEditingController _newPhoneCtl = TextEditingController(); + late Country _selectedCountry; + final _formKey = GlobalKey(); + + @override + void initState() { + _selectedCountry = Country( + name: 'Myanmar', + countryCode: 'MM', + displayName: 'Myanmar(MM)', + displayNameNoCountryCode: 'Myanmar', + e164Key: '', + e164Sc: -1, + example: '', + geographic: false, + level: -1, + phoneCode: '95'); + super.initState(); + } + + @override + Widget build(BuildContext context) { + final currentPhoneBox = DisplayText( + text: widget.user.phone, + labelTextKey: "profile.current_phone", + iconData: Icons.phone); + + final newPhoneBox = InputPhone( + autofocus: true, + lableKey: "profile.new_phone", + validationLabel: "profile.new_phone_empty", + phoneCtl: _newPhoneCtl, + selectedCountry: _selectedCountry, + onValueChange: (country) { + setState(() { + _selectedCountry = country; + }); + }, + ); + + final sendBtn = Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: fcsButton( + context, + getLocalString(context, 'profile.send_sms'), + callack: _sendSMS, + ), + ); + + return Form( + key: _formKey, + child: LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: const LocalAppBar( + labelKey: 'profile.change_phone.title', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor, + ), + body: ListView( + padding: const EdgeInsets.only(left: 15, right: 15, top: 10), + children: [ + currentPhoneBox, + const SizedBox(height: 15), + newPhoneBox, + const SizedBox(height: 30), + sendBtn + ], + ), + ), + ), + ); + } + + Future _sendSMS() async { + if (_formKey.currentState != null && !_formKey.currentState!.validate()) { + return; + } + if (_newPhoneCtl.text.length < 5) { + showMsgDialog(context, "Error", "Invalid phone number"); + return; + } + + setState(() { + _isLoading = true; + }); + + try { + bool isMyanmar = _selectedCountry.phoneCode == "95"; + String dialCode = isMyanmar ? "+959" : "+${_selectedCountry.phoneCode}"; + String phoneNumber = dialCode + _newPhoneCtl.text; + + AuthResult auth = await context.read().sendSms(phoneNumber); + if (auth.authStatus == AuthStatus.SMS_SENT) { + bool? updated = await Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => ConfirmPhoneNumber( + phoneNumber: phoneNumber, + forceResendingToken: auth.forceResendingToken))); + if (updated ?? false) { + Navigator.pop(context, true); + } + } + } catch (e) { + showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/profile/confirm_phone_number.dart b/lib/pages/profile/confirm_phone_number.dart new file mode 100644 index 0000000..e593c5e --- /dev/null +++ b/lib/pages/profile/confirm_phone_number.dart @@ -0,0 +1,232 @@ +// ignore_for_file: use_build_context_synchronously + +import 'dart:async'; + +import 'package:fcs/constants.dart'; +import 'package:fcs/pages/widgets/local_app_bar.dart'; +import 'package:fcs/pages/widgets/progress.dart'; +import 'package:flutter/material.dart'; +import 'package:pin_input_text_field/pin_input_text_field.dart'; +import 'package:provider/provider.dart'; + +import '../../domain/entities/auth_result.dart'; +import '../../domain/entities/auth_status.dart'; +import '../../helpers/theme.dart'; +import '../main/model/main_model.dart'; +import '../main/util.dart'; +import '../widgets/local_text.dart'; + +class ConfirmPhoneNumber extends StatefulWidget { + final String phoneNumber; + final String? forceResendingToken; + const ConfirmPhoneNumber( + {super.key, required this.phoneNumber, this.forceResendingToken}); + + @override + State createState() => _ConfirmPhoneNumberState(); +} + +class _ConfirmPhoneNumberState extends State { + final _formKey = GlobalKey(); + bool _isLoading = false; + late String pin; + late bool allNumberEntered; + late Timer _timer; + int _start = resendCountSec; + bool canResend = false; + String? _forceResendingToken; + + @override + void initState() { + pin = ""; + allNumberEntered = false; + _forceResendingToken = widget.forceResendingToken; + super.initState(); + startTimer(); + } + + void startTimer() { + _timer = Timer.periodic( + const Duration(seconds: 1), + (t) => setState( + () { + if (_start < 1) { + t.cancel(); + canResend = true; + } else { + _start = _start - 1; + } + }, + ), + ); + } + + @override + void dispose() { + _timer.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + bool isMyanmar = widget.phoneNumber.startsWith("+959"); + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: const LocalAppBar( + labelKey: 'profile.confirm_new_phone.title', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor), + body: Form( + key: _formKey, + child: ListView( + padding: const EdgeInsets.only(left: 15, right: 15, top: 10), + children: [ + Container( + padding: const EdgeInsets.only(top: 40), + child: Row( + children: [ + Flexible( + child: LocalText(context, "sms.six.digit", + fontSize: 16, color: labelColor), + ), + ], + ), + ), + Text( + widget.phoneNumber.startsWith("+959") + ? "0${widget.phoneNumber.substring(3)}" + : widget.phoneNumber, + style: const TextStyle(color: primaryColor, fontSize: 16), + ), + const SizedBox( + height: 30, + ), + LocalText(context, "sms.code", color: labelColor, fontSize: 16), + Container( + padding: const EdgeInsets.only(top: 10), + child: PinInputTextField( + pinLength: 6, + cursor: Cursor( + color: primaryColor, + enabled: true, + width: 2, + height: 23), + decoration: BoxLooseDecoration( + textStyle: TextStyle( + color: + Theme.of(context).textTheme.labelMedium!.color, + fontSize: 20), + strokeColorBuilder: + const FixedColorBuilder(labelColor)), + textInputAction: TextInputAction.done, + autoFocus: true, + onChanged: _pinChange, + ), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: fcsButton( + context, + getLocalString(context, 'profile.change_phone.btn'), + callack: allNumberEntered + ? () { + _change(); + } + : null, + ), + ), + const SizedBox(height: 30), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: + canResend ? Colors.white : Colors.grey, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8.0), + side: BorderSide( + color: canResend + ? primaryColor + : Colors.grey.shade400))), + onPressed: canResend + ? () { + _resend(isMyanmar); + } + : null, + child: LocalText(context, "sms.resend", + color: + canResend ? primaryColor : Colors.grey.shade400, + fontSize: 16), + ), + ], + ), + Row( + children: [ + LocalText(context, 'sms.resend.seconds', + fontSize: 15, + translationVariables: [_start.toString()], + color: primaryColor), + ], + ), + const SizedBox(height: 20), + ], + )), + ), + ); + } + + _pinChange(pin) { + setState(() { + this.pin = pin; + allNumberEntered = this.pin.length == 6; + }); + } + + _resend(bool isMyanmar) async { + try { + setState(() { + _start = resendCountSec; + canResend = false; + }); + + _timer.cancel(); + startTimer(); + + var mainModel = context.read(); + + AuthResult auth = await mainModel.sendSms(widget.phoneNumber, + forceResendingToken: _forceResendingToken); + + if (auth.authStatus == AuthStatus.SMS_SENT) { + _forceResendingToken = auth.forceResendingToken; + } + } catch (e) { + await showMsgDialog(context, "Error", e.toString()); + } + } + + _change() async { + setState(() { + _isLoading = true; + }); + + try { + // var mainModel = context.read(); + // await mainModel.updatePhoneNumber(pin); + Navigator.pop(context, true); + } catch (e) { + await showMsgDialog(context, "Error", e.toString()); + } finally { + if (mounted) { + setState(() { + _isLoading = false; + }); + } + } + } +} diff --git a/lib/pages/profile/confirm_recovery_email.dart b/lib/pages/profile/confirm_recovery_email.dart new file mode 100644 index 0000000..800d7c9 --- /dev/null +++ b/lib/pages/profile/confirm_recovery_email.dart @@ -0,0 +1,138 @@ +import 'package:flutter/material.dart'; +import 'package:pin_input_text_field/pin_input_text_field.dart'; +import 'package:provider/provider.dart'; + +import '../../helpers/theme.dart'; +import '../../localization/app_translations.dart'; +import '../main/model/language_model.dart'; +import '../main/util.dart'; +import '../widgets/local_app_bar.dart'; +import '../widgets/local_text.dart'; +import '../widgets/progress.dart'; + +class ConfirmRecoveryEmail extends StatefulWidget { + final String email; + const ConfirmRecoveryEmail({super.key, required this.email}); + + @override + State createState() => _ConfirmRecoveryEmailState(); +} + +class _ConfirmRecoveryEmailState extends State { + bool _isLoading = false; + late String pin; + late bool allNumberEntered; + + @override + void initState() { + pin = ""; + allNumberEntered = false; + super.initState(); + } + + @override + Widget build(BuildContext context) { + var isEng = context.watch().isEng; + + return LocalProgress( + inAsyncCall: _isLoading, + child: Scaffold( + appBar: const LocalAppBar( + labelKey: 'profile.comfirm_email.title', + backgroundColor: Colors.white, + labelColor: primaryColor, + arrowColor: primaryColor), + body: ListView( + padding: const EdgeInsets.only(left: 15, right: 15, top: 20), + children: [ + RichText( + text: TextSpan(children: [ + TextSpan( + text: AppTranslations.of(context)! + .text("profile.confirm_email.send_to"), + style: isEng + ? newLabelStyle( + fontWeight: FontWeight.normal, + color: + labelColor, + fontSize: 16) + : newLabelStyleMM( + fontWeight: FontWeight.normal, + color: + labelColor, + fontSize: 16)), + TextSpan( + text: " ${widget.email}", + style: const TextStyle(fontSize: 16, color: primaryColor)) + ]), + ), + const SizedBox(height: 40), + LocalText( + context, + 'profile.confirm_email.insruction', + fontSize: 16, + color: labelColor, + fontWeight: FontWeight.normal, + ), + const SizedBox(height: 40), + LocalText(context, "sms.code", + color: labelColor, fontSize: 16), + Container( + padding: const EdgeInsets.only(top: 10), + child: PinInputTextField( + pinLength: 6, + cursor: Cursor( + color: primaryColor, enabled: true, width: 2, height: 23), + decoration: BoxLooseDecoration( + textStyle: TextStyle( + color: Theme.of(context).textTheme.labelMedium!.color, + fontSize: 20), + strokeColorBuilder: const FixedColorBuilder(labelColor)), + textInputAction: TextInputAction.done, + autoFocus: true, + onChanged: _pinChange, + ), + ), + const SizedBox(height: 40), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 30), + child: fcsButton( + context, + getLocalString(context, 'btn.confirm'), + callack: allNumberEntered + ? () { + _confirm(); + } + : null, + ), + ) + ], + ), + ), + ); + } + + _pinChange(pin) { + setState(() { + this.pin = pin; + allNumberEntered = this.pin.length == 6; + }); + } + + _confirm() async { + setState(() { + _isLoading = true; + }); + + try { + Navigator.pop(context, true); + } catch (e) { + // ignore: use_build_context_synchronously + await showMsgDialog(context, "Error", e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } +} diff --git a/lib/pages/profile/profile_page.dart b/lib/pages/profile/profile_page.dart index 6c9eb07..468ddfd 100644 --- a/lib/pages/profile/profile_page.dart +++ b/lib/pages/profile/profile_page.dart @@ -25,6 +25,8 @@ import '../../helpers/theme.dart'; import 'package:collection/collection.dart'; import 'account_delection_page.dart'; +import 'add_recovery_email.dart'; +import 'change_phone_number.dart'; typedef void ProfileCallback(); @@ -69,44 +71,70 @@ class _ProfileState extends State { if (mainModel.user == null) { return Container(); } + User user = mainModel.user!; + buildLanguage(languageModel); - DeliveryAddressModel deliveryAddressModel = - Provider.of(context); + var deliveryAddressModel = Provider.of(context); final namebox = DisplayText( - text: "${mainModel.user!.name ?? ''}" + - " (${mainModel.user!.status ?? ''})", + text: "${user.name ?? ''}" + " (${user.status ?? ''})", labelTextKey: "profile.name", iconData: Icons.person, ); - final currencyBox = DisplayText( - text: mainModel.user!.preferCurrency ?? "", - labelTextKey: "profile.currency", - iconData: FontAwesome5Regular.money_bill_alt, + + final currencyBox = Row( + children: [ + Expanded( + child: DisplayText( + text: user.preferCurrency ?? "", + labelTextKey: "profile.currency", + iconData: FontAwesome5Regular.money_bill_alt, + )), + Padding( + padding: const EdgeInsets.only(right: 0), + child: IconButton( + icon: Icon(Icons.edit, color: Colors.grey), + onPressed: _editCurrency), + ) + ], ); + final deleteAccountBox = DisplayText( labelTextKey: "profile.delete.title", iconData: MaterialCommunityIcons.account_remove, ); - final phonenumberbox = DisplayText( - text: mainModel.user!.phone, - labelTextKey: "profile.phone", - iconData: Icons.phone, + final phonenumberBox = Row( + children: [ + Expanded( + child: DisplayText( + text: user.phone, + labelTextKey: "profile.phone", + iconData: Icons.phone), + ), + IconButton( + icon: Icon(Icons.edit, color: Colors.grey), + onPressed: () { + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => ChangePhoneNumber(user: user))); + }) + ], ); + final fcsIDBox = Row( children: [ Expanded( child: DisplayText( - text: mainModel.user!.fcsID ?? "", + text: user.fcsID ?? "", labelTextKey: "customer.fcs.id", icon: FcsIDIcon(), ), ), IconButton( icon: Icon(Icons.content_copy, color: Colors.grey), - onPressed: () => _copy(getLocalString(context, "customer.fcs.id"), - mainModel.user!.fcsID ?? ""), + onPressed: () => _copy( + getLocalString(context, "customer.fcs.id"), user.fcsID ?? ""), ) ], ); @@ -141,6 +169,22 @@ class _ProfileState extends State { }, iconData: Icons.exit_to_app); + final emailBox = Row( + children: [ + Expanded( + child: DisplayText( + text: user.recoveryEmail, + labelTextKey: "profile.recovery.email", + iconData: Icons.email_outlined, + ), + ), + IconButton(icon: Icon(Icons.edit, color: Colors.grey), onPressed: () { + Navigator.of(context, rootNavigator: true).push( + CupertinoPageRoute( + builder: (context) => AddRecoveryEmail(user: user))); + }) + ], + ); return LocalProgress( inAsyncCall: _isLoading, child: Scaffold( @@ -165,20 +209,11 @@ class _ProfileState extends State { ) ], ), - phonenumberbox, + phonenumberBox, fcsIDBox, usaShippingAddressBox, - Row( - children: [ - Expanded(child: currencyBox), - Padding( - padding: const EdgeInsets.only(right: 0), - child: IconButton( - icon: Icon(Icons.edit, color: Colors.grey), - onPressed: _editCurrency), - ) - ], - ), + currencyBox, + emailBox, DefaultDeliveryAddress( labelKey: "profile.default.delivery.address", deliveryAddress: deliveryAddressModel.defalutAddress, @@ -298,20 +333,22 @@ class _ProfileState extends State { } }); - return Column( - children: [ - DisplayText( - labelTextKey: "profile.privileges", - iconData: MaterialCommunityIcons.clipboard_check_outline, - ), - Padding( - padding: const EdgeInsets.only(left: 30.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: getRowPrivilegeWidget(privileges)), - ) - ], - ); + return privileges.isEmpty + ? const SizedBox() + : Column( + children: [ + DisplayText( + labelTextKey: "profile.privileges", + iconData: MaterialCommunityIcons.clipboard_check_outline, + ), + Padding( + padding: const EdgeInsets.only(left: 30.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: getRowPrivilegeWidget(privileges)), + ) + ], + ); } List getRowPrivilegeWidget(List privileges) { @@ -365,7 +402,7 @@ class _ProfileState extends State { if (scaffold == null) { scaffold = ScaffoldMessenger.of(context); } - + scaffold.showSnackBar( SnackBar( content: Text('copied "$title" data to clipboard'), diff --git a/lib/pages/signin/signin_page.dart b/lib/pages/signin/signin_page.dart index 8879658..317c2f5 100644 --- a/lib/pages/signin/signin_page.dart +++ b/lib/pages/signin/signin_page.dart @@ -1,4 +1,4 @@ -import 'package:country_code_picker/country_code_picker.dart'; +import 'package:country_picker/country_picker.dart'; import 'package:fcs/domain/entities/auth_result.dart'; import 'package:fcs/domain/entities/auth_status.dart'; import 'package:fcs/pages/main/model/main_model.dart'; @@ -13,6 +13,7 @@ import 'package:provider/provider.dart'; import '../../helpers/theme.dart'; import '../main/util.dart'; +import '../widgets/input_phone.dart'; import 'sms_code_page.dart'; class SigninPage extends StatefulWidget { @@ -25,15 +26,22 @@ class _SigninPageState extends State { late String dialCode; TextEditingController phonenumberCtl = new TextEditingController(); + late Country _selectedCountry; @override void initState() { super.initState(); - phonenumberCtl = new TextEditingController(text: "09"); - phonenumberCtl.selection = TextSelection.fromPosition( - TextPosition(offset: phonenumberCtl.text.length)); - - dialCode = "+95"; + _selectedCountry = Country( + name: 'Myanmar', + countryCode: 'MM', + displayName: 'Myanmar(MM)', + displayNameNoCountryCode: 'Myanmar', + e164Key: '', + e164Sc: -1, + example: '', + geographic: false, + level: -1, + phoneCode: '95'); } @override @@ -67,53 +75,16 @@ class _SigninPageState extends State { fontSize: 16, ), ), - Row( - children: [ - Container( - decoration: BoxDecoration( - border: Border.all(color: Colors.grey.shade400, width: 1), - borderRadius: BorderRadius.all(Radius.circular(12.0))), - child: CountryCodePicker( - onChanged: _countryChange, - initialSelection: dialCode, - countryFilter: ['mm', 'us'], - showCountryOnly: false, - showOnlyCountryWhenClosed: false, - alignLeft: false, - textStyle: TextStyle(fontSize: 16, color: Colors.black87), - searchDecoration: InputDecoration( - focusedBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: Colors.black, width: 1.0)))), - ), - SizedBox( - width: 10, - ), - Flexible( - child: Container( - padding: EdgeInsets.only(top: 10, bottom: 10), - child: TextFormField( - controller: phonenumberCtl, - cursorColor: primaryColor, - textAlign: TextAlign.left, - autofocus: true, - keyboardType: TextInputType.phone, - style: TextStyle( - fontSize: 18, - ), - decoration: new InputDecoration( - contentPadding: EdgeInsets.only(top: 8), - enabledBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: primaryColor, width: 1.0)), - focusedBorder: UnderlineInputBorder( - borderSide: - BorderSide(color: primaryColor, width: 1.0)), - ), - ), - ), - ), - ], + InputPhone( + autofocus: true, + validationLabel: "login.phone_empty", + phoneCtl: phonenumberCtl, + selectedCountry: _selectedCountry, + onValueChange: (country) { + setState(() { + _selectedCountry = country; + }); + }, ), SizedBox( height: 20, @@ -140,15 +111,8 @@ class _SigninPageState extends State { ); } - _countryChange(CountryCode countryCode) { - setState(() { - dialCode = countryCode.dialCode ?? '+95'; - }); - } - _next() async { - String phoneNumber = phonenumberCtl.text; - if (phoneNumber.length < 5) { + if (phonenumberCtl.text.length < 5) { showMsgDialog(context, "Error", "Invalid phone number"); return; } @@ -157,16 +121,17 @@ class _SigninPageState extends State { _isLoading = true; }); - try { - phoneNumber = phoneNumber[0] == "0" - ? phoneNumber.replaceFirst("0", "") - : phoneNumber; - phoneNumber = dialCode + phoneNumber; + bool isMyanmar = _selectedCountry.phoneCode == "95"; + String dialCode = isMyanmar ? "+959" : "+${_selectedCountry.phoneCode}"; + try { + String phoneNumber = dialCode + phonenumberCtl.text; AuthResult auth = await context.read().sendSms(phoneNumber); if (auth.authStatus == AuthStatus.SMS_SENT) { await Navigator.of(context).push(CupertinoPageRoute( - builder: (context) => SmsCodePage(phoneNumber: phoneNumber))); + builder: (context) => SmsCodePage( + phoneNumber: phoneNumber, + forceResendingToken: auth.forceResendingToken))); Navigator.pop(context); } else if (auth.authStatus == AuthStatus.AUTH_VERIFIED) { await navigateAfterAuthVerified(context); diff --git a/lib/pages/signin/sms_code_page.dart b/lib/pages/signin/sms_code_page.dart index 3cb3515..80c73bd 100644 --- a/lib/pages/signin/sms_code_page.dart +++ b/lib/pages/signin/sms_code_page.dart @@ -12,13 +12,15 @@ import 'package:flutter/material.dart'; import 'package:pin_input_text_field/pin_input_text_field.dart'; import 'package:provider/provider.dart'; +import '../../constants.dart'; import '../../helpers/theme.dart'; -const resend_count_sec = 30; - class SmsCodePage extends StatefulWidget { final String phoneNumber; - const SmsCodePage({Key? key, required this.phoneNumber}) : super(key: key); + final String? forceResendingToken; + const SmsCodePage( + {Key? key, required this.phoneNumber, this.forceResendingToken}) + : super(key: key); @override _SmsCodePageState createState() => _SmsCodePageState(); } @@ -26,16 +28,18 @@ class SmsCodePage extends StatefulWidget { class _SmsCodePageState extends State { bool _isLoading = false; bool canResend = false; - int _start = resend_count_sec; + int _start = resendCountSec; late String pin; late bool allNumberEntered; late Timer _timer; + String? _forceResendingToken; @override void initState() { pin = ""; allNumberEntered = false; + _forceResendingToken = widget.forceResendingToken; super.initState(); startTimer(); } @@ -186,7 +190,28 @@ class _SmsCodePageState extends State { }); } - _resend() async {} + _resend() async { + try { + setState(() { + _start = resendCountSec; + canResend = false; + }); + + _timer.cancel(); + startTimer(); + + var mainModel = context.read(); + + AuthResult auth = await mainModel.sendSms(widget.phoneNumber, + forceResendingToken: _forceResendingToken); + + if (auth.authStatus == AuthStatus.SMS_SENT) { + _forceResendingToken = auth.forceResendingToken; + } + } catch (e) { + await showMsgDialog(context, "Error", e.toString()); + } + } _verify() async { setState(() { diff --git a/lib/pages/widgets/input_phone.dart b/lib/pages/widgets/input_phone.dart new file mode 100644 index 0000000..159d573 --- /dev/null +++ b/lib/pages/widgets/input_phone.dart @@ -0,0 +1,188 @@ +import 'package:fcs/helpers/theme.dart'; +import 'package:flutter/material.dart'; +import 'package:country_picker/country_picker.dart'; +import 'package:provider/provider.dart'; + +import '../../localization/app_translations.dart'; +import '../main/model/language_model.dart'; + +typedef OnValueChange = Function(Country country); + +class InputPhone extends StatefulWidget { + final String? lableKey; + final String? validationLabel; + final OnValueChange onValueChange; + final Country selectedCountry; + final TextEditingController phoneCtl; + final Function(String)? onInputChanged; + final bool autofocus; + + const InputPhone( + {super.key, + this.lableKey, + required this.onValueChange, + required this.selectedCountry, + this.onInputChanged, + required this.phoneCtl, + this.validationLabel, + this.autofocus = false}); + + @override + State createState() => _InputPhoneState(); +} + +class _InputPhoneState extends State { + List? signinCountries = []; + + @override + void initState() { + init(); + super.initState(); + } + + init() { + List? countries = ['mm', 'us']; + + for (var c in countries) { + setState(() { + signinCountries?.add(c.toUpperCase()); + }); + } + } + + @override + Widget build(BuildContext context) { + bool isMyanmar = widget.selectedCountry.phoneCode == "95"; + var languageModel = Provider.of(context); + + return Padding( + padding: const EdgeInsets.only(top: 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + showCountryPicker( + context: context, + countryFilter: signinCountries, + showPhoneCode: true, + showSearch: true, + countryListTheme: const CountryListThemeData( + flagSize: 25, + backgroundColor: Colors.white, + textStyle: TextStyle(fontSize: 15, color: Colors.black), + bottomSheetHeight: 500, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.0), + topRight: Radius.circular(20.0), + ), + inputDecoration: InputDecoration( + labelText: 'Search', + labelStyle: TextStyle(color: Colors.black), + floatingLabelStyle: TextStyle(color: Colors.black), + hintText: 'Search', + hintStyle: TextStyle(color: Colors.black), + hintMaxLines: 1, + prefixIcon: Icon(Icons.search, color: Colors.black), + border: OutlineInputBorder(), + enabledBorder: OutlineInputBorder( + borderSide: + BorderSide(width: 1, color: Colors.black)), + focusedBorder: OutlineInputBorder( + borderRadius: + BorderRadius.all(Radius.circular(5.0)), + borderSide: BorderSide(color: Colors.black)), + ), + searchTextStyle: TextStyle(color: Colors.black)), + onSelect: (Country country) { + widget.onValueChange(country); + }, + ); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + width: 95, + padding: const EdgeInsets.only( + left: 5, bottom: 12, right: 5, top: 12), + decoration: BoxDecoration( + border: Border.all(color: Colors.grey.shade400), + borderRadius: BorderRadius.circular(5), + ), + child: Row( + children: [ + Container( + padding: const EdgeInsets.fromLTRB(0, 0, 5, 0), + child: Text(widget.selectedCountry.flagEmoji)), + Text( + isMyanmar + ? "+95" + : "+${widget.selectedCountry.phoneCode}", + style: const TextStyle( + fontSize: 16, color: Colors.black), + ), + const Icon(Icons.arrow_drop_down), + ], + ), + ) + ], + ), + ), + const SizedBox(width: 5), + Flexible( + child: Container( + padding: const EdgeInsets.only(left: 10), + child: TextFormField( + autofocus: widget.autofocus, + keyboardType: TextInputType.phone, + onChanged: widget.onInputChanged, + controller: widget.phoneCtl, + maxLines: 1, + cursorColor: primaryColor, + style: const TextStyle(fontSize: 16, color: Colors.black), + decoration: InputDecoration( + labelStyle: languageModel.isEng + ? newLabelStyle(color: Colors.black54, fontSize: 20) + : newLabelStyleMM( + color: Colors.black54, fontSize: 20), + labelText: widget.lableKey != null + ? AppTranslations.of(context)! + .text(widget.lableKey!) + : null, + hintStyle: const TextStyle(color: Color(0xFFBBBBBB)), + errorStyle: const TextStyle(color: dangerColor), + prefix: Padding( + padding: EdgeInsets.only(left: isMyanmar ? 0 : 13), + child: Text(isMyanmar ? "9 " : "", + style: const TextStyle(color: Colors.black))), + enabledBorder: const UnderlineInputBorder( + borderSide: + BorderSide(color: primaryColor, width: 1.0)), + focusedBorder: const UnderlineInputBorder( + borderSide: + BorderSide(color: primaryColor, width: 1.0)), + errorBorder: null, + focusedErrorBorder: null), + validator: (value) { + if (value!.isEmpty) { + if (widget.validationLabel == null) return null; + return AppTranslations.of(context)! + .text(widget.validationLabel!); + } + return null; + }, + ), + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3830591..dc0e31e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,6 @@ dependencies: connectivity_plus: ^6.1.0 logging: ^1.0.1 permission_handler: ^11.1.0 - country_code_picker: ^3.0.0 pin_input_text_field: ^4.1.0 flutter_icons_null_safety: ^1.1.0 country_icons: ^2.0.2 @@ -59,6 +58,7 @@ dependencies: qr_flutter: ^4.1.0 flutter_markdown: ^0.6.20+1 flutter_slidable: ^3.1.1 + country_picker: ^2.0.26 dev_dependencies: flutter_test: