Bladeren bron

重置密码

liukai 2 weken geleden
bovenliggende
commit
befdbb26c5

+ 2 - 2
packages/cpt_profile/lib/modules/change_mobile/change_mobile_page.dart

@@ -242,7 +242,7 @@ class ChangeMobilePage extends HookConsumerWidget {
         hintText: state.formData[key]!['hintText'],
         hintStyle: TextStyle(
           color: context.appColors.authFiledHint,
-          fontSize: 16.0,
+          fontSize: 15.0,
           fontWeight: FontWeight.w500,
         ),
         controller: state.formData[key]!['controller'],
@@ -253,7 +253,7 @@ class ChangeMobilePage extends HookConsumerWidget {
         height: 44,
         style: TextStyle(
           color: context.appColors.authFiledText,
-          fontSize: 16.0,
+          fontSize: 15.0,
           fontWeight: FontWeight.w500,
         ),
         inputType: textInputType,

+ 1 - 1
packages/cpt_profile/lib/modules/change_mobile/change_mobile_view_model.g.dart

@@ -7,7 +7,7 @@ part of 'change_mobile_view_model.dart';
 // **************************************************************************
 
 String _$changeMobileViewModelHash() =>
-    r'ee65fa3e237d339b2437eccc1800b5394ed31db9';
+    r'b7c9fe3ec47e96841ff1389e6b13f83be7d7fe9a';
 
 /// See also [ChangeMobileViewModel].
 @ProviderFor(ChangeMobileViewModel)

+ 282 - 0
packages/cpt_profile/lib/modules/reset_password/reset_password_page.dart

@@ -0,0 +1,282 @@
+
+import 'package:cpt_profile/modules/reset_password/reset_password_state.dart';
+import 'package:cs_resources/generated/assets.dart';
+import 'package:cs_resources/generated/l10n.dart';
+import 'package:cs_resources/theme/app_colors_theme.dart';
+import 'package:flutter/material.dart';
+import 'package:auto_route/auto_route.dart';
+import 'package:hooks_riverpod/hooks_riverpod.dart';
+import 'package:router/ext/auto_router_extensions.dart';
+import 'package:widgets/ext/ex_widget.dart';
+import 'package:widgets/my_appbar.dart';
+import 'package:widgets/my_button.dart';
+import 'package:widgets/my_load_image.dart';
+import 'package:widgets/my_text_field.dart';
+import 'package:widgets/my_text_view.dart';
+import 'package:widgets/widget_export.dart';
+
+import '../../router/page/profile_page_router.dart';
+import 'reset_password_view_model.dart';
+
+@RoutePage()
+class ResetPasswordPage extends HookConsumerWidget {
+  const ResetPasswordPage({Key? key}) : super(key: key);
+
+  //启动当前页面
+  static void startInstance({BuildContext? context}) {
+    if (context != null) {
+      context.router.push(const ResetPasswordPageRoute());
+    } else {
+      appRouter.push(const ResetPasswordPageRoute());
+    }
+  }
+
+  @override
+  Widget build(BuildContext context, WidgetRef ref) {
+    final viewModel = ref.watch(resetPasswordViewModelProvider.notifier);
+    final state = ref.watch(resetPasswordViewModelProvider);
+
+    return Scaffold(
+      appBar: MyAppBar.appBar(context, S.current.reset_password),
+      backgroundColor: context.appColors.backgroundDefault,
+      body: SingleChildScrollView(
+        scrollDirection: Axis.vertical,
+        physics: const BouncingScrollPhysics(),
+        child: Container(
+          margin: const EdgeInsets.symmetric(horizontal: 15),
+          width: double.infinity,
+          child: Column(
+            mainAxisSize: MainAxisSize.max,
+            crossAxisAlignment: CrossAxisAlignment.start,
+            children: [
+              //手机号码
+              MyTextView(
+                S.current.mobile_phone,
+                fontSize: 16.5,
+                marginTop: 38,
+                marginBottom: 15,
+                isFontMedium: true,
+                textColor: context.appColors.textBlack,
+              ),
+
+              //电话号码
+              Row(
+                mainAxisSize: MainAxisSize.max,
+                crossAxisAlignment: CrossAxisAlignment.center,
+                children: [
+                  const MyAssetImage(
+                    Assets.authCountrySg,
+                    width: 45,
+                    height: 30,
+                  ),
+                  MyTextView(
+                    "+65",
+                    textColor: context.appColors.textBlack,
+                    fontSize: 18.5,
+                    marginLeft: 15,
+                    marginRight: 12,
+                    isFontMedium: true,
+                  ),
+                  //电话输入框
+                  _buildInputLayout(
+                    context,
+                    state,
+                    "phone",
+                    textInputType: TextInputType.number,
+                    textInputAction: TextInputAction.next,
+                    onSubmit: (formKey, value) {
+                      state.formData[formKey]!['focusNode'].unfocus();
+                      FocusScope.of(context).requestFocus(state.formData['code']!['focusNode']);
+                    },
+                  ).expanded(),
+                ],
+              ),
+
+              //手机的验证码
+              MyTextView(
+                S.current.verification_code,
+                fontSize: 16.5,
+                marginTop: 14,
+                marginBottom: 16,
+                isFontMedium: true,
+                textColor: context.appColors.textBlack,
+              ),
+
+              // 表单 - 电话号码验证码
+              _buildInputLayout(
+                context,
+                state,
+                "code",
+                textInputType: TextInputType.number,
+                textInputAction: TextInputAction.next,
+                errorText: state.codeErrorText,
+                showRightIcon: true,
+                rightWidget: MyTextView(
+                  state.isCounting ? "${state.countdownTime} s" : S.current.get_code,
+                  textAlign: TextAlign.center,
+                  textColor: context.appColors.textPrimary,
+                  fontSize: 15,
+                  paddingRight: 5,
+                  isFontMedium: true,
+                  onClick: state.isCounting ? null : () => viewModel.showVerifyCodedDialog(),
+                ).paddingOnly(top: 15, bottom: 15),
+                onSubmit: (formKey, value) {
+                  state.formData[formKey]!['focusNode'].unfocus();
+                  FocusScope.of(context).requestFocus(state.formData['password']!['focusNode']);
+                },
+              ),
+
+              //新密码
+              MyTextView(
+                S.current.new_password,
+                fontSize: 16.5,
+                marginTop: 14,
+                marginBottom: 16,
+                isFontMedium: true,
+                textColor: context.appColors.textBlack,
+              ),
+
+              // 表单 - 新密码
+              _buildInputLayout(
+                context,
+                state,
+                "password",
+                obscureText: !state.pwdVisibility,
+                errorText: state.passwordErrorText,
+                textInputAction: TextInputAction.next,
+                showRightIcon: true,
+                rightWidget: IconButton(
+                  highlightColor: Colors.transparent,
+                  splashColor: Colors.transparent,
+                  icon: state.pwdVisibility
+                      ? const MyAssetImage(
+                    Assets.authPasswordHide,
+                    width: 22.5,
+                    height: 16.5,
+                  )
+                      : const MyAssetImage(
+                    Assets.authPasswordShow,
+                    width: 22.5,
+                    height: 16.5,
+                  ),
+                  onPressed: () {
+                    viewModel.switchPwdVisibility();
+                  },
+                ),
+                onSubmit: (formKey, value) {
+                  state.formData[formKey]!['focusNode'].unfocus();
+                  FocusScope.of(context).requestFocus(state.formData['confirm_password']!['focusNode']);
+                },
+              ),
+
+              //重复密码
+              MyTextView(
+                S.current.confirm_new_password,
+                fontSize: 16.5,
+                marginTop: 14,
+                marginBottom: 16,
+                isFontMedium: true,
+                textColor: context.appColors.textBlack,
+              ),
+
+              // 表单 - 确认密码
+              _buildInputLayout(
+                context,
+                state,
+                "confirm_password",
+                obscureText: !state.confirmPwdVisibility,
+                errorText: state.confirmPasswordErrorText,
+                showRightIcon: true,
+                rightWidget: IconButton(
+                  highlightColor: Colors.transparent,
+                  splashColor: Colors.transparent,
+                  icon: state.confirmPwdVisibility
+                      ? const MyAssetImage(
+                    Assets.authPasswordHide,
+                    width: 22.5,
+                    height: 16.5,
+                  )
+                      : const MyAssetImage(
+                    Assets.authPasswordShow,
+                    width: 22.5,
+                    height: 16.5,
+                  ),
+                  onPressed: () {
+                    viewModel.switchConfirmPwdVisibility();
+                  },
+                ),
+                onSubmit: (formKey, value) {
+                  state.formData[formKey]!['focusNode'].unfocus();
+                  viewModel.submitResetPassword();
+                },
+              ),
+
+              //提交按钮
+              MyButton(
+                onPressed: viewModel.submitResetPassword,
+                text: S.current.submit,
+                textColor: Colors.white,
+                backgroundColor: context.appColors.btnBgDefault,
+                fontWeight: FontWeight.w500,
+                type: ClickType.throttle,
+                fontSize: 16,
+                minHeight: 50,
+                radius: 5,
+              ).marginOnly(top: 50, bottom: 30),
+            ],
+          ),
+        ),
+      ),
+    );
+  }
+
+  /// 输入框
+  Widget _buildInputLayout(
+      BuildContext context,
+      ResetPasswordState state,
+      String key, {
+        double marginTop = 0,
+        bool? showRightIcon = false, //是否展示右侧的布局
+        Widget? rightWidget, //右侧的布局
+        TextInputType textInputType = TextInputType.text,
+        String? errorText,
+        bool obscureText = false,
+        TextInputAction textInputAction = TextInputAction.done,
+        Function? onSubmit,
+      }) {
+    return IgnoreKeyboardDismiss(
+      child: MyTextField(
+        key,
+        fillBackgroundColor: context.appColors.authFiledBG,
+        state.formData[key]!['value'],
+        hintText: state.formData[key]!['hintText'],
+        hintStyle: TextStyle(
+          color: context.appColors.authFiledHint,
+          fontSize: 15.0,
+          fontWeight: FontWeight.w500,
+        ),
+        controller: state.formData[key]!['controller'],
+        focusNode: state.formData[key]!['focusNode'],
+        margin: EdgeInsets.only(top: marginTop),
+        padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 3),
+        showDivider: false,
+        height: 44,
+        style: TextStyle(
+          color: context.appColors.authFiledText,
+          fontSize: 15.0,
+          fontWeight: FontWeight.w500,
+        ),
+        inputType: textInputType,
+        textInputAction: textInputAction,
+        onSubmit: onSubmit,
+        cursorColor: context.appColors.authFiledText,
+        obscureText: obscureText,
+        errorText: errorText,
+        showLeftIcon: true,
+        showRightIcon: showRightIcon,
+        rightWidget: rightWidget,
+      ),
+    );
+  }
+
+}

+ 85 - 0
packages/cpt_profile/lib/modules/reset_password/reset_password_state.dart

@@ -0,0 +1,85 @@
+import 'package:cs_resources/generated/l10n.dart';
+import 'package:flutter/material.dart';
+
+class ResetPasswordState{
+  //表单的校验与数据
+  final Map<String, Map<String, dynamic>> formData;
+
+  //表单的错误信息展示
+  String? codeErrorText;
+  String? passwordErrorText;
+  String? confirmPasswordErrorText;
+
+  //是否明文展示密码
+  bool pwdVisibility;
+
+  bool confirmPwdVisibility;
+
+  //获取验证码的倒计时
+  bool isCounting;
+  int countdownTime;
+
+  // ===================================  Begin  ↓  ===================================
+
+  ResetPasswordState({
+    Map<String, Map<String, dynamic>>? formData,
+    this.pwdVisibility = false,
+    this.confirmPwdVisibility = false,
+    this.isCounting = false,
+    this.countdownTime = 0,
+    this.codeErrorText,
+    this.passwordErrorText,
+    this.confirmPasswordErrorText,
+  }) : formData = formData ??
+      {
+        'phone': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': S.current.mobile_phone,
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+        'code': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': S.current.verification_code,
+          'focusNode': FocusNode(),
+          'obsecure': false,
+        },
+        'password': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': S.current.password_format,
+          'focusNode': FocusNode(),
+          'obsecure': true,
+        },
+        'confirm_password': {
+          'value': '',
+          'controller': TextEditingController(),
+          'hintText': S.current.password_format,
+          'focusNode': FocusNode(),
+          'obsecure': true,
+        },
+      };
+
+  ResetPasswordState copyWith({
+    String? codeErrorText,
+    String? passwordErrorText,
+    String? confirmPasswordErrorText,
+    bool? pwdVisibility,
+    bool? confirmPwdVisibility,
+    bool? isCounting,
+    int? countdownTime,
+  }) {
+    return ResetPasswordState(
+      formData: this.formData,
+      pwdVisibility: pwdVisibility ?? this.pwdVisibility,
+      confirmPwdVisibility: confirmPwdVisibility ?? this.confirmPwdVisibility,
+      isCounting: isCounting ?? this.isCounting,
+      countdownTime: countdownTime ?? this.countdownTime,
+      codeErrorText: codeErrorText,
+      passwordErrorText: passwordErrorText,
+      confirmPasswordErrorText: confirmPasswordErrorText,
+    );
+  }
+}

