Showing
18 changed files
with
665 additions
and
114 deletions
| @@ -69,7 +69,11 @@ class AccountSettingView extends GetView<MyController> { | @@ -69,7 +69,11 @@ class AccountSettingView extends GetView<MyController> { | ||
| 69 | _SecurityEmailRow( | 69 | _SecurityEmailRow( |
| 70 | email: securityEmail, | 70 | email: securityEmail, |
| 71 | onTap: () async { | 71 | onTap: () async { |
| 72 | - await Get.toNamed(Routes.ADD_SECURITY_EMAIL); | 72 | + if (securityEmail.isEmpty) { |
| 73 | + await Get.toNamed(Routes.ADD_SECURITY_EMAIL); | ||
| 74 | + } else { | ||
| 75 | + await Get.toNamed(Routes.CHANGE_SECURITY_EMAIL); | ||
| 76 | + } | ||
| 73 | controller.refreshEmail(); | 77 | controller.refreshEmail(); |
| 74 | }, | 78 | }, |
| 75 | ), | 79 | ), |
| 1 | import 'dart:async'; | 1 | import 'dart:async'; |
| 2 | +import 'dart:convert'; | ||
| 2 | 3 | ||
| 4 | +import 'package:crypto/crypto.dart'; | ||
| 3 | import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_views.dart'; | 5 | import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_views.dart'; |
| 6 | +import 'package:doublefeel_flutter/core/network/api/user_api.dart'; | ||
| 7 | +import 'package:doublefeel_flutter/core/result/app_result.dart'; | ||
| 8 | +import 'package:doublefeel_flutter/core/services/loading_service.dart'; | ||
| 4 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 9 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 10 | +import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart'; | ||
| 5 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 11 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 6 | import 'package:flutter/material.dart'; | 12 | import 'package:flutter/material.dart'; |
| 7 | import 'package:flutter/services.dart'; | 13 | import 'package:flutter/services.dart'; |
| 8 | import 'package:get/get.dart'; | 14 | import 'package:get/get.dart'; |
| 9 | 15 | ||
| 10 | class AddSecurityEmailController extends GetxController { | 16 | class AddSecurityEmailController extends GetxController { |
| 17 | + final UserApi _userApi = Get.find<UserApi>(); | ||
| 18 | + final userPrefs = Get.find<UserPreferencesStorage>(); | ||
| 11 | static final _emailPattern = RegExp( | 19 | static final _emailPattern = RegExp( |
| 12 | r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$', | 20 | r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$', |
| 13 | ); | 21 | ); |
| @@ -85,12 +93,19 @@ class AddSecurityEmailController extends GetxController { | @@ -85,12 +93,19 @@ class AddSecurityEmailController extends GetxController { | ||
| 85 | isConfirmPasswordObscured.value = !isConfirmPasswordObscured.value; | 93 | isConfirmPasswordObscured.value = !isConfirmPasswordObscured.value; |
| 86 | } | 94 | } |
| 87 | 95 | ||
| 88 | - void sendVerificationEmail() { | 96 | + Future<void> sendVerificationEmail({bool isChangeEmail = false}) async { |
| 89 | hasSubmittedEmail.value = true; | 97 | hasSubmittedEmail.value = true; |
| 90 | if (!isEmailValid) return; | 98 | if (!isEmailValid) return; |
| 91 | - | ||
| 92 | - startResendCountdown(); | ||
| 93 | - Get.to(() => const SecurityEmailVerificationView()); | 99 | + try { |
| 100 | + await LoadingService.instance.run(() async { | ||
| 101 | + final result = await _userApi.requestEmailVerifyCode(emailInput.value); | ||
| 102 | + // if (result is AppSuccess<void>) { | ||
| 103 | + startResendCountdown(); | ||
| 104 | + Get.to( | ||
| 105 | + () => SecurityEmailVerificationView(isChangeEmail: isChangeEmail)); | ||
| 106 | + // } | ||
| 107 | + }); | ||
| 108 | + } catch (_) {} | ||
| 94 | } | 109 | } |
| 95 | 110 | ||
| 96 | void startResendCountdown() { | 111 | void startResendCountdown() { |
| @@ -106,20 +121,71 @@ class AddSecurityEmailController extends GetxController { | @@ -106,20 +121,71 @@ class AddSecurityEmailController extends GetxController { | ||
| 106 | }); | 121 | }); |
| 107 | } | 122 | } |
| 108 | 123 | ||
| 109 | - void submitCode(BuildContext context) { | 124 | + Future<void> submitCode(BuildContext context, |
| 125 | + {bool isChangeEmail = false}) async { | ||
| 110 | if (!canSubmitCode) return; | 126 | if (!canSubmitCode) return; |
| 111 | FocusScope.of(context).unfocus(); | 127 | FocusScope.of(context).unfocus(); |
| 112 | - Get.to(() => const SetSecurityEmailPasswordView()); | 128 | + if (isChangeEmail) { |
| 129 | + try { | ||
| 130 | + await LoadingService.instance.run(() async { | ||
| 131 | + final result = await _userApi.verifyEmailVerifyCode( | ||
| 132 | + emailInput.value, | ||
| 133 | + codeController.text, | ||
| 134 | + ); | ||
| 135 | + if (result is AppSuccess<void>) { | ||
| 136 | + AppToast.show(context.l10n.successChanged); | ||
| 137 | + Get.back(); | ||
| 138 | + Get.back(); | ||
| 139 | + Get.back(); | ||
| 140 | + } else { | ||
| 141 | + AppToast.show(context.l10n.verifyEmailFailed); | ||
| 142 | + Get.back(); | ||
| 143 | + Get.back(); | ||
| 144 | + Get.back(); | ||
| 145 | + } | ||
| 146 | + }); | ||
| 147 | + } catch (_) {} | ||
| 148 | + } else { | ||
| 149 | + try { | ||
| 150 | + await LoadingService.instance.run(() async { | ||
| 151 | + final result = await _userApi.verifyEmailVerifyCode( | ||
| 152 | + emailInput.value, | ||
| 153 | + codeController.text, | ||
| 154 | + ); | ||
| 155 | + // if (result is AppSuccess<void>) { | ||
| 156 | + | ||
| 157 | + Get.to(() => const SetSecurityEmailPasswordView()); | ||
| 158 | + | ||
| 159 | + // } | ||
| 160 | + }); | ||
| 161 | + } catch (_) {} | ||
| 162 | + } | ||
| 113 | } | 163 | } |
| 114 | 164 | ||
| 115 | - void setPassword(BuildContext context) { | 165 | + Future<void> setPassword(BuildContext context) async { |
| 116 | if (!canSetPassword) return; | 166 | if (!canSetPassword) return; |
| 117 | - FocusScope.of(context).unfocus(); | ||
| 118 | - AppToast.show(context.l10n.settingsSaved); | ||
| 119 | - TextInput.finishAutofillContext(); | ||
| 120 | - Get.back(); | ||
| 121 | - Get.back(); | ||
| 122 | - Get.back(); | 167 | + |
| 168 | + try { | ||
| 169 | + final md5Password = | ||
| 170 | + md5.convert(utf8.encode(passwordInput.value)).toString(); | ||
| 171 | + await LoadingService.instance.run(() async { | ||
| 172 | + final result = await _userApi.addSefetyEmail( | ||
| 173 | + emailInput.value, | ||
| 174 | + codeController.text, | ||
| 175 | + md5Password, | ||
| 176 | + ); | ||
| 177 | + // if (result is AppSuccess<void>) { | ||
| 178 | + | ||
| 179 | + FocusScope.of(context).unfocus(); | ||
| 180 | + AppToast.show(context.l10n.settingsSaved); | ||
| 181 | + TextInput.finishAutofillContext(); | ||
| 182 | + Get.back(); | ||
| 183 | + Get.back(); | ||
| 184 | + Get.back(); | ||
| 185 | + | ||
| 186 | + // } | ||
| 187 | + }); | ||
| 188 | + } catch (_) {} | ||
| 123 | } | 189 | } |
| 124 | 190 | ||
| 125 | @override | 191 | @override |
| @@ -9,7 +9,9 @@ import 'package:flutter/services.dart'; | @@ -9,7 +9,9 @@ import 'package:flutter/services.dart'; | ||
| 9 | import 'package:get/get.dart'; | 9 | import 'package:get/get.dart'; |
| 10 | 10 | ||
| 11 | class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | 11 | class AddSecurityEmailView extends GetView<AddSecurityEmailController> { |
| 12 | - const AddSecurityEmailView({super.key}); | 12 | + const AddSecurityEmailView({super.key, this.isChangeEmail = false}); |
| 13 | + | ||
| 14 | + final bool isChangeEmail; | ||
| 13 | 15 | ||
| 14 | @override | 16 | @override |
| 15 | Widget build(BuildContext context) { | 17 | Widget build(BuildContext context) { |
| @@ -38,7 +40,7 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | @@ -38,7 +40,7 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | ||
| 38 | children: [ | 40 | children: [ |
| 39 | const SizedBox(height: 16), | 41 | const SizedBox(height: 16), |
| 40 | Text( | 42 | Text( |
| 41 | - l10n.addSecurityEmail, | 43 | + isChangeEmail ? l10n.changeEmail : l10n.addSecurityEmail, |
| 42 | style: TextStyle( | 44 | style: TextStyle( |
| 43 | color: colors.textPrimary, | 45 | color: colors.textPrimary, |
| 44 | fontSize: 24, | 46 | fontSize: 24, |
| @@ -47,22 +49,59 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | @@ -47,22 +49,59 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | ||
| 47 | ), | 49 | ), |
| 48 | ), | 50 | ), |
| 49 | const SizedBox(height: 8), | 51 | const SizedBox(height: 8), |
| 50 | - Text( | ||
| 51 | - l10n.securityEmailDescription, | ||
| 52 | - textAlign: TextAlign.center, | ||
| 53 | - style: TextStyle( | ||
| 54 | - color: colors.textSecondary, | ||
| 55 | - fontSize: 14, | ||
| 56 | - fontWeight: FontWeight.w400, | ||
| 57 | - height: 1.43, | 52 | + if (isChangeEmail) |
| 53 | + Text.rich( | ||
| 54 | + TextSpan( | ||
| 55 | + children: [ | ||
| 56 | + TextSpan( | ||
| 57 | + text: l10n.yourCurrentEmailIs, | ||
| 58 | + style: TextStyle( | ||
| 59 | + color: colors.textSecondary, | ||
| 60 | + fontSize: 14, | ||
| 61 | + fontWeight: FontWeight.w400, | ||
| 62 | + ), | ||
| 63 | + ), | ||
| 64 | + TextSpan( | ||
| 65 | + text: controller.userPrefs.preferences.value | ||
| 66 | + .meUserInfo?.securityEmail | ||
| 67 | + ?.trim() ?? | ||
| 68 | + '', | ||
| 69 | + style: TextStyle( | ||
| 70 | + color: const Color(0xFF0F0F11), | ||
| 71 | + fontSize: 14, | ||
| 72 | + fontWeight: FontWeight.w400, | ||
| 73 | + ), | ||
| 74 | + ), | ||
| 75 | + TextSpan( | ||
| 76 | + text: l10n.whatWouldYouLikeToUpdateItTo, | ||
| 77 | + style: TextStyle( | ||
| 78 | + color: colors.textSecondary, | ||
| 79 | + fontSize: 14, | ||
| 80 | + fontWeight: FontWeight.w400, | ||
| 81 | + ), | ||
| 82 | + ), | ||
| 83 | + ], | ||
| 84 | + ), | ||
| 85 | + textAlign: TextAlign.center, | ||
| 86 | + ) | ||
| 87 | + else | ||
| 88 | + Text( | ||
| 89 | + l10n.securityEmailDescription, | ||
| 90 | + textAlign: TextAlign.center, | ||
| 91 | + style: TextStyle( | ||
| 92 | + color: colors.textSecondary, | ||
| 93 | + fontSize: 14, | ||
| 94 | + fontWeight: FontWeight.w400, | ||
| 95 | + height: 1.43, | ||
| 96 | + ), | ||
| 58 | ), | 97 | ), |
| 59 | - ), | ||
| 60 | const SizedBox(height: 32), | 98 | const SizedBox(height: 32), |
| 61 | TextField( | 99 | TextField( |
| 62 | controller: c.emailController, | 100 | controller: c.emailController, |
| 63 | keyboardType: TextInputType.emailAddress, | 101 | keyboardType: TextInputType.emailAddress, |
| 64 | autofillHints: const [AutofillHints.email], | 102 | autofillHints: const [AutofillHints.email], |
| 65 | - onSubmitted: (_) => c.sendVerificationEmail(), | 103 | + onSubmitted: (_) => |
| 104 | + c.sendVerificationEmail(isChangeEmail: isChangeEmail), | ||
| 66 | style: TextStyle( | 105 | style: TextStyle( |
| 67 | color: colors.textPrimary, | 106 | color: colors.textPrimary, |
| 68 | fontSize: 16, | 107 | fontSize: 16, |
| @@ -120,8 +159,10 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | @@ -120,8 +159,10 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | ||
| 120 | height: 48, | 159 | height: 48, |
| 121 | child: Obx( | 160 | child: Obx( |
| 122 | () => ElevatedButton( | 161 | () => ElevatedButton( |
| 123 | - onPressed: | ||
| 124 | - c.isEmailValid ? c.sendVerificationEmail : null, | 162 | + onPressed: c.isEmailValid |
| 163 | + ? () => c.sendVerificationEmail( | ||
| 164 | + isChangeEmail: isChangeEmail) | ||
| 165 | + : null, | ||
| 125 | style: ElevatedButton.styleFrom( | 166 | style: ElevatedButton.styleFrom( |
| 126 | backgroundColor: colors.primary, | 167 | backgroundColor: colors.primary, |
| 127 | disabledBackgroundColor: | 168 | disabledBackgroundColor: |
| @@ -150,7 +191,9 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | @@ -150,7 +191,9 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> { | ||
| 150 | 191 | ||
| 151 | class SecurityEmailVerificationView | 192 | class SecurityEmailVerificationView |
| 152 | extends GetView<AddSecurityEmailController> { | 193 | extends GetView<AddSecurityEmailController> { |
| 153 | - const SecurityEmailVerificationView({super.key}); | 194 | + const SecurityEmailVerificationView({super.key, this.isChangeEmail = false}); |
| 195 | + | ||
| 196 | + final bool isChangeEmail; | ||
| 154 | 197 | ||
| 155 | @override | 198 | @override |
| 156 | Widget build(BuildContext context) { | 199 | Widget build(BuildContext context) { |
| @@ -211,7 +254,8 @@ class SecurityEmailVerificationView | @@ -211,7 +254,8 @@ class SecurityEmailVerificationView | ||
| 211 | // inputFormatters: const [ | 254 | // inputFormatters: const [ |
| 212 | // _DigitsOnlyFormatter(), | 255 | // _DigitsOnlyFormatter(), |
| 213 | // ], | 256 | // ], |
| 214 | - onSubmitted: (_) => controller.submitCode(context), | 257 | + onSubmitted: (_) => controller.submitCode(context, |
| 258 | + isChangeEmail: isChangeEmail), | ||
| 215 | style: TextStyle( | 259 | style: TextStyle( |
| 216 | color: colors.textPrimary, | 260 | color: colors.textPrimary, |
| 217 | fontSize: 16, | 261 | fontSize: 16, |
| @@ -283,7 +327,8 @@ class SecurityEmailVerificationView | @@ -283,7 +327,8 @@ class SecurityEmailVerificationView | ||
| 283 | child: Obx( | 327 | child: Obx( |
| 284 | () => ElevatedButton( | 328 | () => ElevatedButton( |
| 285 | onPressed: controller.canSubmitCode | 329 | onPressed: controller.canSubmitCode |
| 286 | - ? () => controller.submitCode(context) | 330 | + ? () => controller.submitCode(context, |
| 331 | + isChangeEmail: isChangeEmail) | ||
| 287 | : null, | 332 | : null, |
| 288 | style: ElevatedButton.styleFrom( | 333 | style: ElevatedButton.styleFrom( |
| 289 | backgroundColor: colors.primary, | 334 | backgroundColor: colors.primary, |
| @@ -715,3 +760,185 @@ class SetSecurityEmailPasswordView extends GetView<AddSecurityEmailController> { | @@ -715,3 +760,185 @@ class SetSecurityEmailPasswordView extends GetView<AddSecurityEmailController> { | ||
| 715 | ); | 760 | ); |
| 716 | } | 761 | } |
| 717 | } | 762 | } |
| 763 | + | ||
| 764 | +class VerifyEmailPwdView extends GetView<AddSecurityEmailController> { | ||
| 765 | + const VerifyEmailPwdView({super.key}); | ||
| 766 | + | ||
| 767 | + @override | ||
| 768 | + Widget build(BuildContext context) { | ||
| 769 | + final c = Get.isRegistered<AddSecurityEmailController>() | ||
| 770 | + ? controller | ||
| 771 | + : Get.put(AddSecurityEmailController()); | ||
| 772 | + final l10n = context.l10n; | ||
| 773 | + final colors = context.colors; | ||
| 774 | + | ||
| 775 | + return AnnotatedRegion<SystemUiOverlayStyle>( | ||
| 776 | + value: SystemUiOverlayStyle( | ||
| 777 | + statusBarColor: Colors.transparent, | ||
| 778 | + statusBarIconBrightness: Brightness.dark, | ||
| 779 | + statusBarBrightness: Brightness.light, | ||
| 780 | + systemNavigationBarColor: colors.backgroundPage, | ||
| 781 | + systemNavigationBarIconBrightness: Brightness.dark, | ||
| 782 | + ), | ||
| 783 | + child: Scaffold( | ||
| 784 | + backgroundColor: colors.backgroundPage, | ||
| 785 | + appBar: const _SecurityEmailAppBar(), | ||
| 786 | + body: SafeArea( | ||
| 787 | + top: false, | ||
| 788 | + child: Padding( | ||
| 789 | + padding: const EdgeInsets.symmetric(horizontal: 28), | ||
| 790 | + child: Column( | ||
| 791 | + children: [ | ||
| 792 | + const SizedBox(height: 16), | ||
| 793 | + Text( | ||
| 794 | + context.l10n.verifyYourPassword, | ||
| 795 | + style: TextStyle( | ||
| 796 | + color: colors.textPrimary, | ||
| 797 | + fontSize: 24, | ||
| 798 | + fontWeight: FontWeight.w600, | ||
| 799 | + height: 1.25, | ||
| 800 | + ), | ||
| 801 | + ), | ||
| 802 | + const SizedBox(height: 8), | ||
| 803 | + Text( | ||
| 804 | + context.l10n.reEnterYourDoublefeelPasswordToContinue, | ||
| 805 | + textAlign: TextAlign.center, | ||
| 806 | + style: TextStyle( | ||
| 807 | + color: colors.textSecondary, | ||
| 808 | + fontSize: 14, | ||
| 809 | + fontWeight: FontWeight.w400, | ||
| 810 | + height: 1.43, | ||
| 811 | + ), | ||
| 812 | + ), | ||
| 813 | + const SizedBox(height: 32), | ||
| 814 | + Obx(() { | ||
| 815 | + final isObscured = c.isPasswordObscured.value; | ||
| 816 | + return TextField( | ||
| 817 | + textInputAction: TextInputAction.next, | ||
| 818 | + controller: c.passwordController, | ||
| 819 | + // keyboardType: TextInputType.visiblePassword, | ||
| 820 | + obscureText: isObscured, | ||
| 821 | + autofillHints: const [AutofillHints.password], | ||
| 822 | + obscuringCharacter: '*', | ||
| 823 | + contextMenuBuilder: (BuildContext context, | ||
| 824 | + EditableTextState editableTextState) { | ||
| 825 | + List<ContextMenuButtonItem> buttonItems = | ||
| 826 | + editableTextState.contextMenuButtonItems; | ||
| 827 | + buttonItems.removeWhere( | ||
| 828 | + (item) => item.type != ContextMenuButtonType.paste); | ||
| 829 | + return AdaptiveTextSelectionToolbar.buttonItems( | ||
| 830 | + anchors: editableTextState.contextMenuAnchors, | ||
| 831 | + buttonItems: buttonItems, | ||
| 832 | + ); | ||
| 833 | + }, | ||
| 834 | + inputFormatters: [ | ||
| 835 | + FilteringTextInputFormatter.deny(RegExp(r'\s')), | ||
| 836 | + FilteringTextInputFormatter.allow( | ||
| 837 | + RegExp(r'[\x21-\x7E]'), | ||
| 838 | + ), | ||
| 839 | + ], | ||
| 840 | + style: TextStyle( | ||
| 841 | + color: context.colors.textPrimary, | ||
| 842 | + fontSize: 16, | ||
| 843 | + fontWeight: FontWeight.w400, | ||
| 844 | + ), | ||
| 845 | + decoration: InputDecoration( | ||
| 846 | + hintText: l10n.emailLoginYourPassword, | ||
| 847 | + hintStyle: TextStyle( | ||
| 848 | + color: context.colors.textTertiary, | ||
| 849 | + fontSize: 16, | ||
| 850 | + ), | ||
| 851 | + counterText: '', | ||
| 852 | + filled: true, | ||
| 853 | + fillColor: Colors.white, | ||
| 854 | + contentPadding: const EdgeInsets.symmetric( | ||
| 855 | + horizontal: 20, | ||
| 856 | + vertical: 16, | ||
| 857 | + ), | ||
| 858 | + border: OutlineInputBorder( | ||
| 859 | + borderRadius: BorderRadius.circular(27), | ||
| 860 | + borderSide: BorderSide.none, | ||
| 861 | + ), | ||
| 862 | + enabledBorder: OutlineInputBorder( | ||
| 863 | + borderRadius: BorderRadius.circular(27), | ||
| 864 | + borderSide: BorderSide.none, | ||
| 865 | + ), | ||
| 866 | + focusedBorder: OutlineInputBorder( | ||
| 867 | + borderRadius: BorderRadius.circular(27), | ||
| 868 | + borderSide: BorderSide( | ||
| 869 | + color: context.colors.primary, | ||
| 870 | + width: 1, | ||
| 871 | + ), | ||
| 872 | + ), | ||
| 873 | + suffixIconConstraints: const BoxConstraints( | ||
| 874 | + minWidth: 24, | ||
| 875 | + minHeight: 24, | ||
| 876 | + ), | ||
| 877 | + suffixIcon: GestureDetector( | ||
| 878 | + onTap: c.togglePasswordVisibility, | ||
| 879 | + child: Padding( | ||
| 880 | + padding: const EdgeInsets.only(right: 16), | ||
| 881 | + child: Image.asset( | ||
| 882 | + isObscured | ||
| 883 | + ? 'assets/images/common/ic_hide_password.png' | ||
| 884 | + : 'assets/images/common/ic_show_password.webp', | ||
| 885 | + color: context.colors.textTertiary, | ||
| 886 | + width: 24, | ||
| 887 | + height: 24, | ||
| 888 | + ), | ||
| 889 | + ), | ||
| 890 | + ), | ||
| 891 | + ), | ||
| 892 | + onSubmitted: (_) => | ||
| 893 | + Get.to(() => AddSecurityEmailView(isChangeEmail: true)), | ||
| 894 | + ); | ||
| 895 | + }), | ||
| 896 | + const SizedBox(height: 8), | ||
| 897 | + Padding( | ||
| 898 | + padding: const EdgeInsets.symmetric(horizontal: 16), | ||
| 899 | + child: Text( | ||
| 900 | + textAlign: TextAlign.center, | ||
| 901 | + context.l10n | ||
| 902 | + .passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter, | ||
| 903 | + style: TextStyle( | ||
| 904 | + color: context.colors.textTertiary, | ||
| 905 | + fontSize: 12, | ||
| 906 | + fontWeight: FontWeight.w400, | ||
| 907 | + ), | ||
| 908 | + ), | ||
| 909 | + ), | ||
| 910 | + const SizedBox(height: 86), | ||
| 911 | + SizedBox( | ||
| 912 | + width: double.infinity, | ||
| 913 | + height: 48, | ||
| 914 | + child: Obx( | ||
| 915 | + () => ElevatedButton( | ||
| 916 | + onPressed: c.isPasswordValid | ||
| 917 | + ? () => Get.to( | ||
| 918 | + () => AddSecurityEmailView(isChangeEmail: true)) | ||
| 919 | + : null, | ||
| 920 | + style: ElevatedButton.styleFrom( | ||
| 921 | + backgroundColor: colors.primary, | ||
| 922 | + disabledBackgroundColor: | ||
| 923 | + colors.primary.withValues(alpha: 0.4), | ||
| 924 | + foregroundColor: Colors.white, | ||
| 925 | + disabledForegroundColor: Colors.white, | ||
| 926 | + elevation: 0, | ||
| 927 | + shape: const StadiumBorder(), | ||
| 928 | + textStyle: const TextStyle( | ||
| 929 | + fontSize: 16, | ||
| 930 | + fontWeight: FontWeight.w600, | ||
| 931 | + ), | ||
| 932 | + ), | ||
| 933 | + child: Text(l10n.watchThemeNext), | ||
| 934 | + ), | ||
| 935 | + ), | ||
| 936 | + ), | ||
| 937 | + ], | ||
| 938 | + ), | ||
| 939 | + ), | ||
| 940 | + ), | ||
| 941 | + ), | ||
| 942 | + ); | ||
| 943 | + } | ||
| 944 | +} |
| 1 | import 'dart:async'; | 1 | import 'dart:async'; |
| 2 | import 'dart:convert'; | 2 | import 'dart:convert'; |
| 3 | +import 'package:crypto/crypto.dart'; | ||
| 3 | import 'package:doublefeel_flutter/app/modules/login/views/email_login_new_password_view.dart'; | 4 | import 'package:doublefeel_flutter/app/modules/login/views/email_login_new_password_view.dart'; |
| 4 | import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart'; | 5 | import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart'; |
| 5 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; | 6 | import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; |
| @@ -86,6 +87,7 @@ class LoginController extends GetxController { | @@ -86,6 +87,7 @@ class LoginController extends GetxController { | ||
| 86 | bool get canEmailLogin => isEmailValid && isPasswordValid; | 87 | bool get canEmailLogin => isEmailValid && isPasswordValid; |
| 87 | 88 | ||
| 88 | AppleSignInModel? appleSignInModel; | 89 | AppleSignInModel? appleSignInModel; |
| 90 | + GoogleSignInModel? googleSignInModel; | ||
| 89 | 91 | ||
| 90 | bool _isToggling = false; | 92 | bool _isToggling = false; |
| 91 | 93 | ||
| @@ -169,19 +171,19 @@ class LoginController extends GetxController { | @@ -169,19 +171,19 @@ class LoginController extends GetxController { | ||
| 169 | void onGoogleLoginPressed() async { | 171 | void onGoogleLoginPressed() async { |
| 170 | try { | 172 | try { |
| 171 | final result = await LoadingService.instance.run(() async { | 173 | final result = await LoadingService.instance.run(() async { |
| 172 | - // return await PlatformHostApi().requestGoogleSignIn(); | 174 | + return await PlatformHostApi().requestGoogleSignIn(); |
| 173 | }, type: LoadingType.circular); | 175 | }, type: LoadingType.circular); |
| 174 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 | 176 | // result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败 |
| 175 | if (result == null || | 177 | if (result == null || |
| 176 | - result.identityToken?.isNotEmpty != true || | ||
| 177 | - result.userId.isNotEmpty != true) { | ||
| 178 | - appleSignInModel = null; | 178 | + result.idToken.isNotEmpty != true || |
| 179 | + result.clientID?.isNotEmpty != true) { | ||
| 180 | + googleSignInModel = null; | ||
| 179 | return; | 181 | return; |
| 180 | } | 182 | } |
| 181 | - appleSignInModel = result; | ||
| 182 | - await _loginWithApple(result); | 183 | + googleSignInModel = result; |
| 184 | + await _loginWithGoogle(result); | ||
| 183 | } catch (e) { | 185 | } catch (e) { |
| 184 | - appleSignInModel = null; | 186 | + googleSignInModel = null; |
| 185 | AppToast.show(e.toString()); | 187 | AppToast.show(e.toString()); |
| 186 | } | 188 | } |
| 187 | } | 189 | } |
| @@ -335,6 +337,7 @@ class LoginController extends GetxController { | @@ -335,6 +337,7 @@ class LoginController extends GetxController { | ||
| 335 | if (loginRes is AppSuccess<LoginResponse>) { | 337 | if (loginRes is AppSuccess<LoginResponse>) { |
| 336 | await _handleLoginSuccess(loginRes.data); | 338 | await _handleLoginSuccess(loginRes.data); |
| 337 | await Get.find<LocalStorage>().setLastLoginMethod('phone'); | 339 | await Get.find<LocalStorage>().setLastLoginMethod('phone'); |
| 340 | + await environmentConfig.updateHideChooseRegion(true); | ||
| 338 | } | 341 | } |
| 339 | }); | 342 | }); |
| 340 | } finally { | 343 | } finally { |
| @@ -355,22 +358,23 @@ class LoginController extends GetxController { | @@ -355,22 +358,23 @@ class LoginController extends GetxController { | ||
| 355 | return; | 358 | return; |
| 356 | } | 359 | } |
| 357 | 360 | ||
| 358 | - // TODO: 邮箱登录接口待接入,API 路径由后端确认后在此调用 | ||
| 359 | - // isLoggingIn.value = true; | ||
| 360 | - // try { | ||
| 361 | - // await LoadingService.instance.run(() async { | ||
| 362 | - // final loginRes = await _userApi.loginWithEmail( | ||
| 363 | - // email: emailInput.value.trim(), | ||
| 364 | - // password: passwordInput.value, | ||
| 365 | - // ); | ||
| 366 | - // if (loginRes is AppSuccess<LoginResponse>) { | ||
| 367 | - // await _handleLoginSuccess(loginRes.data); | ||
| 368 | - // await Get.find<LocalStorage>().setLastLoginMethod('email'); | ||
| 369 | - // } | ||
| 370 | - // }); | ||
| 371 | - // } finally { | ||
| 372 | - // isLoggingIn.value = false; | ||
| 373 | - // } | 361 | + isLoggingIn.value = true; |
| 362 | + try { | ||
| 363 | + final md5Password = | ||
| 364 | + md5.convert(utf8.encode(passwordInput.value)).toString(); | ||
| 365 | + await LoadingService.instance.run(() async { | ||
| 366 | + final loginRes = await _userApi.loginWithEmail( | ||
| 367 | + email: emailInput.value.trim(), | ||
| 368 | + password: md5Password, | ||
| 369 | + ); | ||
| 370 | + if (loginRes is AppSuccess<LoginResponse>) { | ||
| 371 | + await _handleLoginSuccess(loginRes.data, emailPassword: md5Password); | ||
| 372 | + await Get.find<LocalStorage>().setLastLoginMethod('email'); | ||
| 373 | + } | ||
| 374 | + }); | ||
| 375 | + } finally { | ||
| 376 | + isLoggingIn.value = false; | ||
| 377 | + } | ||
| 374 | var success = true; | 378 | var success = true; |
| 375 | if (success) { | 379 | if (success) { |
| 376 | TextInput.finishAutofillContext(); | 380 | TextInput.finishAutofillContext(); |
| @@ -452,25 +456,62 @@ class LoginController extends GetxController { | @@ -452,25 +456,62 @@ class LoginController extends GetxController { | ||
| 452 | } | 456 | } |
| 453 | } | 457 | } |
| 454 | 458 | ||
| 459 | + Future<void> _loginWithGoogle(GoogleSignInModel googleModel) async { | ||
| 460 | + isLoggingIn.value = true; | ||
| 461 | + try { | ||
| 462 | + await LoadingService.instance.run(() async { | ||
| 463 | + final loginRes = await _userApi.loginWithGoogle(googleModel); | ||
| 464 | + | ||
| 465 | + if (loginRes is AppSuccess<LoginResponse>) { | ||
| 466 | + await _handleLoginSuccess(loginRes.data, googleModel: googleModel); | ||
| 467 | + await Get.find<LocalStorage>().setLastLoginMethod('google'); | ||
| 468 | + await environmentConfig.updateHideChooseRegion(true); | ||
| 469 | + } | ||
| 470 | + }); | ||
| 471 | + } finally { | ||
| 472 | + isLoggingIn.value = false; | ||
| 473 | + } | ||
| 474 | + } | ||
| 475 | + | ||
| 455 | /// 登录/注册成功后的公共处理逻辑(手机号登录与苹果登录共用) | 476 | /// 登录/注册成功后的公共处理逻辑(手机号登录与苹果登录共用) |
| 456 | Future<void> _handleLoginSuccess(LoginResponse loginData, | 477 | Future<void> _handleLoginSuccess(LoginResponse loginData, |
| 457 | - {AppleSignInModel? appleModel}) async { | 478 | + {AppleSignInModel? appleModel, |
| 479 | + GoogleSignInModel? googleModel, | ||
| 480 | + String? emailPassword}) async { | ||
| 458 | bool isApple = appleModel != null; | 481 | bool isApple = appleModel != null; |
| 482 | + bool isGoogle = googleModel != null; | ||
| 483 | + bool isEmail = emailPassword != null; | ||
| 459 | final isRegister = loginData.isNewUser == true; | 484 | final isRegister = loginData.isNewUser == true; |
| 460 | String accessToken = loginData.accessToken; | 485 | String accessToken = loginData.accessToken; |
| 461 | 486 | ||
| 462 | if (isRegister) { | 487 | if (isRegister) { |
| 463 | - final regRes = await _userApi.register(isApple | ||
| 464 | - ? { | ||
| 465 | - 'login_type': 'apple', | ||
| 466 | - 'apple_user_id': appleModel.userId, | ||
| 467 | - 'identity_token': appleModel.identityToken, | ||
| 468 | - } | ||
| 469 | - : { | ||
| 470 | - 'login_type': 'verification_code', | ||
| 471 | - 'telephone': cleanPhone, | ||
| 472 | - 'verification_code': codeInput.value, | ||
| 473 | - }); | 488 | + Object? registerData; |
| 489 | + if (isApple) { | ||
| 490 | + registerData = { | ||
| 491 | + 'login_type': 'apple', | ||
| 492 | + 'apple_user_id': appleModel.userId, | ||
| 493 | + 'identity_token': appleModel.identityToken, | ||
| 494 | + }; | ||
| 495 | + } else if (isGoogle) { | ||
| 496 | + registerData = { | ||
| 497 | + 'login_type': 'google', | ||
| 498 | + 'identity_token': googleModel.idToken, | ||
| 499 | + 'expected_client_id': googleModel.clientID, | ||
| 500 | + }; | ||
| 501 | + } else if (isEmail) { | ||
| 502 | + registerData = { | ||
| 503 | + 'login_type': 'email', | ||
| 504 | + 'email': emailInput.value.trim(), | ||
| 505 | + 'password': emailPassword, | ||
| 506 | + }; | ||
| 507 | + } else { | ||
| 508 | + registerData = { | ||
| 509 | + 'login_type': 'verification_code', | ||
| 510 | + 'telephone': cleanPhone, | ||
| 511 | + 'verification_code': codeInput.value, | ||
| 512 | + }; | ||
| 513 | + } | ||
| 514 | + final regRes = await _userApi.register(registerData); | ||
| 474 | if (regRes is AppSuccess<RegisterResponse>) { | 515 | if (regRes is AppSuccess<RegisterResponse>) { |
| 475 | accessToken = regRes.data.accessToken; | 516 | accessToken = regRes.data.accessToken; |
| 476 | } else { | 517 | } else { |
| 1 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; | 1 | import 'package:doublefeel_flutter/core/theme/app_theme.dart'; |
| 2 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; | 2 | import 'package:doublefeel_flutter/core/util/app_toast.dart'; |
| 3 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; | 3 | import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; |
| 4 | +import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; | ||
| 4 | import 'package:flutter/material.dart'; | 5 | import 'package:flutter/material.dart'; |
| 5 | import 'package:flutter/services.dart'; | 6 | import 'package:flutter/services.dart'; |
| 6 | import 'package:get/get.dart'; | 7 | import 'package:get/get.dart'; |
| @@ -127,28 +128,10 @@ Future<void> sendFeedbackEmail({ | @@ -127,28 +128,10 @@ Future<void> sendFeedbackEmail({ | ||
| 127 | final String body = | 128 | final String body = |
| 128 | 'This issue was reported from $pageName by User ID: $cleanUserId.'; | 129 | 'This issue was reported from $pageName by User ID: $cleanUserId.'; |
| 129 | 130 | ||
| 130 | - final Uri emailUri = Uri( | ||
| 131 | - scheme: 'mailto', | ||
| 132 | - path: 'support@doublefeel.cn', | ||
| 133 | - query: _encodeQueryParameters({ | ||
| 134 | - 'subject': 'DoubleFeel Feedback', | ||
| 135 | - 'body': body, | ||
| 136 | - }), | ||
| 137 | - ); | ||
| 138 | - | ||
| 139 | - // try { | ||
| 140 | - // await launchUrl( | ||
| 141 | - // emailUri, | ||
| 142 | - // mode: LaunchMode.externalApplication, | ||
| 143 | - // ); | ||
| 144 | - // } catch (_) {} | ||
| 145 | -} | ||
| 146 | - | ||
| 147 | -String _encodeQueryParameters(Map<String, String> params) { | ||
| 148 | - return params.entries | ||
| 149 | - .map((e) => | ||
| 150 | - '${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}') | ||
| 151 | - .join('&'); | 131 | + try { |
| 132 | + await PlatformHostApi() | ||
| 133 | + .sendEmail('support@doublefeel.cn', 'DoubleFeel Feedback', body); | ||
| 134 | + } catch (_) {} | ||
| 152 | } | 135 | } |
| 153 | 136 | ||
| 154 | class _UserIdField extends StatelessWidget { | 137 | class _UserIdField extends StatelessWidget { |
| @@ -226,5 +226,14 @@ abstract final class AppPages { | @@ -226,5 +226,14 @@ abstract final class AppPages { | ||
| 226 | ); | 226 | ); |
| 227 | }), | 227 | }), |
| 228 | ), | 228 | ), |
| 229 | + GetPage( | ||
| 230 | + name: Routes.CHANGE_SECURITY_EMAIL, | ||
| 231 | + page: () => const VerifyEmailPwdView(), | ||
| 232 | + binding: BindingsBuilder(() { | ||
| 233 | + Get.lazyPut<AddSecurityEmailController>( | ||
| 234 | + () => AddSecurityEmailController(), | ||
| 235 | + ); | ||
| 236 | + }), | ||
| 237 | + ), | ||
| 229 | ]; | 238 | ]; |
| 230 | } | 239 | } |
| @@ -27,6 +27,7 @@ abstract class Routes { | @@ -27,6 +27,7 @@ abstract class Routes { | ||
| 27 | static const FRIEND_HOME = _Paths.FRIEND_HOME; | 27 | static const FRIEND_HOME = _Paths.FRIEND_HOME; |
| 28 | static const APPLE_HEALTH_UPLOAD_TEST = _Paths.APPLE_HEALTH_UPLOAD_TEST; | 28 | static const APPLE_HEALTH_UPLOAD_TEST = _Paths.APPLE_HEALTH_UPLOAD_TEST; |
| 29 | static const ADD_SECURITY_EMAIL = _Paths.ADD_SECURITY_EMAIL; | 29 | static const ADD_SECURITY_EMAIL = _Paths.ADD_SECURITY_EMAIL; |
| 30 | + static const CHANGE_SECURITY_EMAIL = _Paths.CHANGE_SECURITY_EMAIL; | ||
| 30 | } | 31 | } |
| 31 | 32 | ||
| 32 | abstract class _Paths { | 33 | abstract class _Paths { |
| @@ -55,4 +56,5 @@ abstract class _Paths { | @@ -55,4 +56,5 @@ abstract class _Paths { | ||
| 55 | static const FRIEND_HOME = '/friend-home'; | 56 | static const FRIEND_HOME = '/friend-home'; |
| 56 | static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test'; | 57 | static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test'; |
| 57 | static const ADD_SECURITY_EMAIL = '/add-security-email'; | 58 | static const ADD_SECURITY_EMAIL = '/add-security-email'; |
| 59 | + static const CHANGE_SECURITY_EMAIL = '/change-security-email'; | ||
| 58 | } | 60 | } |
| @@ -45,6 +45,26 @@ class UserApi { | @@ -45,6 +45,26 @@ class UserApi { | ||
| 45 | ); | 45 | ); |
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | + Future<AppResult<LoginResponse>> loginWithEmail({ | ||
| 49 | + required String email, | ||
| 50 | + required String password, | ||
| 51 | + }) { | ||
| 52 | + return safeCall( | ||
| 53 | + call: () async { | ||
| 54 | + final response = await _dioClient.dio.post( | ||
| 55 | + ApiPaths.userLogin, | ||
| 56 | + data: { | ||
| 57 | + 'login_type': 'email', | ||
| 58 | + 'email': email, | ||
| 59 | + 'password': password, | ||
| 60 | + }, | ||
| 61 | + options: _dioClient.noTokenOptions(), | ||
| 62 | + ); | ||
| 63 | + return LoginResponse.fromJson(response.data as Map<String, dynamic>); | ||
| 64 | + }, | ||
| 65 | + ); | ||
| 66 | + } | ||
| 67 | + | ||
| 48 | Future<AppResult<LoginResponse>> loginWithApple(AppleSignInModel appleModel) { | 68 | Future<AppResult<LoginResponse>> loginWithApple(AppleSignInModel appleModel) { |
| 49 | return safeCall( | 69 | return safeCall( |
| 50 | call: () async { | 70 | call: () async { |
| @@ -67,6 +87,30 @@ class UserApi { | @@ -67,6 +87,30 @@ class UserApi { | ||
| 67 | ); | 87 | ); |
| 68 | } | 88 | } |
| 69 | 89 | ||
| 90 | + Future<AppResult<LoginResponse>> loginWithGoogle( | ||
| 91 | + GoogleSignInModel googleModel) { | ||
| 92 | + return safeCall( | ||
| 93 | + call: () async { | ||
| 94 | + var googleLoginRequest = GoogleLoginRequest( | ||
| 95 | + identityToken: googleModel.idToken, | ||
| 96 | + userId: googleModel.clientID ?? ''); | ||
| 97 | + if (googleModel.email != null) { | ||
| 98 | + googleLoginRequest.email = googleModel.email; | ||
| 99 | + } | ||
| 100 | + if (googleModel.nickname != null) { | ||
| 101 | + googleLoginRequest.nickname = googleModel.nickname; | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + final response = await _dioClient.dio.post( | ||
| 105 | + ApiPaths.userLogin, | ||
| 106 | + data: googleLoginRequest.toJson(), | ||
| 107 | + options: _dioClient.noTokenOptions(), | ||
| 108 | + ); | ||
| 109 | + return LoginResponse.fromJson(response.data as Map<String, dynamic>); | ||
| 110 | + }, | ||
| 111 | + ); | ||
| 112 | + } | ||
| 113 | + | ||
| 70 | Future<AppResult<RegisterResponse>> register(Object? registerData) { | 114 | Future<AppResult<RegisterResponse>> register(Object? registerData) { |
| 71 | return safeCall( | 115 | return safeCall( |
| 72 | call: () async { | 116 | call: () async { |
| @@ -238,4 +282,46 @@ class UserApi { | @@ -238,4 +282,46 @@ class UserApi { | ||
| 238 | }, | 282 | }, |
| 239 | ); | 283 | ); |
| 240 | } | 284 | } |
| 285 | + | ||
| 286 | + Future<AppResult<void>> requestEmailVerifyCode(String email) { | ||
| 287 | + return safeCall( | ||
| 288 | + call: () async { | ||
| 289 | + await _dioClient.dio.post( | ||
| 290 | + ApiPaths.emailSendCode, | ||
| 291 | + data: { | ||
| 292 | + 'safety_email': email, | ||
| 293 | + }, | ||
| 294 | + ); | ||
| 295 | + }, | ||
| 296 | + ); | ||
| 297 | + } | ||
| 298 | + | ||
| 299 | + Future<AppResult<void>> verifyEmailVerifyCode(String email, String code) { | ||
| 300 | + return safeCall( | ||
| 301 | + call: () async { | ||
| 302 | + await _dioClient.dio.post( | ||
| 303 | + ApiPaths.emailVerifyCode, | ||
| 304 | + data: { | ||
| 305 | + 'safety_email': email, | ||
| 306 | + 'safety_email_code': code, | ||
| 307 | + }, | ||
| 308 | + ); | ||
| 309 | + }, | ||
| 310 | + ); | ||
| 311 | + } | ||
| 312 | + | ||
| 313 | + Future<AppResult<void>> addSefetyEmail( | ||
| 314 | + String email, String code, String pwd) { | ||
| 315 | + return safeCall( | ||
| 316 | + call: () async { | ||
| 317 | + await _dioClient.dio.post( | ||
| 318 | + ApiPaths.addSafetyEmail, | ||
| 319 | + data: { | ||
| 320 | + 'safety_email': email, | ||
| 321 | + 'safety_email_code': code, | ||
| 322 | + }, | ||
| 323 | + ); | ||
| 324 | + }, | ||
| 325 | + ); | ||
| 326 | + } | ||
| 241 | } | 327 | } |
| @@ -4,6 +4,11 @@ abstract final class ApiPaths { | @@ -4,6 +4,11 @@ abstract final class ApiPaths { | ||
| 4 | static const userRegister = '/client/doublefeel/user/register/'; | 4 | static const userRegister = '/client/doublefeel/user/register/'; |
| 5 | static const userLogin = '/client/doublefeel/user/login/'; | 5 | static const userLogin = '/client/doublefeel/user/login/'; |
| 6 | static const smsSendCode = '/client/doublefeel/sms/send_code/'; | 6 | static const smsSendCode = '/client/doublefeel/sms/send_code/'; |
| 7 | + static const emailSendCode = | ||
| 8 | + '/client/doublefeel/safety_email/send_verify_code/'; | ||
| 9 | + static const emailVerifyCode = '/client/doublefeel/safety_email/verify_code/'; | ||
| 10 | + static const addSafetyEmail = | ||
| 11 | + '/client/doublefeel/safety_email/add_safety_email/'; | ||
| 7 | static const userPair = '/client/doublefeel/user/pair/'; | 12 | static const userPair = '/client/doublefeel/user/pair/'; |
| 8 | static const userLogout = '/client/doublefeel/user/logout/'; | 13 | static const userLogout = '/client/doublefeel/user/logout/'; |
| 9 | static const userDeleteAccount = '/client/doublefeel/user/account/delete/'; | 14 | static const userDeleteAccount = '/client/doublefeel/user/account/delete/'; |
| @@ -42,6 +42,30 @@ class AppleLoginRequest { | @@ -42,6 +42,30 @@ class AppleLoginRequest { | ||
| 42 | }; | 42 | }; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | +class GoogleLoginRequest { | ||
| 46 | + GoogleLoginRequest({ | ||
| 47 | + this.loginType = 'google', | ||
| 48 | + required this.identityToken, | ||
| 49 | + required this.userId, | ||
| 50 | + this.email, | ||
| 51 | + this.nickname, | ||
| 52 | + }); | ||
| 53 | + | ||
| 54 | + final String loginType; | ||
| 55 | + final String identityToken; | ||
| 56 | + final String userId; | ||
| 57 | + String? email; | ||
| 58 | + String? nickname; | ||
| 59 | + | ||
| 60 | + Map<String, dynamic> toJson() => { | ||
| 61 | + 'login_type': loginType, | ||
| 62 | + 'expected_client_id': userId, | ||
| 63 | + 'identity_token': identityToken, | ||
| 64 | + if (email != null) 'email': email, | ||
| 65 | + if (nickname != null) 'nickname': nickname, | ||
| 66 | + }; | ||
| 67 | +} | ||
| 68 | + | ||
| 45 | class VerifyCodeRequest { | 69 | class VerifyCodeRequest { |
| 46 | const VerifyCodeRequest({required this.telephone, required this.scene}); | 70 | const VerifyCodeRequest({required this.telephone, required this.scene}); |
| 47 | 71 |
| @@ -353,9 +353,9 @@ | @@ -353,9 +353,9 @@ | ||
| 353 | "sleepQualityGreat": "Great sleep", | 353 | "sleepQualityGreat": "Great sleep", |
| 354 | "sleepQualityGood": "Good sleep", | 354 | "sleepQualityGood": "Good sleep", |
| 355 | "sleepQualityPoor": "Poor sleep", | 355 | "sleepQualityPoor": "Poor sleep", |
| 356 | - "sleepQualityExcellent": "Excellent", | ||
| 357 | - "sleepQualityNormal": "Normal", | ||
| 358 | - "sleepQualityAttention": "Needs attention", | 356 | + "sleepQualityExcellent": "Great", |
| 357 | + "sleepQualityNormal": "Good", | ||
| 358 | + "sleepQualityAttention": "Poor", | ||
| 359 | "hrvDailyStressTrend": "Daily Stress Trend", | 359 | "hrvDailyStressTrend": "Daily Stress Trend", |
| 360 | "hrvMonthlyStressTrend": "Monthly Stress Trend", | 360 | "hrvMonthlyStressTrend": "Monthly Stress Trend", |
| 361 | "hrvMoreRelaxed": "More relaxed", | 361 | "hrvMoreRelaxed": "More relaxed", |
| @@ -460,14 +460,14 @@ | @@ -460,14 +460,14 @@ | ||
| 460 | "friendsWaitingForData": "Waiting for data", | 460 | "friendsWaitingForData": "Waiting for data", |
| 461 | "friendsSleepQuality": "Sleep quality", | 461 | "friendsSleepQuality": "Sleep quality", |
| 462 | "friendsTodaySteps": "Steps today", | 462 | "friendsTodaySteps": "Steps today", |
| 463 | - "friendsSleepQualityExcellent": "Slept great", | ||
| 464 | - "friendsSleepQualityNormal": "Slept well", | ||
| 465 | - "friendsSleepQualityAttention": "Slept poorly", | 463 | + "friendsSleepQualityExcellent": "Great Sleep", |
| 464 | + "friendsSleepQualityNormal": "Good Sleep", | ||
| 465 | + "friendsSleepQualityAttention": "Poor Sleep", | ||
| 466 | "friendsRemove": "Remove", | 466 | "friendsRemove": "Remove", |
| 467 | "friendsEditRemark": "Edit Note", | 467 | "friendsEditRemark": "Edit Note", |
| 468 | "friendsShowOnWatchFace": "Show on Watch", | 468 | "friendsShowOnWatchFace": "Show on Watch", |
| 469 | "friendsShownOnWatchFace": "Shown on Watch", | 469 | "friendsShownOnWatchFace": "Shown on Watch", |
| 470 | - "friendsSelect": "Select a friend", | 470 | + "friendsSelect": "Choose a Loved One", |
| 471 | "friendsSelectAndSync": "Select and sync to Watch", | 471 | "friendsSelectAndSync": "Select and sync to Watch", |
| 472 | "friendsBack": "Back", | 472 | "friendsBack": "Back", |
| 473 | "friendsTrendTitle": "{name}'s Trends", | 473 | "friendsTrendTitle": "{name}'s Trends", |
| @@ -577,7 +577,7 @@ | @@ -577,7 +577,7 @@ | ||
| 577 | "membersCanViewTheCompleteData": "Unlock Pro to view", | 577 | "membersCanViewTheCompleteData": "Unlock Pro to view", |
| 578 | "unlockNow": "Unlock", | 578 | "unlockNow": "Unlock", |
| 579 | "pressureOverload": "Overload", | 579 | "pressureOverload": "Overload", |
| 580 | - "beMindfulOfStress": "Stressful", | 580 | + "beMindfulOfStress": "Pay Attention", |
| 581 | "statusNormal": "Normal", | 581 | "statusNormal": "Normal", |
| 582 | "inExcellentCondition": "Excellent", | 582 | "inExcellentCondition": "Excellent", |
| 583 | "waitingForData": "Waiting for data", | 583 | "waitingForData": "Waiting for data", |
| @@ -868,5 +868,12 @@ | @@ -868,5 +868,12 @@ | ||
| 868 | "monthlyMembership": "Monthly", | 868 | "monthlyMembership": "Monthly", |
| 869 | "quarterlyMembership": "Quarterly", | 869 | "quarterlyMembership": "Quarterly", |
| 870 | "annualMembership": "Annual", | 870 | "annualMembership": "Annual", |
| 871 | - "lifetimeMembership": "Lifetime" | 871 | + "lifetimeMembership": "Lifetime", |
| 872 | + "verifyYourPassword": "Verify your password", | ||
| 873 | + "reEnterYourDoublefeelPasswordToContinue": "Re-enter your DoubleFeel password to continue.", | ||
| 874 | + "changeEmail": "Change Email", | ||
| 875 | + "yourCurrentEmailIs": "Your current email is", | ||
| 876 | + "whatWouldYouLikeToUpdateItTo": ". What would you like to update it to?", | ||
| 877 | + "successChanged": "Success changed", | ||
| 878 | + "verifyEmailFailed": "Verify email failed" | ||
| 872 | } | 879 | } |
| @@ -1261,5 +1261,12 @@ | @@ -1261,5 +1261,12 @@ | ||
| 1261 | "monthlyMembership": "月度会员", | 1261 | "monthlyMembership": "月度会员", |
| 1262 | "quarterlyMembership": "季度会员", | 1262 | "quarterlyMembership": "季度会员", |
| 1263 | "annualMembership": "年度会员", | 1263 | "annualMembership": "年度会员", |
| 1264 | - "lifetimeMembership": "终身会员" | 1264 | + "lifetimeMembership": "终身会员", |
| 1265 | + "verifyYourPassword": "请确认您的密码", | ||
| 1266 | + "reEnterYourDoublefeelPasswordToContinue": "请重新输入您的 DoubleFeel 密码以继续。", | ||
| 1267 | + "changeEmail": "更改电子邮件地址", | ||
| 1268 | + "yourCurrentEmailIs": "您当前的电子邮箱是", | ||
| 1269 | + "whatWouldYouLikeToUpdateItTo": ". 您想将其更新为什么内容?", | ||
| 1270 | + "successChanged": "成功发生了变化", | ||
| 1271 | + "verifyEmailFailed": "邮箱验证失败" | ||
| 1265 | } | 1272 | } |
| @@ -4573,6 +4573,48 @@ abstract class AppLocalizations { | @@ -4573,6 +4573,48 @@ abstract class AppLocalizations { | ||
| 4573 | /// In zh, this message translates to: | 4573 | /// In zh, this message translates to: |
| 4574 | /// **'终身会员'** | 4574 | /// **'终身会员'** |
| 4575 | String get lifetimeMembership; | 4575 | String get lifetimeMembership; |
| 4576 | + | ||
| 4577 | + /// No description provided for @verifyYourPassword. | ||
| 4578 | + /// | ||
| 4579 | + /// In zh, this message translates to: | ||
| 4580 | + /// **'请确认您的密码'** | ||
| 4581 | + String get verifyYourPassword; | ||
| 4582 | + | ||
| 4583 | + /// No description provided for @reEnterYourDoublefeelPasswordToContinue. | ||
| 4584 | + /// | ||
| 4585 | + /// In zh, this message translates to: | ||
| 4586 | + /// **'请重新输入您的 DoubleFeel 密码以继续。'** | ||
| 4587 | + String get reEnterYourDoublefeelPasswordToContinue; | ||
| 4588 | + | ||
| 4589 | + /// No description provided for @changeEmail. | ||
| 4590 | + /// | ||
| 4591 | + /// In zh, this message translates to: | ||
| 4592 | + /// **'更改电子邮件地址'** | ||
| 4593 | + String get changeEmail; | ||
| 4594 | + | ||
| 4595 | + /// No description provided for @yourCurrentEmailIs. | ||
| 4596 | + /// | ||
| 4597 | + /// In zh, this message translates to: | ||
| 4598 | + /// **'您当前的电子邮箱是'** | ||
| 4599 | + String get yourCurrentEmailIs; | ||
| 4600 | + | ||
| 4601 | + /// No description provided for @whatWouldYouLikeToUpdateItTo. | ||
| 4602 | + /// | ||
| 4603 | + /// In zh, this message translates to: | ||
| 4604 | + /// **'. 您想将其更新为什么内容?'** | ||
| 4605 | + String get whatWouldYouLikeToUpdateItTo; | ||
| 4606 | + | ||
| 4607 | + /// No description provided for @successChanged. | ||
| 4608 | + /// | ||
| 4609 | + /// In zh, this message translates to: | ||
| 4610 | + /// **'成功发生了变化'** | ||
| 4611 | + String get successChanged; | ||
| 4612 | + | ||
| 4613 | + /// No description provided for @verifyEmailFailed. | ||
| 4614 | + /// | ||
| 4615 | + /// In zh, this message translates to: | ||
| 4616 | + /// **'邮箱验证失败'** | ||
| 4617 | + String get verifyEmailFailed; | ||
| 4576 | } | 4618 | } |
| 4577 | 4619 | ||
| 4578 | class _AppLocalizationsDelegate | 4620 | class _AppLocalizationsDelegate |
| @@ -1177,13 +1177,13 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1177,13 +1177,13 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1177 | String get sleepQualityPoor => 'Poor sleep'; | 1177 | String get sleepQualityPoor => 'Poor sleep'; |
| 1178 | 1178 | ||
| 1179 | @override | 1179 | @override |
| 1180 | - String get sleepQualityExcellent => 'Excellent'; | 1180 | + String get sleepQualityExcellent => 'Great'; |
| 1181 | 1181 | ||
| 1182 | @override | 1182 | @override |
| 1183 | - String get sleepQualityNormal => 'Normal'; | 1183 | + String get sleepQualityNormal => 'Good'; |
| 1184 | 1184 | ||
| 1185 | @override | 1185 | @override |
| 1186 | - String get sleepQualityAttention => 'Needs attention'; | 1186 | + String get sleepQualityAttention => 'Poor'; |
| 1187 | 1187 | ||
| 1188 | @override | 1188 | @override |
| 1189 | String get hrvDailyStressTrend => 'Daily Stress Trend'; | 1189 | String get hrvDailyStressTrend => 'Daily Stress Trend'; |
| @@ -1553,13 +1553,13 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1553,13 +1553,13 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1553 | String get friendsTodaySteps => 'Steps today'; | 1553 | String get friendsTodaySteps => 'Steps today'; |
| 1554 | 1554 | ||
| 1555 | @override | 1555 | @override |
| 1556 | - String get friendsSleepQualityExcellent => 'Slept great'; | 1556 | + String get friendsSleepQualityExcellent => 'Great Sleep'; |
| 1557 | 1557 | ||
| 1558 | @override | 1558 | @override |
| 1559 | - String get friendsSleepQualityNormal => 'Slept well'; | 1559 | + String get friendsSleepQualityNormal => 'Good Sleep'; |
| 1560 | 1560 | ||
| 1561 | @override | 1561 | @override |
| 1562 | - String get friendsSleepQualityAttention => 'Slept poorly'; | 1562 | + String get friendsSleepQualityAttention => 'Poor Sleep'; |
| 1563 | 1563 | ||
| 1564 | @override | 1564 | @override |
| 1565 | String get friendsRemove => 'Remove'; | 1565 | String get friendsRemove => 'Remove'; |
| @@ -1574,7 +1574,7 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1574,7 +1574,7 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1574 | String get friendsShownOnWatchFace => 'Shown on Watch'; | 1574 | String get friendsShownOnWatchFace => 'Shown on Watch'; |
| 1575 | 1575 | ||
| 1576 | @override | 1576 | @override |
| 1577 | - String get friendsSelect => 'Select a friend'; | 1577 | + String get friendsSelect => 'Choose a Loved One'; |
| 1578 | 1578 | ||
| 1579 | @override | 1579 | @override |
| 1580 | String get friendsSelectAndSync => 'Select and sync to Watch'; | 1580 | String get friendsSelectAndSync => 'Select and sync to Watch'; |
| @@ -1952,7 +1952,7 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -1952,7 +1952,7 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 1952 | String get pressureOverload => 'Overload'; | 1952 | String get pressureOverload => 'Overload'; |
| 1953 | 1953 | ||
| 1954 | @override | 1954 | @override |
| 1955 | - String get beMindfulOfStress => 'Stressful'; | 1955 | + String get beMindfulOfStress => 'Pay Attention'; |
| 1956 | 1956 | ||
| 1957 | @override | 1957 | @override |
| 1958 | String get statusNormal => 'Normal'; | 1958 | String get statusNormal => 'Normal'; |
| @@ -2562,4 +2562,27 @@ class AppLocalizationsEn extends AppLocalizations { | @@ -2562,4 +2562,27 @@ class AppLocalizationsEn extends AppLocalizations { | ||
| 2562 | 2562 | ||
| 2563 | @override | 2563 | @override |
| 2564 | String get lifetimeMembership => 'Lifetime'; | 2564 | String get lifetimeMembership => 'Lifetime'; |
| 2565 | + | ||
| 2566 | + @override | ||
| 2567 | + String get verifyYourPassword => 'Verify your password'; | ||
| 2568 | + | ||
| 2569 | + @override | ||
| 2570 | + String get reEnterYourDoublefeelPasswordToContinue => | ||
| 2571 | + 'Re-enter your DoubleFeel password to continue.'; | ||
| 2572 | + | ||
| 2573 | + @override | ||
| 2574 | + String get changeEmail => 'Change Email'; | ||
| 2575 | + | ||
| 2576 | + @override | ||
| 2577 | + String get yourCurrentEmailIs => 'Your current email is'; | ||
| 2578 | + | ||
| 2579 | + @override | ||
| 2580 | + String get whatWouldYouLikeToUpdateItTo => | ||
| 2581 | + '. What would you like to update it to?'; | ||
| 2582 | + | ||
| 2583 | + @override | ||
| 2584 | + String get successChanged => 'Success changed'; | ||
| 2585 | + | ||
| 2586 | + @override | ||
| 2587 | + String get verifyEmailFailed => 'Verify email failed'; | ||
| 2565 | } | 2588 | } |
| @@ -2463,4 +2463,26 @@ class AppLocalizationsZh extends AppLocalizations { | @@ -2463,4 +2463,26 @@ class AppLocalizationsZh extends AppLocalizations { | ||
| 2463 | 2463 | ||
| 2464 | @override | 2464 | @override |
| 2465 | String get lifetimeMembership => '终身会员'; | 2465 | String get lifetimeMembership => '终身会员'; |
| 2466 | + | ||
| 2467 | + @override | ||
| 2468 | + String get verifyYourPassword => '请确认您的密码'; | ||
| 2469 | + | ||
| 2470 | + @override | ||
| 2471 | + String get reEnterYourDoublefeelPasswordToContinue => | ||
| 2472 | + '请重新输入您的 DoubleFeel 密码以继续。'; | ||
| 2473 | + | ||
| 2474 | + @override | ||
| 2475 | + String get changeEmail => '更改电子邮件地址'; | ||
| 2476 | + | ||
| 2477 | + @override | ||
| 2478 | + String get yourCurrentEmailIs => '您当前的电子邮箱是'; | ||
| 2479 | + | ||
| 2480 | + @override | ||
| 2481 | + String get whatWouldYouLikeToUpdateItTo => '. 您想将其更新为什么内容?'; | ||
| 2482 | + | ||
| 2483 | + @override | ||
| 2484 | + String get successChanged => '成功发生了变化'; | ||
| 2485 | + | ||
| 2486 | + @override | ||
| 2487 | + String get verifyEmailFailed => '邮箱验证失败'; | ||
| 2466 | } | 2488 | } |
| @@ -13,12 +13,13 @@ class AppleSignInModel { | @@ -13,12 +13,13 @@ class AppleSignInModel { | ||
| 13 | this.identityToken, | 13 | this.identityToken, |
| 14 | }); | 14 | }); |
| 15 | } | 15 | } |
| 16 | -class GoogleSignInModel{ | ||
| 17 | - final String? email; | ||
| 18 | - final String? clientID; | ||
| 19 | - final String idToken; | ||
| 20 | - final String? nickname; | ||
| 21 | - final String? avatarUrl; | 16 | + |
| 17 | +class GoogleSignInModel { | ||
| 18 | + final String? email = null; | ||
| 19 | + final String? clientID = null; | ||
| 20 | + final String idToken = ''; | ||
| 21 | + final String? nickname = null; | ||
| 22 | + final String? avatarUrl = null; | ||
| 22 | } | 23 | } |
| 23 | 24 | ||
| 24 | class WatchAppOtherInfo { | 25 | class WatchAppOtherInfo { |
| @@ -122,7 +123,7 @@ abstract class PlatformHostApi { | @@ -122,7 +123,7 @@ abstract class PlatformHostApi { | ||
| 122 | /// 返回完整的 User-Agent 字符串,由 native 侧组装: | 123 | /// 返回完整的 User-Agent 字符串,由 native 侧组装: |
| 123 | /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)` | 124 | /// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)` |
| 124 | String getFullUserAgent(); | 125 | String getFullUserAgent(); |
| 125 | - | 126 | + |
| 126 | /// 是否是中国大陆地区 | 127 | /// 是否是中国大陆地区 |
| 127 | @async | 128 | @async |
| 128 | bool isChinaRegion(); | 129 | bool isChinaRegion(); |
| @@ -170,6 +171,7 @@ abstract class PlatformHostApi { | @@ -170,6 +171,7 @@ abstract class PlatformHostApi { | ||
| 170 | /// 请求苹果登录 | 171 | /// 请求苹果登录 |
| 171 | @async | 172 | @async |
| 172 | AppleSignInModel? requestAppleSignIn(); | 173 | AppleSignInModel? requestAppleSignIn(); |
| 174 | + | ||
| 173 | /// google登录 | 175 | /// google登录 |
| 174 | @async | 176 | @async |
| 175 | GoogleSignInModel? requestGoogleSignIn(); | 177 | GoogleSignInModel? requestGoogleSignIn(); |
| @@ -194,7 +194,7 @@ packages: | @@ -194,7 +194,7 @@ packages: | ||
| 194 | source: hosted | 194 | source: hosted |
| 195 | version: "0.3.4+2" | 195 | version: "0.3.4+2" |
| 196 | crypto: | 196 | crypto: |
| 197 | - dependency: transitive | 197 | + dependency: "direct main" |
| 198 | description: | 198 | description: |
| 199 | name: crypto | 199 | name: crypto |
| 200 | sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf | 200 | sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf |
| @@ -62,6 +62,7 @@ dependencies: | @@ -62,6 +62,7 @@ dependencies: | ||
| 62 | url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git" | 62 | url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git" |
| 63 | ref: 2.4.2-ohos-1.0.0-beta.2 | 63 | ref: 2.4.2-ohos-1.0.0-beta.2 |
| 64 | path: sqflite | 64 | path: sqflite |
| 65 | + crypto: ^3.0.7 | ||
| 65 | 66 | ||
| 66 | 67 | ||
| 67 | # share_plus (git) 间接依赖 path_provider 的 git 版本, | 68 | # share_plus (git) 间接依赖 path_provider 的 git 版本, |
-
Please register or login to post a comment