diff --git a/assets/icons/icon-flag.png b/assets/icons/icon-flag.png new file mode 100644 index 0000000..32fee5b Binary files /dev/null and b/assets/icons/icon-flag.png differ diff --git a/assets/images/img-success-signup.png b/assets/images/img-success-signup.png new file mode 100644 index 0000000..a85648d Binary files /dev/null and b/assets/images/img-success-signup.png differ diff --git a/lib/application/assets/path_assets.dart b/lib/application/assets/path_assets.dart index 3bfdc02..bf981d5 100644 --- a/lib/application/assets/path_assets.dart +++ b/lib/application/assets/path_assets.dart @@ -11,6 +11,7 @@ class PathAssets { static const String icon1 = 'assets/icons/icon-1.png'; static const String iconConnect = 'assets/icons/icon-connect.png'; static const String iconShield = 'assets/icons/icon-shield.png'; + static const String iconFlag = 'assets/icons/icon-flag.png'; /// IMAGE static const String imgSplashLogo = 'assets/images/splash-logo.png'; @@ -23,4 +24,5 @@ class PathAssets { static const String imgKtpCropped = 'assets/images/img-ktp-cropped.png'; static const String imgKtpClear = 'assets/images/img-ktp-clear.png'; static const String imgKtpBlur = 'assets/images/img-ktp-blur.png'; + static const String imgSuccessSignup = 'assets/images/img-success-signup.png'; } diff --git a/lib/application/component/button/button_view.dart b/lib/application/component/button/button_view.dart index 19799b6..8ea3823 100644 --- a/lib/application/component/button/button_view.dart +++ b/lib/application/component/button/button_view.dart @@ -55,78 +55,80 @@ class ButtonView extends StatelessWidget { final widthPrefix = this.widthPrefix ?? (heightWrapContent ? width! / 4.7 : _widthBtn / 16); - return Container( - margin: EdgeInsets.symmetric(vertical: marginVertical ?? 32.0), - width: width ?? _widthBtn, - height: heightWrapContent ? null : height ?? _heightBtn, - child: ElevatedButton( - style: ElevatedButton.styleFrom( - disabledBackgroundColor: isOutlined ? Colors.white : color.surface, - padding: contentPadding, - backgroundColor: backgroundColor ?? - (isOutlined - ? Colors.white - : isSecondaryColor - ? ColorPalette.grey - : ColorPalette.primary), - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48), - side: isOutlined - ? BorderSide( - color: disabled - ? color.surface - : isSecondaryColor - ? ColorPalette.greyBorder - : ColorPalette.primary, - ) - : BorderSide.none, + return Center( + child: Container( + margin: EdgeInsets.symmetric(vertical: marginVertical ?? 24.0), + width: width ?? _widthBtn, + height: heightWrapContent ? null : height ?? _heightBtn, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + disabledBackgroundColor: isOutlined ? Colors.white : color.surface, + padding: contentPadding, + backgroundColor: backgroundColor ?? + (isOutlined + ? Colors.white + : isSecondaryColor + ? ColorPalette.grey + : ColorPalette.primary), + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(sizeBorderRadius ?? 48), + side: isOutlined + ? BorderSide( + color: disabled + ? ColorPalette.greyBorder + : isSecondaryColor + ? ColorPalette.greyBorder + : ColorPalette.primary, + ) + : BorderSide.none, + ), ), - ), - onPressed: disabled ? null : onPressed, - child: Row( - mainAxisAlignment: mainAxisAlignmentContent ?? - (prefixIcon != null - ? MainAxisAlignment.center - : suffixIcon != null - ? MainAxisAlignment.end - : MainAxisAlignment.center), - children: [ - if (prefixIcon != null) ...[ - prefixIcon!, - SizedBox(width: widthPrefix), - ] else - Container(), - Flexible( - child: Text( - name, - textAlign: TextAlign.center, - maxLines: maxLines, - overflow: TextOverflow.ellipsis, - style: TextStyle( - fontSize: textSize ?? 16, - fontWeight: textWeight, - color: textColor ?? - (disabled && isOutlined - ? color.primary - : disabled - ? Colors.white - : isOutlined && isSecondaryColor - ? ColorPalette.blackFont - : isOutlined - ? color.primary - : isSecondaryColor - ? Colors.white - : Colors.white), + onPressed: disabled ? null : onPressed, + child: Row( + mainAxisAlignment: mainAxisAlignmentContent ?? + (prefixIcon != null + ? MainAxisAlignment.center + : suffixIcon != null + ? MainAxisAlignment.end + : MainAxisAlignment.center), + children: [ + if (prefixIcon != null) ...[ + prefixIcon!, + SizedBox(width: widthPrefix), + ] else + Container(), + Flexible( + child: Text( + name, + textAlign: TextAlign.center, + maxLines: maxLines, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: textSize ?? 16, + fontWeight: textWeight, + color: textColor ?? + (disabled && isOutlined + ? color.primary + : disabled + ? Colors.white + : isOutlined && isSecondaryColor + ? ColorPalette.blackFont + : isOutlined + ? color.primary + : isSecondaryColor + ? Colors.white + : Colors.white), + ), ), ), - ), - if (suffixIcon != null) ...[ - SizedBox(width: widthSuffix), - suffixIcon! - ] else - Container() - ], + if (suffixIcon != null) ...[ + SizedBox(width: widthSuffix), + suffixIcon! + ] else + Container() + ], + ), ), ), ); diff --git a/lib/application/component/otp/otp_view.dart b/lib/application/component/otp/otp_view.dart new file mode 100644 index 0000000..1ab57b0 --- /dev/null +++ b/lib/application/component/otp/otp_view.dart @@ -0,0 +1,146 @@ +import 'package:cims_apps/application/component/otp/otp_viewmodel.dart'; +import 'package:cims_apps/application/component/text_caption/text_caption.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/route/route.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/auth/registration/view/registration_password_view.dart'; +import 'package:flutter/material.dart'; +import 'package:pinput/pinput.dart'; +import 'package:provider/provider.dart'; + +class OtpView extends StatelessWidget { + final String title; + final String? contentTitle, contentSubtitle; + const OtpView({ + Key? key, + required this.title, + this.contentTitle, + this.contentSubtitle, + }) : super(key: key); + + Widget _otpContent(BuildContext context, OtpViewModel provider) { + return Form( + key: provider.formKey, + child: Column( + children: [ + Pinput( + length: 4, + controller: provider.ctrlPin, + focusNode: provider.focusNode, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + validator: (value) { + if (value!.isEmpty) { + return 'Pin must be complete'; + } + return null; + }, + defaultPinTheme: PinTheme( + textStyle: const TextStyle( + color: ColorPalette.slate800, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + width: SizeConfig.width * .19, + height: SizeConfig.height * .08, + decoration: BoxDecoration( + border: Border.all(color: const Color(0xFFE2E8F0)), + borderRadius: BorderRadius.circular(8), + ), + ), + errorPinTheme: PinTheme( + textStyle: const TextStyle( + color: ColorPalette.slate800, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + width: SizeConfig.width * .19, + height: SizeConfig.height * .08, + decoration: BoxDecoration( + border: Border.all(color: Colors.redAccent), + borderRadius: BorderRadius.circular(8), + ), + ), + onCompleted: (pin) => provider.enableButton(), + onChanged: (value) { + if (provider.ctrlPin.length != 4) { + provider.enableButton(isActive: false); + } + }, + ), + Container( + margin: const EdgeInsets.symmetric(vertical: 32.0), + width: SizeConfig.width, + height: SizeConfig.height * .07, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + elevation: 0, + backgroundColor: ColorPalette.primary, + ), + onPressed: !provider.buttonIsActive + ? null + : () { + if (provider.formKey.currentState!.validate()) { + final pin = provider.ctrlPin.text; + provider.validateOtp(pin).then((value) { + if (value) { + routePush(context, + page: const RegistrationPasswordView(), + routeType: RouteType.pushReplace); + } else { + provider.ctrlPin.clear(); + } + }); + } + }, + child: const Text( + 'Verify', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + ), + ), + ), + ], + )); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => OtpViewModel(), + builder: (context, child) { + return Scaffold( + appBar: AppBar( + title: Text(title), + ), + body: Container( + padding: const EdgeInsets.all(16.0), + child: + Consumer(builder: (context, provider, child) { + return Column( + children: [ + TextCaption( + title: contentTitle ?? '', + subtitle: contentSubtitle ?? '', + ), + _otpContent(context, provider), + TextButton( + onPressed: () { + provider.ctrlPin.clear(); + }, + child: const Text( + 'Resend Code', + style: TextStyle( + fontWeight: FontWeight.w700, + ), + )) + ], + ); + }), + ), + ); + }); + } +} diff --git a/lib/application/component/otp/otp_viewmodel.dart b/lib/application/component/otp/otp_viewmodel.dart new file mode 100644 index 0000000..4aac81d --- /dev/null +++ b/lib/application/component/otp/otp_viewmodel.dart @@ -0,0 +1,22 @@ +import 'package:flutter/material.dart'; + +class OtpViewModel extends ChangeNotifier { + var formKey = GlobalKey(); + var focusNode = FocusNode(); + bool buttonIsActive = false; + + TextEditingController ctrlPin = TextEditingController(); + + Future validateOtp(String pin) async { + final pinLength = pin.length; + if (pinLength == 4) { + return true; + } + return false; + } + + void enableButton({bool isActive = true}) { + buttonIsActive = isActive; + notifyListeners(); + } +} diff --git a/lib/application/component/text_caption/text_caption.dart b/lib/application/component/text_caption/text_caption.dart index 8a5d57b..00ab5b6 100644 --- a/lib/application/component/text_caption/text_caption.dart +++ b/lib/application/component/text_caption/text_caption.dart @@ -3,10 +3,14 @@ import 'package:flutter/material.dart'; class TextCaption extends StatelessWidget { final String title, subtitle; + final TextAlign? textAlignSubtitle; + final CrossAxisAlignment? crossAxisAlignment; const TextCaption({ Key? key, required this.title, this.subtitle = '', + this.textAlignSubtitle, + this.crossAxisAlignment, }) : super(key: key); @override @@ -14,7 +18,7 @@ class TextCaption extends StatelessWidget { return Padding( padding: const EdgeInsets.only(bottom: 32.0), child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: crossAxisAlignment ?? CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( @@ -30,10 +34,11 @@ class TextCaption extends StatelessWidget { padding: const EdgeInsets.only(top: 8.0), child: Text( subtitle, + textAlign: textAlignSubtitle ?? TextAlign.start, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: ColorPalette.slate800, + color: ColorPalette.slate500, ), ), ) diff --git a/lib/application/component/text_form/text_form_view.dart b/lib/application/component/text_form/text_form_view.dart index 8540251..8617893 100644 --- a/lib/application/component/text_form/text_form_view.dart +++ b/lib/application/component/text_form/text_form_view.dart @@ -14,7 +14,7 @@ class TextFormView extends StatelessWidget { final String? hintText, errorText; final TextEditingController? ctrl; final Widget? suffixIcon, suffixLable; - final Widget? prefixIcon; + final Widget? prefixIcon, prefix; final TextInputType? keyboardType; final FormFieldValidator? validator; final bool obscureText; @@ -70,7 +70,8 @@ class TextFormView extends StatelessWidget { this.disableColor = false, this.enableInteractiveSelection = true, this.focusNode, - this.isTextAlignCenter = false}) + this.isTextAlignCenter = false, + this.prefix}) : super(key: key); @override @@ -94,12 +95,12 @@ class TextFormView extends StatelessWidget { name, style: const TextStyle( fontSize: 16, - color: ColorPalette.greyLight, + // color: ColorPalette.greyLight, ), ), suffixLable ?? const Text( - " * ", + "", style: TextStyle( fontSize: 16, color: Colors.red, @@ -148,46 +149,47 @@ class TextFormView extends StatelessWidget { enableInteractiveSelection: enableInteractiveSelection, textAlign: isTextAlignCenter ? TextAlign.center : TextAlign.left, decoration: InputDecoration( - helperText: helperText, - errorStyle: errorStyle, - errorText: errorText, - errorMaxLines: 2, - hintStyle: hintTextStyle ?? - const TextStyle( - fontSize: 14, - color: ColorPalette.greyFont, - fontWeight: FontWeight.normal, + helperText: helperText, + errorStyle: errorStyle, + errorText: errorText, + errorMaxLines: 2, + hintStyle: hintTextStyle ?? + const TextStyle( + fontSize: 14, + color: ColorPalette.greyFont, + fontWeight: FontWeight.normal, + ), + isDense: true, + hintText: hintText, + filled: true, + fillColor: enabled && disableColor == false + ? Colors.white + : const Color.fromARGB(255, 233, 236, 239), + disabledBorder: OutlineInputBorder( + borderRadius: _borderRadius, + borderSide: BorderSide( + color: disabledborderColor ?? ColorPalette.greyBorder, ), - isDense: true, - hintText: hintText, - filled: true, - fillColor: enabled && disableColor == false - ? Colors.white - : const Color.fromARGB(255, 233, 236, 239), - disabledBorder: OutlineInputBorder( - borderRadius: _borderRadius, - borderSide: BorderSide( - color: disabledborderColor ?? ColorPalette.greyBorder, ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: _borderRadius, - borderSide: BorderSide( - color: enabledborderColor ?? ColorPalette.greyBorder, + enabledBorder: OutlineInputBorder( + borderRadius: _borderRadius, + borderSide: BorderSide( + color: enabledborderColor ?? ColorPalette.greyBorder, + ), ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: _borderRadius, - borderSide: BorderSide( - color: focusedBorderColor ?? ColorPalette.greyBorder, + focusedBorder: OutlineInputBorder( + borderRadius: _borderRadius, + borderSide: BorderSide( + color: focusedBorderColor ?? ColorPalette.greyBorder, + ), ), - ), - border: OutlineInputBorder(borderRadius: _borderRadius), - suffixIcon: suffixIcon, - prefixIcon: prefixIcon, - suffixIconConstraints: suffixIconConstraints, - prefixIconConstraints: preffixIconConstraints, - ), + border: OutlineInputBorder(borderRadius: _borderRadius), + suffixIcon: suffixIcon, + prefixIcon: prefixIcon, + suffixIconConstraints: suffixIconConstraints, + prefixIconConstraints: preffixIconConstraints, + prefix: prefix, + contentPadding: EdgeInsets.zero), ) ], ); diff --git a/lib/application/theme/color_palette.dart b/lib/application/theme/color_palette.dart index 724bd85..90ea85b 100644 --- a/lib/application/theme/color_palette.dart +++ b/lib/application/theme/color_palette.dart @@ -73,5 +73,6 @@ class ColorPalette { static const Color chathamsBlue = Color(0xFF285BB9); static const Color background = Color(0xFFDADADA); static const Color backgroundBlueLight = Color(0xFFEBF3FD); + static const Color slate500 = Color(0xFF64748B); static const Color slate800 = Color(0xFF1E293B); } diff --git a/lib/core/utils/string_utils.dart b/lib/core/utils/string_utils.dart new file mode 100644 index 0000000..307fb19 --- /dev/null +++ b/lib/core/utils/string_utils.dart @@ -0,0 +1,16 @@ +class StringUtils { + static bool emailValidation(String email) { + return RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') + .hasMatch(email); + } + + static bool phoneValidation(String phone) { + return RegExp(r'^(\+62|62|0)8[1-9][0-9]{6,10}$').hasMatch(phone); + } + + static bool passwordValidation(String password) { + return RegExp(r'^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*?[\W_])(?=.{8,})') + .hasMatch(password); + } +} diff --git a/lib/features/auth/registration/view/registration_password_view.dart b/lib/features/auth/registration/view/registration_password_view.dart new file mode 100644 index 0000000..4210406 --- /dev/null +++ b/lib/features/auth/registration/view/registration_password_view.dart @@ -0,0 +1,151 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; +import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/component/text_caption/text_caption.dart'; +import 'package:cims_apps/application/component/text_form/text_form_view.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/route/route.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/auth/registration/view/submission_data/submission_parent.dart'; +import 'package:cims_apps/features/auth/registration/viewmodel/registration_viewmodel.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class RegistrationPasswordView extends StatelessWidget { + const RegistrationPasswordView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider( + create: (context) => RegistrationViewModel(), + builder: (context, child) { + return Scaffold( + appBar: AppBar( + title: const Text('Sign Up'), + ), + body: SingleChildScrollView( + padding: const EdgeInsets.all(16.0), + child: Consumer( + builder: (context, provider, child) { + return Form( + key: provider.formKey, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const TextCaption( + title: 'Create your password', + subtitle: + 'The password you create serves as your login', + ), + TextFormView( + name: 'Password', + hintText: 'Input password', + ctrl: provider.passwordCtrl, + obscureText: !provider.showPassword, + validator: (value) { + if (value!.isEmpty) { + return 'Password must filled'; + } else { + return null; + } + }, + suffixIcon: GestureDetector( + onTap: () { + provider.toggleVisibility(); + }, + child: Icon( + provider.showPassword + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: ColorPalette.greyDarker, + ), + ), + ), + const SizedBox( + height: 32.0, + ), + TextFormView( + name: 'Confirm Password', + hintText: 'Input password', + ctrl: provider.confirmPasswordCtrl, + obscureText: !provider.showPasswordConfirm, + validator: (value) { + if (value!.isEmpty) { + return 'Password must filled'; + } else if (value != provider.passwordCtrl.text) { + return 'Password must be same'; + } else { + return null; + } + }, + suffixIcon: GestureDetector( + onTap: () { + provider.toggleVisibilityConfirm(); + }, + child: Icon( + provider.showPasswordConfirm + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + color: ColorPalette.greyDarker, + ), + ), + ), + ButtonView( + name: 'Confirm', + onPressed: () { + if (provider.formKey.currentState!.validate()) { + routePush(context, page: const DialogSuccess()); + } + }, + ) + ], + ), + ); + }), + ), + ); + }); + } +} + +class DialogSuccess extends StatelessWidget { + const DialogSuccess({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + padding: const EdgeInsets.all(24.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + ImageView( + image: PathAssets.imgSuccessSignup, + width: SizeConfig.width * .8, + ), + const TextCaption( + title: 'Success', + subtitle: + 'Congratulations, your account creation was successful!', + crossAxisAlignment: CrossAxisAlignment.center, + textAlignSubtitle: TextAlign.center, + ), + SizedBox( + height: SizeConfig.height * .2, + ), + ButtonView( + name: 'Next', + marginVertical: 8.0, + onPressed: () { + routePush(context, + page: const SubmissionParent(), + routeType: RouteType.pushReplace); + }, + ) + ], + ), + ), + ); + } +} diff --git a/lib/features/auth/registration/view/registration_view.dart b/lib/features/auth/registration/view/registration_view.dart index b98fb40..04cdf85 100644 --- a/lib/features/auth/registration/view/registration_view.dart +++ b/lib/features/auth/registration/view/registration_view.dart @@ -1,10 +1,16 @@ +import 'package:cims_apps/application/assets/path_assets.dart'; import 'package:cims_apps/application/component/button/button_view.dart'; +import 'package:cims_apps/application/component/image/image_view.dart'; +import 'package:cims_apps/application/component/otp/otp_view.dart'; import 'package:cims_apps/application/component/text_caption/text_caption.dart'; import 'package:cims_apps/application/component/text_form/text_form_view.dart'; -import 'package:cims_apps/core/route/route.dart'; -import 'package:cims_apps/features/auth/registration/view/initial_registration_step.dart'; +import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/utils/size_config.dart'; +import 'package:cims_apps/features/auth/registration/viewmodel/registration_viewmodel.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; class RegistrationView extends StatelessWidget { static const routName = '/RegistrationView'; @@ -12,54 +18,130 @@ class RegistrationView extends StatelessWidget { @override Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Sign Up'), - ), - body: Container( - padding: const EdgeInsets.all(24.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const TextCaption( - title: 'Enter your phone number', - subtitle: 'Input your registered phone number', + showOtpWidget() { + Navigator.of(context).pop(); + showModalBottomSheet( + context: context, + isScrollControlled: true, + enableDrag: false, + builder: (BuildContext context) { + return Padding( + padding: EdgeInsets.only( + top: MediaQueryData.fromView( + WidgetsBinding.instance.window, + ).padding.top, ), - TextFormView(name: 'Phone Number'), - ButtonView( - name: 'Next', - onPressed: () { - routePush(context, page: const InitialRegistrationStep()); - }, + child: const OtpView( + title: 'Sign Up', + contentTitle: 'Check your SMS', + contentSubtitle: + 'Enter 4 digit code We’ve sent to verify your phone number', ), - Align( - alignment: Alignment.center, - child: RichText( - textAlign: TextAlign.center, - text: TextSpan(children: [ - const TextSpan( - text: 'Already have an account? ', - style: TextStyle( - color: Colors.black, - decoration: TextDecoration.none, - ), + ); + }, + ); + } + + return ChangeNotifierProvider( + create: (context) => RegistrationViewModel(), + builder: (context, child) { + return Scaffold( + appBar: AppBar( + title: const Text('Sign Up'), + ), + body: Container( + padding: const EdgeInsets.all(24.0), + child: Consumer( + builder: (context, provider, child) { + return Form( + key: provider.formKeyPhone, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const TextCaption( + title: 'Enter your phone number', + subtitle: 'Input your registered phone number', + ), + TextFormView( + name: 'Phone Number', + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter.deny(RegExp(r'^0')) + ], + prefixIcon: Container( + width: SizeConfig.width * .23, + padding: + const EdgeInsets.symmetric(horizontal: 16.0), + margin: const EdgeInsets.only(right: 16), + decoration: const BoxDecoration( + color: ColorPalette.grey, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + bottomLeft: Radius.circular(8), + )), + child: const Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ImageView( + image: PathAssets.iconFlag, + fit: BoxFit.contain, + width: 24, + height: 24, + ), + Text( + '+62', + style: TextStyle( + fontWeight: FontWeight.w600, + color: ColorPalette.slate800, + ), + ) + ], + )), + ctrl: provider.phoneNumberCtrl, + validator: (value) { + if (value!.isEmpty) { + return 'Phone number must be filled'; + } else { + return null; + } + }, + ), + ButtonView( + name: 'Next', + onPressed: () { + if (provider.formKeyPhone.currentState!.validate()) { + showOtpWidget(); + } + }, + ), + Align( + alignment: Alignment.center, + child: RichText( + textAlign: TextAlign.center, + text: TextSpan(children: [ + const TextSpan( + text: 'Already have an account? ', + style: TextStyle( + color: Colors.black, + decoration: TextDecoration.none, + ), + ), + TextSpan( + recognizer: TapGestureRecognizer()..onTap = () {}, + text: ' Sign In', + style: const TextStyle( + color: Colors.blue, + ), + ), + ]), + ), + ) + ], ), - TextSpan( - recognizer: TapGestureRecognizer() - ..onTap = () { - print('object'); - }, - text: ' Sign In', - style: const TextStyle( - color: Colors.blue, - ), - ), - ]), - ), - ) - ], - ), - ), - ); + ); + }), + ), + ); + }); } } diff --git a/lib/features/auth/registration/view/submission_data/submission_parent.dart b/lib/features/auth/registration/view/submission_data/submission_parent.dart index 6c962f4..087e8dc 100644 --- a/lib/features/auth/registration/view/submission_data/submission_parent.dart +++ b/lib/features/auth/registration/view/submission_data/submission_parent.dart @@ -1,10 +1,12 @@ import 'package:cims_apps/application/component/button/button_view.dart'; import 'package:cims_apps/application/theme/color_palette.dart'; +import 'package:cims_apps/core/route/route.dart'; import 'package:cims_apps/core/utils/size_config.dart'; import 'package:cims_apps/features/auth/registration/view/submission_data/initial_take_photo.dart'; import 'package:cims_apps/features/auth/registration/view/submission_data/submit_email.dart'; import 'package:cims_apps/features/auth/registration/view/submission_data/submit_personal_data.dart'; import 'package:cims_apps/features/auth/registration/viewmodel/submission_data_viewmodel.dart'; +import 'package:cims_apps/features/bottom_navigation_view.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -71,53 +73,63 @@ class _SubmissionParentState extends State { return ChangeNotifierProvider( create: (context) => SubmissionDataViewModel(), builder: (context, child) { - return Scaffold( - appBar: AppBar( - title: const Text('Registration'), - ), - body: Stack( - children: [ - Consumer( - builder: (context, provider, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 16.0, vertical: 16.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: List.generate( - provider.stepAmount, - (index) => _stepItem( - isCurrentStep: provider.currentStep == index + 1, + return WillPopScope( + onWillPop: () async { + await routePush(context, + page: const BottomNavigationView(), + routeType: RouteType.pushReplace); + return false; + }, + child: Scaffold( + appBar: AppBar( + title: const Text('Registration'), + ), + body: Stack( + children: [ + Consumer( + builder: (context, provider, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 16.0, vertical: 16.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate( + provider.stepAmount, + (index) => _stepItem( + isCurrentStep: + provider.currentStep == index + 1, + ), ), ), ), - ), - Expanded( - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: _content(provider.currentStep), + Expanded( + child: Container( + padding: + const EdgeInsets.symmetric(horizontal: 16.0), + child: _content(provider.currentStep), + ), ), - ), - provider.currentStep == 3 - ? const SizedBox() - : Align( - alignment: Alignment.bottomCenter, - child: ButtonView( - name: 'Next', - marginVertical: 16.0, - onPressed: () { - provider.nextSubmission(context); - }, - ), - ) - ], - ); - }), - ], + provider.currentStep == 3 + ? const SizedBox() + : Align( + alignment: Alignment.bottomCenter, + child: ButtonView( + name: 'Next', + marginVertical: 16.0, + onPressed: () { + provider.nextSubmission(context); + }, + ), + ) + ], + ); + }), + ], + ), ), ); }); diff --git a/lib/features/auth/registration/viewmodel/registration_viewmodel.dart b/lib/features/auth/registration/viewmodel/registration_viewmodel.dart new file mode 100644 index 0000000..55a8f7f --- /dev/null +++ b/lib/features/auth/registration/viewmodel/registration_viewmodel.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class RegistrationViewModel extends ChangeNotifier { + TextEditingController passwordCtrl = TextEditingController(); + TextEditingController confirmPasswordCtrl = TextEditingController(); + TextEditingController phoneNumberCtrl = TextEditingController(); + var formKey = GlobalKey(); + var formKeyPhone = GlobalKey(); + bool showPassword = false; + bool showPasswordConfirm = false; + + void toggleVisibility() { + showPassword = !showPassword; + notifyListeners(); + } + + void toggleVisibilityConfirm() { + showPasswordConfirm = !showPasswordConfirm; + notifyListeners(); + } +} diff --git a/pubspec.lock b/pubspec.lock index ae75197..2ab6860 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -163,6 +163,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: transitive description: @@ -323,6 +328,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + pinput: + dependency: "direct main" + description: + name: pinput + sha256: "543da5bfdefd9e06914a12100f8c9156f84cef3efc14bca507c49e966c5b813b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" platform: dependency: transitive description: @@ -376,6 +389,14 @@ packages: description: flutter source: sdk version: "0.0.99" + smart_auth: + dependency: transitive + description: + name: smart_auth + sha256: a25229b38c02f733d0a4e98d941b42bed91a976cb589e934895e60ccfa674cf6 + url: "https://pub.dev" + source: hosted + version: "1.1.1" source_span: dependency: transitive description: @@ -464,6 +485,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + universal_platform: + dependency: transitive + description: + name: universal_platform + sha256: d315be0f6641898b280ffa34e2ddb14f3d12b1a37882557869646e0cc363d0cc + url: "https://pub.dev" + source: hosted + version: "1.0.0+1" uuid: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 39356dc..2864079 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -40,6 +40,8 @@ dependencies: cached_network_image: ^3.2.3 remove_emoji_input_formatter: ^0.0.1+1 provider: ^6.1.1 + pinput: ^2.2.21 +