+ 158 - 0
packages/cpt_profile/lib/modules/reset_password/reset_password_view_model.dart

@@ -0,0 +1,158 @@
+import 'dart:async';
+
+import 'package:cpt_profile/modules/reset_password/reset_password_state.dart';
+import 'package:flutter/material.dart';
+import 'package:plugin_platform/engine/toast/toast_engine.dart';
+import 'package:riverpod_annotation/riverpod_annotation.dart';
+import 'package:router/ext/auto_router_extensions.dart';
+import 'package:shared/utils/log_utils.dart';
+import 'package:shared/utils/util.dart';
+
+part 'reset_password_view_model.g.dart';
+
+@riverpod
+class ResetPasswordViewModel extends _$ResetPasswordViewModel {
+  @override
+  ResetPasswordState build() {
+    final state = ResetPasswordState();
+    initListener(state);
+    ref.onDispose(() {
+      onDispose(state);
+    });
+    return state;
+  }
+
+  /// 提交重置密码请求
+  void submitResetPassword() {
+    state = state.copyWith(codeErrorText: null, passwordErrorText: null, confirmPasswordErrorText: null);
+
+    final FocusNode phoneFocusNode = state.formData['phone']!['focusNode'];
+    final FocusNode codeFocusNode = state.formData['code']!['focusNode'];
+    final FocusNode passwordFocusNode = state.formData['password']!['focusNode'];
+    final FocusNode confirmPasswordFocusNode = state.formData['confirm_password']!['focusNode'];
+
+    final TextEditingController phoneController = state.formData['phone']!['controller'];
+    final TextEditingController codeController = state.formData['code']!['controller'];
+    final TextEditingController passwordController = state.formData['password']!['controller'];
+    final TextEditingController confirmPasswordController = state.formData['confirm_password']!['controller'];
+
+    phoneFocusNode.unfocus();
+    codeFocusNode.unfocus();
+    passwordFocusNode.unfocus();
+    confirmPasswordFocusNode.unfocus();
+
+    final phone = phoneController.text;
+    final code = codeController.text;
+    final password = passwordController.text;
+    final confirmPassword = confirmPasswordController.text;
+
+    Log.d('当前待提交的 phone:$phone code:$code password:$password confirmPassword:$confirmPassword');
+
+    if (Utils.isEmpty(phone)) {
+      ToastEngine.show("Enter Mobile Phone");
+      return;
+    }
+
+    if (Utils.isEmpty(code)) {
+      state = state.copyWith(codeErrorText: "Verification Code cannot be empty!");
+      return;
+    }
+
+    if (Utils.isEmpty(password)) {
+      state = state.copyWith(passwordErrorText: "Password cannot be empty!");
+      return;
+    }
+
+    if (Utils.isEmpty(confirmPassword)) {
+      state = state.copyWith(confirmPasswordErrorText: "Confirm Password cannot be empty!");
+      return;
+    }
+
+    if (confirmPassword != password) {
+      state = state.copyWith(confirmPasswordErrorText: "Password mismatch, please check password");
+      return;
+    }
+
+    //执行密码登录
+    ToastEngine.show('准备执行请求发送验证码 phone:$phone code:$code password:$password confirmPassword:$confirmPassword');
+
+    //页面返回
+    appRouter.maybePop();
+  }
+
+  //切换隐藏显示密码
+  void switchPwdVisibility() {
+    state = state.copyWith(pwdVisibility: !state.pwdVisibility);
+  }
+
+  void switchConfirmPwdVisibility() {
+    state = state.copyWith(confirmPwdVisibility: !state.confirmPwdVisibility);
+  }
+
+  void showVerifyCodedDialog() {
+    _startCountDown();
+  }
+
+  Timer? countdownTimer;
+
+  /// 开启倒计时
+  void _startCountDown() {
+    //60秒倒计时
+    state = state.copyWith(isCounting: true, countdownTime: 60);
+
+    //每秒的倒计时
+    countdownTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
+      int time = state.countdownTime;
+      Log.d('倒计时-->$time');
+      if (time > 0) {
+        time--;
+        state = state.copyWith(isCounting: true, countdownTime: time);
+      } else {
+        state = state.copyWith(isCounting: false, countdownTime: 0);
+        countdownTimer?.cancel(); // 取消计时器
+      }
+    });
+  }
+
+  //初始化监听
+  void initListener(ResetPasswordState initState) {
+    final FocusNode codeFocusNode = initState.formData['code']!['focusNode'];
+    final FocusNode passwordFocusNode = initState.formData['password']!['focusNode'];
+    final FocusNode confirmPasswordFocusNode = initState.formData['confirm_password']!['focusNode'];
+
+    codeFocusNode.addListener(() {
+      // 获取焦点的时候清空错误文本
+      if (codeFocusNode.hasFocus) {
+        state = state.copyWith(codeErrorText: null);
+      }
+    });
+
+    passwordFocusNode.addListener(() {
+      // 获取焦点的时候清空错误文本
+      if (passwordFocusNode.hasFocus) {
+        state = state.copyWith(passwordErrorText: null);
+      }
+    });
+
+    confirmPasswordFocusNode.addListener(() {
+      // 获取焦点的时候清空错误文本
+      if (confirmPasswordFocusNode.hasFocus) {
+        state = state.copyWith(confirmPasswordErrorText: null);
+      }
+    });
+  }
+
+  //销毁资源
+  void onDispose(ResetPasswordState initState) {
+    final FocusNode codeFocusNode = initState.formData['code']!['focusNode'];
+    final FocusNode passwordFocusNode = initState.formData['password']!['focusNode'];
+    final FocusNode confirmPasswordFocusNode = initState.formData['confirm_password']!['focusNode'];
+    codeFocusNode.dispose();
+    passwordFocusNode.dispose();
+    confirmPasswordFocusNode.dispose();
+
+    countdownTimer?.cancel();
+
+    Log.d("ForgotVerifyViewModel 销毁 onDispose");
+  }
+}

