Commit 4ec8a24339594f7fdd658285a34b43856ecd7aa1

Authored by 常守达
1 parent 2aff1294

feat(login): 接入Google登陆、发送邮件接口

@@ -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 版本,