From 8be05fbd918478d6613a69672fdcf869ca00cf1b Mon Sep 17 00:00:00 2001 From: mohammad Date: Mon, 30 Dec 2024 16:51:35 +0300 Subject: [PATCH] change_password --- assets/icons/password_unvisibility.svg | 3 + assets/icons/password_visibility.svg | 3 + assets/icons/verification_icon.svg | 10 + lib/features/auth/bloc/auth_cubit.dart | 1 + lib/features/auth/model/user_model.dart | 9 +- .../widgets/securty/bloc/security_bloc.dart | 207 +++++++++++++++ .../widgets/securty/bloc/security_event.dart | 31 +++ .../widgets/securty/bloc/security_state.dart | 44 ++++ .../securty/view/change_password_page.dart | 88 +++++++ .../securty/{ => view}/securty_view.dart | 7 +- .../securty/view/set_password_page.dart | 239 ++++++++++++++++++ .../securty/view/verification_code_page.dart | 181 +++++++++++++ lib/generated/assets.dart | 7 + lib/services/api/profile_api.dart | 60 +++-- lib/utils/resource_manager/color_manager.dart | 5 +- lib/utils/resource_manager/constants.dart | 2 +- 16 files changed, 864 insertions(+), 33 deletions(-) create mode 100644 assets/icons/password_unvisibility.svg create mode 100644 assets/icons/password_visibility.svg create mode 100644 assets/icons/verification_icon.svg create mode 100644 lib/features/menu/view/widgets/securty/bloc/security_bloc.dart create mode 100644 lib/features/menu/view/widgets/securty/bloc/security_event.dart create mode 100644 lib/features/menu/view/widgets/securty/bloc/security_state.dart create mode 100644 lib/features/menu/view/widgets/securty/view/change_password_page.dart rename lib/features/menu/view/widgets/securty/{ => view}/securty_view.dart (95%) create mode 100644 lib/features/menu/view/widgets/securty/view/set_password_page.dart create mode 100644 lib/features/menu/view/widgets/securty/view/verification_code_page.dart diff --git a/assets/icons/password_unvisibility.svg b/assets/icons/password_unvisibility.svg new file mode 100644 index 0000000..c9d8a2d --- /dev/null +++ b/assets/icons/password_unvisibility.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/password_visibility.svg b/assets/icons/password_visibility.svg new file mode 100644 index 0000000..25c5543 --- /dev/null +++ b/assets/icons/password_visibility.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/icons/verification_icon.svg b/assets/icons/verification_icon.svg new file mode 100644 index 0000000..949b9a2 --- /dev/null +++ b/assets/icons/verification_icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/lib/features/auth/bloc/auth_cubit.dart b/lib/features/auth/bloc/auth_cubit.dart index f27cbf6..099c827 100644 --- a/lib/features/auth/bloc/auth_cubit.dart +++ b/lib/features/auth/bloc/auth_cubit.dart @@ -204,6 +204,7 @@ class AuthCubit extends Cubit { const FlutterSecureStorage().write( key: UserModel.userUuidKey, value: Token.decodeToken(token.accessToken)['uuid'].toString()); + user = UserModel.fromToken(token); emailController.clear(); passwordController.clear(); diff --git a/lib/features/auth/model/user_model.dart b/lib/features/auth/model/user_model.dart index fd68fb4..00dad95 100644 --- a/lib/features/auth/model/user_model.dart +++ b/lib/features/auth/model/user_model.dart @@ -14,6 +14,7 @@ class UserModel { final bool? isEmailVerified; final String? regionName; final String? timeZone; + final String? regionUuid; final bool? isAgreementAccepted; UserModel({ @@ -24,10 +25,10 @@ class UserModel { required this.profilePicture, required this.phoneNumber, required this.isEmailVerified, + required this.regionUuid, required this.isAgreementAccepted, required this.regionName, // Add this line required this.timeZone, // Add this line - }); factory UserModel.fromJson(Map json) { @@ -42,21 +43,23 @@ class UserModel { isAgreementAccepted: json['isAgreementAccepted'], regionName: json['region']?['regionName'], // Extract regionName timeZone: json['timeZone']?['timeZoneOffset'], // Extract regionName + regionUuid: json['region']?['uuid'], ); } //uuid to json //from token - factory UserModel.fromToken(Token token) { + factory UserModel.fromToken(Token token) { Map tempJson = Token.decodeToken(token.accessToken); return UserModel( uuid: tempJson['uuid'].toString(), email: tempJson['email'], lastName: tempJson['lastName'], - firstName:tempJson['firstName'] , + firstName: tempJson['firstName'], profilePicture: UserModel.decodeBase64Image(tempJson['profilePicture']), phoneNumber: null, isEmailVerified: null, isAgreementAccepted: null, + regionUuid: null, regionName: tempJson['region']?['regionName'], timeZone: tempJson['timezone']?['timeZoneOffset'], ); diff --git a/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart b/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart new file mode 100644 index 0000000..33995ac --- /dev/null +++ b/lib/features/menu/view/widgets/securty/bloc/security_bloc.dart @@ -0,0 +1,207 @@ +import 'dart:async'; + +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart'; +import 'package:syncrow_app/services/api/authentication_api.dart'; + +class SecurityBloc extends Bloc { + bool _isPasswordVisible = false; + String otpCode = ''; + String validate = ''; + SecurityBloc() : super(PasswordVisibilityState(false)) { + on(_onSetPassword); + on(_onTogglePasswordVisibility); + on(_onStartTimer); + on(_onStopTimer); + on(_onUpdateTimer); + on(verifyCode); + on(changePassword); + } + + void _onSetPassword(SetPassword event, Emitter emit) { + if (event.password.length < 6) { + emit(PasswordErrorState('Password must be at least 6 characters long.')); + } else { + emit(PasswordSetState('Password successfully set.')); + } + } + + void _onTogglePasswordVisibility( + TogglePasswordVisibility event, Emitter emit) { + _isPasswordVisible = !_isPasswordVisible; + emit(PasswordVisibilityState(_isPasswordVisible)); + } + + String? passwordValidator(String? value) { + if (value == null || value.isEmpty) { + return 'Please enter your password'; + } + List validationErrors = []; + + if (!RegExp(r'^(?=.*[a-z])').hasMatch(value)) { + validationErrors.add(' - one lowercase letter'); + } + if (!RegExp(r'^(?=.*[A-Z])').hasMatch(value)) { + validationErrors.add(' - one uppercase letter'); + } + if (!RegExp(r'^(?=.*\d)').hasMatch(value)) { + validationErrors.add(' - one number'); + } + if (!RegExp(r'^(?=.*[@$!%*?&])').hasMatch(value)) { + validationErrors.add(' - one special character'); + } + if (value.length < 8) { + validationErrors.add(' - minimum 8 characters'); + } + + if (validationErrors.isNotEmpty) { + return 'Password must contain at least:\n${validationErrors.join('\n')}'; + } + + return null; + } + + TextEditingController newPassword = TextEditingController(); + + Timer? _timer; + int _remainingTime = 0; + + Future _onStartTimer( + StartTimerEvent event, Emitter emit) async { + if (_timer != null && _timer!.isActive) { + return; + } + _remainingTime = 1; + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); + try { + _remainingTime = 30; + var res = (await AuthenticationAPI.sendOtp(body: { + 'email': HomeCubit.user!.email, + 'type': 'VERIFICATION', + if (HomeCubit.user!.regionUuid != null) + 'regionUuid': HomeCubit.user!.regionUuid + })); + _remainingTime = res['cooldown']; + } on DioException catch (e) { + if (e.response!.statusCode == 400) { + final errorData = e.response!.data; + String errorMessage = errorData['message']; + if (errorMessage == 'User not found') { + validate = 'Invalid Credential'; + emit(AuthInitialState()); + return 1; + } else { + validate = ''; + _remainingTime = errorData['data']['cooldown'] ?? 1; + emit(AuthInitialState()); + } + } else { + emit(AuthInitialState()); + + return 1; + } + emit(AuthInitialState()); + } catch (e) { + emit(AuthInitialState()); + + return 1; + } + + _timer = Timer.periodic(const Duration(seconds: 1), (timer) { + _remainingTime--; + if (_remainingTime <= 0) { + _timer?.cancel(); + add(UpdateTimerEvent(remainingTime: 0, isButtonEnabled: true)); + } else { + add(UpdateTimerEvent( + remainingTime: _remainingTime, isButtonEnabled: false)); + } + }); + } + + void _onStopTimer(StopTimerEvent event, Emitter emit) { + _timer?.cancel(); + emit(TimerState(isButtonEnabled: true, remainingTime: 0)); + } + + void _onUpdateTimer(UpdateTimerEvent event, Emitter emit) { + emit(TimerState( + isButtonEnabled: event.isButtonEnabled, + remainingTime: event.remainingTime)); + } + + String formattedTime(int time) { + final int days = (time / 86400).floor(); // 86400 seconds in a day + final int hours = ((time % 86400) / 3600).floor(); + final int minutes = (((time % 86400) % 3600) / 60).floor(); + final int seconds = (((time % 86400) % 3600) % 60).floor(); + + final String formattedTime = [ + if (days > 0) '${days}d', // Append 'd' for days + if (days > 0 || hours > 0) + hours + .toString() + .padLeft(2, '0'), // Show hours if there are days or hours + minutes.toString().padLeft(2, '0'), + seconds.toString().padLeft(2, '0'), + ].join(':'); + + return formattedTime; + } + + Future verifyCode( + VerifyPassCodeEvent event, Emitter emit) async { + emit(LoadingForgetState()); + try { + final response = await AuthenticationAPI.verifyPassCode(body: { + 'email': HomeCubit.user!.email!, + 'type': 'VERIFICATION', + 'otpCode': otpCode + }); + //929971 + print('response=-=-=-${response}'); + if (response['statusCode'] == 200) { + _timer?.cancel(); + emit(SuccessForgetState()); + } + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = + errorData['error']['message'] ?? 'something went wrong'; + validate = errorMessage; + emit(AuthInitialState()); + } + } + + Future changePassword( + ChangePasswordEvent event, Emitter emit) async { + emit(LoadingForgetState()); + try { + print('response=-=-=-${event.otpCode}'); + + final response = await AuthenticationAPI.forgetPassword( + email: HomeCubit.user!.email!, + otpCode: event.otpCode, + password: newPassword.text, + ); + //537580 + print('response=-=-=-${response}'); + // if (response['statusCode'] == 200) { + // _timer?.cancel(); + // emit(ChangedPassState()); + // } + emit(ChangedPassState()); + } on DioException catch (e) { + final errorData = e.response!.data; + String errorMessage = + errorData['error']['message'] ?? 'something went wrong'; + validate = errorMessage; + emit(AuthInitialState()); + } + } +} diff --git a/lib/features/menu/view/widgets/securty/bloc/security_event.dart b/lib/features/menu/view/widgets/securty/bloc/security_event.dart new file mode 100644 index 0000000..352192c --- /dev/null +++ b/lib/features/menu/view/widgets/securty/bloc/security_event.dart @@ -0,0 +1,31 @@ +abstract class SecurityEvent {} + +class SetPassword extends SecurityEvent { + final String password; + + SetPassword(this.password); +} + +class TogglePasswordVisibility extends SecurityEvent {} + +class SubmitEvent extends SecurityEvent {} + +class StartTimerEvent extends SecurityEvent {} + +class StopTimerEvent extends SecurityEvent {} + +class UpdateTimerEvent extends SecurityEvent { + final int remainingTime; + final bool isButtonEnabled; + UpdateTimerEvent( + {required this.remainingTime, required this.isButtonEnabled}); +} + +class ChangePasswordEvent extends SecurityEvent { + final String otpCode; + ChangePasswordEvent({ + required this.otpCode, + }); +} + +class VerifyPassCodeEvent extends SecurityEvent {} diff --git a/lib/features/menu/view/widgets/securty/bloc/security_state.dart b/lib/features/menu/view/widgets/securty/bloc/security_state.dart new file mode 100644 index 0000000..c0b95bc --- /dev/null +++ b/lib/features/menu/view/widgets/securty/bloc/security_state.dart @@ -0,0 +1,44 @@ +abstract class SecurityState {} + +class InitialState extends SecurityState {} + +class PasswordVisibilityState extends SecurityState { + final bool isVisible; + + PasswordVisibilityState(this.isVisible); +} + +class PasswordSetState extends SecurityState { + final String message; + + PasswordSetState(this.message); +} + +class PasswordErrorState extends SecurityState { + final String error; + + PasswordErrorState(this.error); +} + +class AuthTokenLoading extends SecurityState {} + +class AuthLoading extends SecurityState {} + +class AuthInitialState extends SecurityState {} + +class TimerState extends SecurityState { + final bool isButtonEnabled; + final int remainingTime; + + TimerState({required this.isButtonEnabled, required this.remainingTime}); + + @override + List get props => [isButtonEnabled, remainingTime]; +} +class InitialForgetState extends SecurityState {} + +class LoadingForgetState extends SecurityState {} + +class SuccessForgetState extends SecurityState {} +class ChangedPassState extends SecurityState {} + diff --git a/lib/features/menu/view/widgets/securty/view/change_password_page.dart b/lib/features/menu/view/widgets/securty/view/change_password_page.dart new file mode 100644 index 0000000..75ef741 --- /dev/null +++ b/lib/features/menu/view/widgets/securty/view/change_password_page.dart @@ -0,0 +1,88 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/view/verification_code_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class ChangePasswordPage extends StatelessWidget { + const ChangePasswordPage({super.key}); + + @override + Widget build(BuildContext context) { + return DefaultScaffold( + title: 'Change Password', + bottomNavBar: SizedBox( + height: 150, + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + InkWell( + onTap: () { + // In your parent widget or navigator + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => BlocProvider( + create: (_) => SecurityBloc(), // Provide the Bloc + child: const VerificationCodePage(), + ), + ), + ); + }, + child: Container( + height: 50, + margin: const EdgeInsets.only(right: 20, left: 20), + decoration: const BoxDecoration( + color: ColorsManager.blueColor, + borderRadius: BorderRadius.all(Radius.circular(20))), + child: const Center( + child: Text( + 'Get Verification Code', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: ColorsManager.onPrimaryColor), + )), + ), + ), + ], + ), + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only(top: 80, bottom: 30), + child: SvgPicture.asset(Assets.verificationIcon), + ), + const Padding( + padding: EdgeInsets.all(8.0), + child: BodyMedium( + text: 'Account Verification', + fontWeight: FontWeight.w700, + fontSize: 18, + ), + ), + const BodyMedium( + text: 'Click here to send a verification ', + fontWeight: FontWeight.w400, + fontSize: 16, + ), + const SizedBox( + height: 4, + ), + const BodyMedium( + text: 'code to your email: test@test.com', + fontWeight: FontWeight.w400, + fontSize: 16, + ), + ], + ), + ); + } +} diff --git a/lib/features/menu/view/widgets/securty/securty_view.dart b/lib/features/menu/view/widgets/securty/view/securty_view.dart similarity index 95% rename from lib/features/menu/view/widgets/securty/securty_view.dart rename to lib/features/menu/view/widgets/securty/view/securty_view.dart index 3923ed3..abfb7f8 100644 --- a/lib/features/menu/view/widgets/securty/securty_view.dart +++ b/lib/features/menu/view/widgets/securty/view/securty_view.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/view/change_password_page.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; @@ -23,7 +24,11 @@ class SecurtyView extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, children: [ InkWell( - onTap: () {}, + onTap: () { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => const ChangePasswordPage(), + )); + }, child: const Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/features/menu/view/widgets/securty/view/set_password_page.dart b/lib/features/menu/view/widgets/securty/view/set_password_page.dart new file mode 100644 index 0000000..c330fe9 --- /dev/null +++ b/lib/features/menu/view/widgets/securty/view/set_password_page.dart @@ -0,0 +1,239 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_app/features/auth/bloc/auth_cubit.dart'; +import 'package:syncrow_app/features/devices/view/device_settings/update_dialog.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/generated/assets.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class SetPasswordPage extends StatelessWidget { + String? otpCode; + SetPasswordPage({super.key, this.otpCode}); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => SecurityBloc(), + child: BlocConsumer( + listener: (context, state) { + if (state is SuccessForgetState) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Change Password Successfully '), + ), + ); + Navigator.of(context).pop(); + } + if (state is ChangedPassState) { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: SizedBox( + height: MediaQuery.of(context).size.height * 0.2, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const SizedBox( + height: 10, + ), + BodyLarge( + text: 'Password Changed', + fontWeight: FontWeight.w700, + fontColor: + ColorsManager.switchButton.withOpacity(0.6), + fontSize: 16, + ), + const Padding( + padding: EdgeInsets.only(left: 15, right: 15), + child: Divider( + color: ColorsManager.textGray, + ), + ), + const Padding( + padding: EdgeInsets.only( + left: 15, right: 20, top: 15, bottom: 20), + child: Column( + children: [ + Center( + child: Text( + 'Your password has been', + textAlign: TextAlign.center, + )), + Center( + child: Text( + 'successfully updated.', + textAlign: TextAlign.center, + )), + ], + ), + ), + Container( + decoration: const BoxDecoration( + border: Border( + left: BorderSide( + color: ColorsManager.textGray, + width: 0.5, + ), + top: BorderSide( + color: ColorsManager.textGray, + width: 1.0, + ), + )), + child: InkWell( + onTap: () { + AuthCubit.get(context).logout(); + }, + child: Padding( + padding: + const EdgeInsets.only(top: 15, bottom: 15), + child: Center( + child: Text( + 'Done', + style: TextStyle( + color: ColorsManager.switchButton + .withOpacity(0.6), + fontSize: 14, + fontWeight: FontWeight.w400), + ), + ), + )), + ) + ], + ), + ), + ); + }, + ); + } + }, builder: (context, state) { + final _bloc = BlocProvider.of(context); + + return DefaultScaffold( + title: 'Change Password', + child: ListView( + shrinkWrap: true, + children: [ + const SizedBox(height: 55), + const Center( + child: Padding( + padding: EdgeInsets.all(8.0), + child: BodyMedium( + text: 'Set Password', + fontWeight: FontWeight.w700, + fontSize: 18, + ), + ), + ), + const Center( + child: BodyMedium( + text: 'Secure your account with a', + fontWeight: FontWeight.w400, + fontSize: 16, + ), + ), + const SizedBox(height: 4), + const Center( + child: BodyMedium( + text: 'strong password', + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const SizedBox(height: 40), + BlocBuilder( + builder: (context, state) { + if (state is PasswordErrorState) { + return Center( + child: Text( + state.error, + style: const TextStyle(color: Colors.red), + ), + ); + } else if (state is PasswordSetState) { + return Center( + child: Text( + state.message, + style: const TextStyle(color: Colors.green), + ), + ); + } + return const SizedBox.shrink(); + }, + ), + PasswordInputField(controller: _bloc.newPassword), + const SizedBox(height: 55), + InkWell( + onTap: () { + _bloc.add(ChangePasswordEvent(otpCode: otpCode.toString())); + }, + child: Container( + padding: const EdgeInsets.only( + right: 20, left: 20, top: 15, bottom: 15), + decoration: const BoxDecoration( + color: ColorsManager.blueColor, + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + child: const Center( + child: Text( + "Done", + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w700, + ), + ), + ), + ), + ) + ], + ), + ); + })); + } +} + +class PasswordInputField extends StatelessWidget { + PasswordInputField({this.controller}); + TextEditingController? controller = TextEditingController(); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + bool isPasswordVisible = false; + if (state is PasswordVisibilityState) { + isPasswordVisible = state.isVisible; + } + + return TextField( + controller: controller, + obscureText: !isPasswordVisible, + decoration: InputDecoration( + hintText: 'Password', + suffixIcon: IconButton( + icon: SvgPicture.asset(isPasswordVisible + ? Assets.passwordVisibility + : Assets.passwordUnvisibility), + onPressed: () { + context.read().add(TogglePasswordVisibility()); + }, + ), + ), + ); + }, + ); + } + + void dispose() { + controller!.dispose(); + } +} diff --git a/lib/features/menu/view/widgets/securty/view/verification_code_page.dart b/lib/features/menu/view/widgets/securty/view/verification_code_page.dart new file mode 100644 index 0000000..1a3bcb2 --- /dev/null +++ b/lib/features/menu/view/widgets/securty/view/verification_code_page.dart @@ -0,0 +1,181 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_bloc.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_event.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/bloc/security_state.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/view/set_password_page.dart'; +import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; +import 'package:pin_code_fields/pin_code_fields.dart'; + +class VerificationCodePage extends StatelessWidget { + const VerificationCodePage({super.key}); + + @override + Widget build(BuildContext context) { + String otp = ''; + return BlocProvider( + create: (context) => SecurityBloc()..add(StartTimerEvent()), + child: BlocConsumer( + listener: (context, state) { + if (state is SuccessForgetState) { + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => SetPasswordPage( + otpCode: otp, + ), + )); + } + }, + builder: (context, state) { + final _bloc = BlocProvider.of(context); + + return DefaultScaffold( + title: 'Change Password', + child: Column( + children: [ + const SizedBox(height: 55), + const Padding( + padding: EdgeInsets.all(8.0), + child: BodyMedium( + text: 'Verification Code', + fontWeight: FontWeight.w700, + fontSize: 18, + ), + ), + const BodyMedium( + text: 'We have sent the verification code', + fontWeight: FontWeight.w400, + fontSize: 16, + ), + const SizedBox(height: 4), + Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const BodyMedium( + text: 'to ', + fontWeight: FontWeight.w400, + fontSize: 14, + ), + BodyMedium( + text: HomeCubit.user!.email!, + fontWeight: FontWeight.w700, + fontSize: 14, + ), + ], + ), + const SizedBox(height: 40), + BlocBuilder( + builder: (context, state) { + return Center( + child: PinCodeTextField( + hintCharacter: '0', + appContext: context, + length: 6, + mainAxisAlignment: MainAxisAlignment.center, + backgroundColor: Colors.transparent, + enableActiveFill: true, + pinTheme: PinTheme( + shape: PinCodeFieldShape.box, + activeColor: Colors.white, + selectedColor: Colors.white, + inactiveColor: Colors.white, + inactiveFillColor: Colors.white70, + selectedFillColor: Colors.white70, + activeFillColor: Colors.white, + errorBorderColor: Colors.white, + fieldHeight: 55.0, + fieldWidth: 55.0, + fieldOuterPadding: const EdgeInsets.only(right: 8), + borderRadius: BorderRadius.circular(17), + borderWidth: 1, + ), + cursorWidth: 1, + keyboardType: TextInputType.number, + onChanged: (value) { + // Update OTP code in the bloc when user enters a pin + _bloc.otpCode = value; + otp = value; + }, + ), + ); + }, + ), + const SizedBox(height: 55), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: InkWell( + onTap: state is TimerState && + !state.isButtonEnabled && + state.remainingTime != 1 + ? null + : () { + _bloc.add(StartTimerEvent()); + }, + child: Container( + padding: const EdgeInsets.only( + right: 20, left: 20, top: 15, bottom: 15), + decoration: BoxDecoration( + color: state is TimerState && !state.isButtonEnabled + ? ColorsManager.blueButton + : ColorsManager.blueColor, + borderRadius: + BorderRadius.all(Radius.circular(20))), + child: Center( + child: Center( + child: Text( + '${state is TimerState && !state.isButtonEnabled && state.remainingTime != 1 ? "${_bloc.formattedTime(state.remainingTime)} " : "Resend"}', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w700, + color: state is TimerState && + !state.isButtonEnabled + ? Colors.white + : ColorsManager.onPrimaryColor, + ), + ), + ), + ), + ), + )), + const SizedBox(width: 20), + Expanded( + child: InkWell( + onTap: () { + context + .read() + .add(VerifyPassCodeEvent()); + }, + child: Container( + padding: const EdgeInsets.only( + right: 20, left: 20, top: 15, bottom: 15), + decoration: const BoxDecoration( + color: ColorsManager.blueColor, + borderRadius: + BorderRadius.all(Radius.circular(20))), + child: const Center( + child: Text( + "Verify", + style: TextStyle( + fontSize: 16, + color: Colors.white, + fontWeight: FontWeight.w700), + ), + ), + ), + ), + ), + ], + ), + ], + ), + ); + }, + ), + ); + } +} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 7ff3fa0..c6f2991 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -1125,4 +1125,11 @@ class Assets { "assets/icons/edit_device_name_icon.svg"; static const String sosHomeIcon = "assets/icons/sos_home_icon.svg"; static const String editNameSetting = "assets/icons/edit_name_setting.svg"; + + static const String verificationIcon = "assets/icons/verification_icon.svg"; + + static const String passwordUnvisibility = + "assets/icons/password_unvisibility.svg"; + static const String passwordVisibility = + "assets/icons/password_visibility.svg"; } diff --git a/lib/services/api/profile_api.dart b/lib/services/api/profile_api.dart index c942f68..fcc5f11 100644 --- a/lib/services/api/profile_api.dart +++ b/lib/services/api/profile_api.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:developer'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/auth/model/user_model.dart'; import 'package:syncrow_app/features/menu/model/region_model.dart'; @@ -9,14 +10,15 @@ import 'package:syncrow_app/services/api/http_service.dart'; class ProfileApi { static final HTTPService _httpService = HTTPService(); - static Future> saveName({String? firstName, String? lastName,}) async { + static Future> saveName({ + String? firstName, + String? lastName, + }) async { try { final response = await _httpService.put( - path: ApiEndpoints.saveName.replaceAll('{userUuid}', HomeCubit.user!.uuid!), - body: { - "firstName": firstName, - "lastName": lastName - }, + path: ApiEndpoints.saveName + .replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: {"firstName": firstName, "lastName": lastName}, expectedResponseModel: (json) { return json; }, @@ -27,10 +29,13 @@ class ProfileApi { } } - static Future saveRegion({String? regionUuid,}) async { + static Future saveRegion({ + String? regionUuid, + }) async { try { final response = await _httpService.put( - path: ApiEndpoints.saveRegion.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + path: ApiEndpoints.saveRegion + .replaceAll('{userUuid}', HomeCubit.user!.uuid!), body: { "regionUuid": regionUuid, }, @@ -44,10 +49,13 @@ class ProfileApi { } } - static Future saveTimeZone({String? regionUuid,}) async { + static Future saveTimeZone({ + String? regionUuid, + }) async { try { final response = await _httpService.put( - path: ApiEndpoints.saveTimeZone.replaceAll('{userUuid}', HomeCubit.user!.uuid!), + path: ApiEndpoints.saveTimeZone + .replaceAll('{userUuid}', HomeCubit.user!.uuid!), body: { "timezoneUuid": regionUuid, }, @@ -64,10 +72,9 @@ class ProfileApi { static Future> saveImage(String image) async { try { final response = await _httpService.put( - path: ApiEndpoints.sendPicture.replaceAll('{userUuid}', HomeCubit.user!.uuid!), - body: { - "profilePicture": 'data:image/png;base64,$image' - }, + path: ApiEndpoints.sendPicture + .replaceAll('{userUuid}', HomeCubit.user!.uuid!), + body: {"profilePicture": 'data:image/png;base64,$image'}, expectedResponseModel: (json) { return json; }, @@ -78,14 +85,14 @@ class ProfileApi { } } - Future fetchUserInfo(userId) async { + Future fetchUserInfo(userId) async { final response = await _httpService.get( - path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), - showServerMessage: true, - expectedResponseModel: (json) { - return UserModel.fromJson(json); - } - ); + path: ApiEndpoints.getUser.replaceAll('{userUuid}', userId!), + showServerMessage: true, + expectedResponseModel: (json) { + log('user json =$json'); + return UserModel.fromJson(json); + }); return response; } @@ -94,9 +101,10 @@ class ProfileApi { path: ApiEndpoints.getRegion, showServerMessage: true, expectedResponseModel: (json) { - return (json as List).map((zone) => RegionModel.fromJson(zone)).toList(); - } - ); + return (json as List) + .map((zone) => RegionModel.fromJson(zone)) + .toList(); + }); return response as List; } @@ -106,9 +114,7 @@ class ProfileApi { showServerMessage: true, expectedResponseModel: (json) { return (json as List).map((zone) => TimeZone.fromJson(zone)).toList(); - } - ); + }); return response as List; } - } diff --git a/lib/utils/resource_manager/color_manager.dart b/lib/utils/resource_manager/color_manager.dart index ac3a4bb..052d0e0 100644 --- a/lib/utils/resource_manager/color_manager.dart +++ b/lib/utils/resource_manager/color_manager.dart @@ -34,5 +34,8 @@ abstract class ColorsManager { static const Color blueColor = Color(0xff5481F3); static const Color blueColor1 = Color(0xff0A7AFF); static const Color grayButtonColors = Color(0xffCCCCCC); + static const Color blueButton = Color(0xff85BDFF); + } -//background: #F5F5F5;023DFE +//background: #F5F5F5;background: #85BDFF; + diff --git a/lib/utils/resource_manager/constants.dart b/lib/utils/resource_manager/constants.dart index 998ef97..18782e5 100644 --- a/lib/utils/resource_manager/constants.dart +++ b/lib/utils/resource_manager/constants.dart @@ -5,7 +5,7 @@ import 'package:syncrow_app/features/devices/model/function_model.dart'; import 'package:syncrow_app/features/menu/view/widgets/join_home/join_home_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/manage_home/manage_home_view.dart'; import 'package:syncrow_app/features/menu/view/widgets/privacy/privacy_view.dart'; -import 'package:syncrow_app/features/menu/view/widgets/securty/securty_view.dart'; +import 'package:syncrow_app/features/menu/view/widgets/securty/view/securty_view.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';