+ 27 - 0
packages/cpt_profile/lib/modules/reset_password/reset_password_view_model.g.dart

@@ -0,0 +1,27 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'reset_password_view_model.dart';
+
+// **************************************************************************
+// RiverpodGenerator
+// **************************************************************************
+
+String _$resetPasswordViewModelHash() =>
+    r'4b6b7ea5f180d679389a10da92758d39ea9f86e3';
+
+/// See also [ResetPasswordViewModel].
+@ProviderFor(ResetPasswordViewModel)
+final resetPasswordViewModelProvider = AutoDisposeNotifierProvider<
+    ResetPasswordViewModel, ResetPasswordState>.internal(
+  ResetPasswordViewModel.new,
+  name: r'resetPasswordViewModelProvider',
+  debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
+      ? null
+      : _$resetPasswordViewModelHash,
+  dependencies: null,
+  allTransitiveDependencies: null,
+);
+
+typedef _$ResetPasswordViewModel = AutoDisposeNotifier<ResetPasswordState>;
+// ignore_for_file: type=lint
+// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package

+ 1 - 1
packages/cpt_profile/lib/modules/setting/setting_page.dart

@@ -59,7 +59,7 @@ class SettingPage extends HookConsumerWidget {
             SettingItemContainer(title: S.current.change_mobile_phone).onTap(viewModel.gotoChangeMobilePage),
 
             //重置密码
-            SettingItemContainer(title: S.current.reset_password),
+            SettingItemContainer(title: S.current.reset_password).onTap(viewModel.gotoResetPasswordPage),
 
             //隐私协议
             SettingItemContainer(title: S.current.privacy_policy),

+ 5 - 0
packages/cpt_profile/lib/modules/setting/setting_view_model.dart

@@ -1,4 +1,5 @@
 import 'package:cpt_profile/modules/change_mobile/change_mobile_page.dart';
+import 'package:cpt_profile/modules/reset_password/reset_password_page.dart';
 import 'package:cpt_profile/modules/setting/setting_state.dart';
 import 'package:riverpod_annotation/riverpod_annotation.dart';
 
@@ -19,4 +20,8 @@ class SettingViewModel extends _$SettingViewModel {
   void gotoChangeMobilePage() {
     ChangeMobilePage.startInstance();
   }
+
+  void gotoResetPasswordPage() {
+    ResetPasswordPage.startInstance();
+  }
 }

+ 1 - 1
packages/cpt_profile/lib/modules/setting/setting_view_model.g.dart

@@ -6,7 +6,7 @@ part of 'setting_view_model.dart';
 // RiverpodGenerator
 // **************************************************************************
 
-String _$settingViewModelHash() => r'89a579d989f14f87f6017c39a3df45ab9b93f2c1';
+String _$settingViewModelHash() => r'e008ce2120bd069243f13ad1502eba8992950a63';
 
 /// See also [SettingViewModel].
 @ProviderFor(SettingViewModel)

+ 2 - 0
packages/cpt_profile/lib/router/page/profile_page_router.dart

@@ -6,6 +6,7 @@ import 'package:router/path/router_path.dart';
 import '../../modules/profile_edit/Profile_edit_page.dart';
 import '../../modules/setting/setting_page.dart';
 import '../../modules/change_mobile/change_mobile_page.dart';
+import '../../modules/reset_password/reset_password_page.dart';
 
 
 part 'profile_page_router.gr.dart';
@@ -21,5 +22,6 @@ class ProfilePageRouter extends _$ProfilePageRouter {
     CustomRoute(page: ProfileEditPageRoute.page, path: RouterPath.profileEdit, transitionsBuilder: applySlideTransition),
     CustomRoute(page: SettingPageRoute.page, path: RouterPath.settings, transitionsBuilder: applySlideTransition),
     CustomRoute(page: ChangeMobilePageRoute.page, path: RouterPath.settingsChangeMobile, transitionsBuilder: applySlideTransition),
+    CustomRoute(page: ResetPasswordPageRoute.page, path: RouterPath.settingsResetPassword, transitionsBuilder: applySlideTransition),
   ];
 }

+ 20 - 0
packages/cpt_profile/lib/router/page/profile_page_router.gr.dart

@@ -27,6 +27,12 @@ abstract class _$ProfilePageRouter extends RootStackRouter {
         child: const ProfileEditPage(),
       );
     },
+    ResetPasswordPageRoute.name: (routeData) {
+      return AutoRoutePage<dynamic>(
+        routeData: routeData,
+        child: const ResetPasswordPage(),
+      );
+    },
     SettingPageRoute.name: (routeData) {
       return AutoRoutePage<dynamic>(
         routeData: routeData,
@@ -65,6 +71,20 @@ class ProfileEditPageRoute extends PageRouteInfo<void> {
 }
 
 /// generated route for
+/// [ResetPasswordPage]
+class ResetPasswordPageRoute extends PageRouteInfo<void> {
+  const ResetPasswordPageRoute({List<PageRouteInfo>? children})
+      : super(
+          ResetPasswordPageRoute.name,
+          initialChildren: children,
+        );
+
+  static const String name = 'ResetPasswordPageRoute';
+
+  static const PageInfo<void> page = PageInfo<void>(name);
+}
+
+/// generated route for
 /// [SettingPage]
 class SettingPageRoute extends PageRouteInfo<void> {
   const SettingPageRoute({List<PageRouteInfo>? children})