Commit df1e3d3b2370b0cf31c2b87eb429da8a73df6f8f

Authored by 常守达
1 parent 30b195eb

feat(login): 换绑UI

@@ -203,7 +203,7 @@ class BindPartnerView extends GetView<BindPartnerController> { @@ -203,7 +203,7 @@ class BindPartnerView extends GetView<BindPartnerController> {
203 Clipboard.setData( 203 Clipboard.setData(
204 ClipboardData(text: controller.myInviteCode.value), 204 ClipboardData(text: controller.myInviteCode.value),
205 ); 205 );
206 - AppToast.show('复制成功'); 206 + AppToast.show(context.l10n.copiedSuccessfully);
207 ta.track('click_doublefeel_add_friend_page', properties: { 207 ta.track('click_doublefeel_add_friend_page', properties: {
208 'channel_type': controller.channel, 208 'channel_type': controller.channel,
209 'click_content': '复制共享码' 209 'click_content': '复制共享码'
@@ -103,7 +103,6 @@ class MyController extends GetxController { @@ -103,7 +103,6 @@ class MyController extends GetxController {
103 await loadWatchThemes(); 103 await loadWatchThemes();
104 } 104 }
105 105
106 -  
107 Future<void> toPremiumPage() async { 106 Future<void> toPremiumPage() async {
108 await Get.toNamed(Routes.PURCHASE, arguments: { 107 await Get.toNamed(Routes.PURCHASE, arguments: {
109 IntentKeys.channelType: '我的页面会员入口', 108 IntentKeys.channelType: '我的页面会员入口',
@@ -116,4 +115,6 @@ class MyController extends GetxController { @@ -116,4 +115,6 @@ class MyController extends GetxController {
116 } on Exception catch (e) {} 115 } on Exception catch (e) {}
117 } 116 }
118 } 117 }
  118 +
  119 + void refreshEmail() {}
119 } 120 }
@@ -5,6 +5,7 @@ import 'package:doublefeel_flutter/app/modules/home/controllers/home_controller. @@ -5,6 +5,7 @@ import 'package:doublefeel_flutter/app/modules/home/controllers/home_controller.
5 import 'package:doublefeel_flutter/app/modules/home/controllers/trend/trend_controller.dart'; 5 import 'package:doublefeel_flutter/app/modules/home/controllers/trend/trend_controller.dart';
6 import 'package:doublefeel_flutter/app/modules/home/widgets/today/friend_select_bottom_sheet.dart'; 6 import 'package:doublefeel_flutter/app/modules/home/widgets/today/friend_select_bottom_sheet.dart';
7 import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart'; 7 import 'package:doublefeel_flutter/app/modules/home/widgets/today/no_health_data_page.dart';
  8 +import 'package:doublefeel_flutter/app/modules/login/views/email_login_contact_us_bottom_sheet.dart';
8 import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart'; 9 import 'package:doublefeel_flutter/app/modules/report_common/models/report_period.dart';
9 import 'package:doublefeel_flutter/app/routes/app_pages.dart'; 10 import 'package:doublefeel_flutter/app/routes/app_pages.dart';
10 import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; 11 import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
@@ -394,7 +395,20 @@ class TodayController extends GetMaterialController { @@ -394,7 +395,20 @@ class TodayController extends GetMaterialController {
394 }); 395 });
395 }, 396 },
396 onHelp: () { 397 onHelp: () {
397 - Get.toNamed(Routes.HELP); 398 + if (environmentConfig.region.value == AppRegion.china) {
  399 + Get.toNamed(Routes.HELP);
  400 + } else {
  401 + final userId = Get.find<UserPreferencesStorage>()
  402 + .preferences
  403 + .value
  404 + .meUserInfo
  405 + ?.id ??
  406 + 0;
  407 + sendFeedbackEmail(
  408 + userId: userId.toString(),
  409 + pageName: 'No heart rate data available page',
  410 + );
  411 + }
398 }, 412 },
399 )); 413 ));
400 checkHealthDataAuthCardVisible(); 414 checkHealthDataAuthCardVisible();
1 import 'package:doublefeel_flutter/app/actions/dialog_action.dart'; 1 import 'package:doublefeel_flutter/app/actions/dialog_action.dart';
2 import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart'; 2 import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart';
3 import 'package:doublefeel_flutter/app/modules/home/controllers/my_controller.dart'; 3 import 'package:doublefeel_flutter/app/modules/home/controllers/my_controller.dart';
4 -import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_views.dart';  
5 import 'package:doublefeel_flutter/app/routes/app_pages.dart'; 4 import 'package:doublefeel_flutter/app/routes/app_pages.dart';
6 import 'package:doublefeel_flutter/app/utils/dialog_utils.dart'; 5 import 'package:doublefeel_flutter/app/utils/dialog_utils.dart';
7 6
@@ -69,7 +68,10 @@ class AccountSettingView extends GetView<MyController> { @@ -69,7 +68,10 @@ class AccountSettingView extends GetView<MyController> {
69 ], 68 ],
70 _SecurityEmailRow( 69 _SecurityEmailRow(
71 email: securityEmail, 70 email: securityEmail,
72 - onTap: () => Get.to(() => const AddSecurityEmailView()), 71 + onTap: () async {
  72 + await Get.toNamed(Routes.ADD_SECURITY_EMAIL);
  73 + controller.refreshEmail();
  74 + },
73 ), 75 ),
74 const Spacer(), 76 const Spacer(),
75 _AccountActionButton( 77 _AccountActionButton(
@@ -81,7 +83,7 @@ class AccountSettingView extends GetView<MyController> { @@ -81,7 +83,7 @@ class AccountSettingView extends GetView<MyController> {
81 const SizedBox(height: 12), 83 const SizedBox(height: 12),
82 _AccountActionButton( 84 _AccountActionButton(
83 label: context.l10n.deleteAccount, 85 label: context.l10n.deleteAccount,
84 - color: AppColors.warning, 86 + color: context.colors.warning,
85 onTap: _showDeleteAccountSheet, 87 onTap: _showDeleteAccountSheet,
86 ), 88 ),
87 SizedBox(height: MediaQuery.paddingOf(context).bottom + 40), 89 SizedBox(height: MediaQuery.paddingOf(context).bottom + 40),
@@ -176,8 +178,8 @@ class _AccountInfoRow extends StatelessWidget { @@ -176,8 +178,8 @@ class _AccountInfoRow extends StatelessWidget {
176 children: [ 178 children: [
177 Text( 179 Text(
178 title, 180 title,
179 - style: const TextStyle(  
180 - color: AppColors.textPrimary, 181 + style: TextStyle(
  182 + color: context.colors.textPrimary,
181 fontSize: 14, 183 fontSize: 14,
182 fontWeight: FontWeight.w400, 184 fontWeight: FontWeight.w400,
183 height: 1.4, 185 height: 1.4,
@@ -190,8 +192,8 @@ class _AccountInfoRow extends StatelessWidget { @@ -190,8 +192,8 @@ class _AccountInfoRow extends StatelessWidget {
190 maxLines: 1, 192 maxLines: 1,
191 overflow: TextOverflow.ellipsis, 193 overflow: TextOverflow.ellipsis,
192 textAlign: TextAlign.right, 194 textAlign: TextAlign.right,
193 - style: const TextStyle(  
194 - color: AppColors.textSecondary, 195 + style: TextStyle(
  196 + color: context.colors.textSecondary,
195 fontSize: 14, 197 fontSize: 14,
196 fontWeight: FontWeight.w400, 198 fontWeight: FontWeight.w400,
197 height: 1.4, 199 height: 1.4,
@@ -230,8 +232,8 @@ class _SecurityEmailRow extends StatelessWidget { @@ -230,8 +232,8 @@ class _SecurityEmailRow extends StatelessWidget {
230 children: [ 232 children: [
231 Text( 233 Text(
232 context.l10n.securityEmail, 234 context.l10n.securityEmail,
233 - style: const TextStyle(  
234 - color: AppColors.textPrimary, 235 + style: TextStyle(
  236 + color: context.colors.textPrimary,
235 fontSize: 14, 237 fontSize: 14,
236 fontWeight: FontWeight.w400, 238 fontWeight: FontWeight.w400,
237 height: 1.4, 239 height: 1.4,
@@ -244,8 +246,8 @@ class _SecurityEmailRow extends StatelessWidget { @@ -244,8 +246,8 @@ class _SecurityEmailRow extends StatelessWidget {
244 maxLines: 1, 246 maxLines: 1,
245 overflow: TextOverflow.ellipsis, 247 overflow: TextOverflow.ellipsis,
246 textAlign: TextAlign.right, 248 textAlign: TextAlign.right,
247 - style: const TextStyle(  
248 - color: AppColors.textSecondary, 249 + style: TextStyle(
  250 + color: context.colors.textSecondary,
249 fontSize: 14, 251 fontSize: 14,
250 fontWeight: FontWeight.w400, 252 fontWeight: FontWeight.w400,
251 height: 1.4, 253 height: 1.4,
  1 +import 'dart:async';
  2 +
  3 +import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_views.dart';
  4 +import 'package:doublefeel_flutter/core/util/app_toast.dart';
  5 +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
  6 +import 'package:flutter/material.dart';
  7 +import 'package:flutter/services.dart';
  8 +import 'package:get/get.dart';
  9 +
  10 +class AddSecurityEmailController extends GetxController {
  11 + static final _emailPattern = RegExp(
  12 + r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$',
  13 + );
  14 + static const resendSeconds = 60;
  15 +
  16 + final emailController = TextEditingController();
  17 + final codeController = TextEditingController();
  18 +
  19 + final hasSubmittedEmail = false.obs;
  20 + final emailInput = ''.obs;
  21 + final codeInput = ''.obs;
  22 + final secondsRemaining = resendSeconds.obs;
  23 +
  24 + Timer? _resendTimer;
  25 +
  26 + static final _passwordRegex = RegExp(
  27 + r'^(?=.*[0-9])(?=.*[A-Z]).{6,}$',
  28 + );
  29 +
  30 + final passwordController = TextEditingController();
  31 + final passwordInput = ''.obs;
  32 + final isPasswordObscured = true.obs;
  33 +
  34 + final confirmPasswordController = TextEditingController();
  35 + final confirmPasswordInput = ''.obs;
  36 + final isConfirmPasswordObscured = true.obs;
  37 +
  38 + @override
  39 + void onInit() {
  40 + super.onInit();
  41 + emailController.addListener(_onEmailChanged);
  42 + codeController.addListener(_onCodeChanged);
  43 + passwordController.addListener(_onPasswordChanged);
  44 + confirmPasswordController.addListener(_onConfirmPasswordChanged);
  45 + }
  46 +
  47 + void _onEmailChanged() {
  48 + emailInput.value = emailController.text.replaceAll(' ', '').trim();
  49 + }
  50 +
  51 + void _onCodeChanged() {
  52 + codeInput.value = codeController.text.trim();
  53 + }
  54 +
  55 + void _onPasswordChanged() {
  56 + passwordInput.value = passwordController.text.trim();
  57 + }
  58 +
  59 + void _onConfirmPasswordChanged() {
  60 + confirmPasswordInput.value = confirmPasswordController.text.trim();
  61 + }
  62 +
  63 + bool get isEmailValid => _emailPattern.hasMatch(emailInput.value);
  64 +
  65 + bool get isPasswordValid =>
  66 + _passwordRegex.hasMatch(passwordInput.value) &&
  67 + passwordInput.value.isNotEmpty;
  68 +
  69 + bool get isConfirmPasswordValid =>
  70 + _passwordRegex.hasMatch(confirmPasswordInput.value) &&
  71 + confirmPasswordInput.value.isNotEmpty &&
  72 + confirmPasswordInput.value == passwordInput.value;
  73 +
  74 + bool get canSetPassword => isPasswordValid && isConfirmPasswordValid;
  75 +
  76 + bool get showError => hasSubmittedEmail.value && !isEmailValid;
  77 +
  78 + bool get canSubmitCode => codeInput.value.isNotEmpty;
  79 +
  80 + void togglePasswordVisibility() {
  81 + isPasswordObscured.value = !isPasswordObscured.value;
  82 + }
  83 +
  84 + void toggleConfirmPasswordVisibility() {
  85 + isConfirmPasswordObscured.value = !isConfirmPasswordObscured.value;
  86 + }
  87 +
  88 + void sendVerificationEmail() {
  89 + hasSubmittedEmail.value = true;
  90 + if (!isEmailValid) return;
  91 +
  92 + startResendCountdown();
  93 + Get.to(() => const SecurityEmailVerificationView());
  94 + }
  95 +
  96 + void startResendCountdown() {
  97 + _resendTimer?.cancel();
  98 + secondsRemaining.value = resendSeconds;
  99 + _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
  100 + if (secondsRemaining.value <= 1) {
  101 + timer.cancel();
  102 + secondsRemaining.value = 0;
  103 + return;
  104 + }
  105 + secondsRemaining.value -= 1;
  106 + });
  107 + }
  108 +
  109 + void submitCode(BuildContext context) {
  110 + if (!canSubmitCode) return;
  111 + FocusScope.of(context).unfocus();
  112 + Get.to(() => const SetSecurityEmailPasswordView());
  113 + }
  114 +
  115 + void setPassword(BuildContext context) {
  116 + 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();
  123 + }
  124 +
  125 + @override
  126 + void onClose() {
  127 + _resendTimer?.cancel();
  128 + emailController.dispose();
  129 + codeController.dispose();
  130 + passwordController.dispose();
  131 + confirmPasswordController.dispose();
  132 + super.onClose();
  133 + }
  134 +}
1 -import 'dart:async';  
2 - 1 +import 'package:doublefeel_flutter/app/actions/dialog_action.dart';
  2 +import 'package:doublefeel_flutter/app/models/dialog_meta_data.dart';
  3 +import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_controller.dart';
  4 +import 'package:doublefeel_flutter/app/utils/dialog_utils.dart';
  5 +import 'package:doublefeel_flutter/core/theme/app_theme.dart';
3 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; 6 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
4 import 'package:flutter/material.dart'; 7 import 'package:flutter/material.dart';
5 import 'package:flutter/services.dart'; 8 import 'package:flutter/services.dart';
6 import 'package:get/get.dart'; 9 import 'package:get/get.dart';
7 10
8 -const _securityEmailPageColor = Color(0xFFF5F2FF);  
9 -const _securityEmailTitleColor = Color(0xFF0F0F11);  
10 -const _securityEmailDescriptionColor = Color(0xFF78787D);  
11 -const _securityEmailBrandColor = Color(0xFF845EEE);  
12 -  
13 -class AddSecurityEmailView extends StatefulWidget { 11 +class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
14 const AddSecurityEmailView({super.key}); 12 const AddSecurityEmailView({super.key});
15 13
16 @override 14 @override
17 - State<AddSecurityEmailView> createState() => _AddSecurityEmailViewState();  
18 -}  
19 -  
20 -class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {  
21 - static final _emailPattern = RegExp(  
22 - r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$',  
23 - );  
24 -  
25 - final _emailController = TextEditingController();  
26 - bool _hasSubmitted = false;  
27 -  
28 - bool get _isEmailValid =>  
29 - _emailPattern.hasMatch(_emailController.text.trim());  
30 -  
31 - @override  
32 - void dispose() {  
33 - _emailController.dispose();  
34 - super.dispose();  
35 - }  
36 -  
37 - void _sendVerificationEmail() {  
38 - setState(() => _hasSubmitted = true);  
39 - if (!_isEmailValid) return;  
40 -  
41 - Get.to(  
42 - () => SecurityEmailVerificationView(  
43 - email: _emailController.text.trim(),  
44 - ),  
45 - );  
46 - }  
47 -  
48 - @override  
49 Widget build(BuildContext context) { 15 Widget build(BuildContext context) {
  16 + final c = Get.isRegistered<AddSecurityEmailController>()
  17 + ? controller
  18 + : Get.put(AddSecurityEmailController());
50 final l10n = context.l10n; 19 final l10n = context.l10n;
51 - final showError = _hasSubmitted && !_isEmailValid; 20 + final colors = context.colors;
52 21
53 return AnnotatedRegion<SystemUiOverlayStyle>( 22 return AnnotatedRegion<SystemUiOverlayStyle>(
54 - value: const SystemUiOverlayStyle( 23 + value: SystemUiOverlayStyle(
55 statusBarColor: Colors.transparent, 24 statusBarColor: Colors.transparent,
56 statusBarIconBrightness: Brightness.dark, 25 statusBarIconBrightness: Brightness.dark,
57 statusBarBrightness: Brightness.light, 26 statusBarBrightness: Brightness.light,
58 - systemNavigationBarColor: _securityEmailPageColor, 27 + systemNavigationBarColor: colors.backgroundPage,
59 systemNavigationBarIconBrightness: Brightness.dark, 28 systemNavigationBarIconBrightness: Brightness.dark,
60 ), 29 ),
61 child: Scaffold( 30 child: Scaffold(
62 - backgroundColor: _securityEmailPageColor, 31 + backgroundColor: colors.backgroundPage,
63 appBar: const _SecurityEmailAppBar(), 32 appBar: const _SecurityEmailAppBar(),
64 body: SafeArea( 33 body: SafeArea(
65 top: false, 34 top: false,
@@ -70,8 +39,8 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> { @@ -70,8 +39,8 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {
70 const SizedBox(height: 16), 39 const SizedBox(height: 16),
71 Text( 40 Text(
72 l10n.addSecurityEmail, 41 l10n.addSecurityEmail,
73 - style: const TextStyle(  
74 - color: _securityEmailTitleColor, 42 + style: TextStyle(
  43 + color: colors.textPrimary,
75 fontSize: 24, 44 fontSize: 24,
76 fontWeight: FontWeight.w600, 45 fontWeight: FontWeight.w600,
77 height: 1.25, 46 height: 1.25,
@@ -81,8 +50,8 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> { @@ -81,8 +50,8 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {
81 Text( 50 Text(
82 l10n.securityEmailDescription, 51 l10n.securityEmailDescription,
83 textAlign: TextAlign.center, 52 textAlign: TextAlign.center,
84 - style: const TextStyle(  
85 - color: _securityEmailDescriptionColor, 53 + style: TextStyle(
  54 + color: colors.textSecondary,
86 fontSize: 14, 55 fontSize: 14,
87 fontWeight: FontWeight.w400, 56 fontWeight: FontWeight.w400,
88 height: 1.43, 57 height: 1.43,
@@ -90,16 +59,12 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> { @@ -90,16 +59,12 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {
90 ), 59 ),
91 const SizedBox(height: 32), 60 const SizedBox(height: 32),
92 TextField( 61 TextField(
93 - controller: _emailController, 62 + controller: c.emailController,
94 keyboardType: TextInputType.emailAddress, 63 keyboardType: TextInputType.emailAddress,
95 autofillHints: const [AutofillHints.email], 64 autofillHints: const [AutofillHints.email],
96 - inputFormatters: [  
97 - FilteringTextInputFormatter.deny(RegExp(r'\s')),  
98 - ],  
99 - onChanged: (_) => setState(() {}),  
100 - onSubmitted: (_) => _sendVerificationEmail(),  
101 - style: const TextStyle(  
102 - color: _securityEmailTitleColor, 65 + onSubmitted: (_) => c.sendVerificationEmail(),
  66 + style: TextStyle(
  67 + color: colors.textPrimary,
103 fontSize: 16, 68 fontSize: 16,
104 fontWeight: FontWeight.w400, 69 fontWeight: FontWeight.w400,
105 ), 70 ),
@@ -114,35 +79,64 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> { @@ -114,35 +79,64 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {
114 contentPadding: const EdgeInsets.symmetric( 79 contentPadding: const EdgeInsets.symmetric(
115 horizontal: 20, 80 horizontal: 20,
116 ), 81 ),
117 - errorText: showError ? l10n.invalidEmailFormat : null,  
118 - errorStyle: const TextStyle(fontSize: 12),  
119 border: _inputBorder(), 82 border: _inputBorder(),
120 enabledBorder: _inputBorder(), 83 enabledBorder: _inputBorder(),
121 focusedBorder: _inputBorder( 84 focusedBorder: _inputBorder(
122 - color: _securityEmailBrandColor, 85 + color: colors.primary,
123 ), 86 ),
  87 + suffixIcon: Obx(() {
  88 + return c.emailInput.value.isNotEmpty
  89 + ? GestureDetector(
  90 + onTap: c.emailController.clear,
  91 + child: Icon(
  92 + Icons.cancel,
  93 + color: context.colors.textTertiary,
  94 + size: 18,
  95 + ),
  96 + )
  97 + : SizedBox();
  98 + }),
124 ), 99 ),
125 ), 100 ),
  101 + Obx(() {
  102 + if (!c.showError) return const SizedBox(height: 0);
  103 + return Padding(
  104 + padding: const EdgeInsets.only(top: 8, left: 20),
  105 + child: Align(
  106 + alignment: Alignment.centerLeft,
  107 + child: Text(
  108 + l10n.invalidEmailFormat,
  109 + style: TextStyle(
  110 + color: Theme.of(context).colorScheme.error,
  111 + fontSize: 12,
  112 + ),
  113 + ),
  114 + ),
  115 + );
  116 + }),
126 const SizedBox(height: 32), 117 const SizedBox(height: 32),
127 SizedBox( 118 SizedBox(
128 width: double.infinity, 119 width: double.infinity,
129 height: 48, 120 height: 48,
130 - child: ElevatedButton(  
131 - onPressed: _isEmailValid ? _sendVerificationEmail : null,  
132 - style: ElevatedButton.styleFrom(  
133 - backgroundColor: _securityEmailBrandColor,  
134 - disabledBackgroundColor:  
135 - _securityEmailBrandColor.withValues(alpha: 0.4),  
136 - foregroundColor: Colors.white,  
137 - disabledForegroundColor: Colors.white,  
138 - elevation: 0,  
139 - shape: const StadiumBorder(),  
140 - textStyle: const TextStyle(  
141 - fontSize: 16,  
142 - fontWeight: FontWeight.w600, 121 + child: Obx(
  122 + () => ElevatedButton(
  123 + onPressed:
  124 + c.isEmailValid ? c.sendVerificationEmail : null,
  125 + style: ElevatedButton.styleFrom(
  126 + backgroundColor: colors.primary,
  127 + disabledBackgroundColor:
  128 + colors.primary.withValues(alpha: 0.4),
  129 + foregroundColor: Colors.white,
  130 + disabledForegroundColor: Colors.white,
  131 + elevation: 0,
  132 + shape: const StadiumBorder(),
  133 + textStyle: const TextStyle(
  134 + fontSize: 16,
  135 + fontWeight: FontWeight.w600,
  136 + ),
143 ), 137 ),
  138 + child: Text(l10n.watchThemeNext),
144 ), 139 ),
145 - child: Text(l10n.sendVerificationEmail),  
146 ), 140 ),
147 ), 141 ),
148 ], 142 ],
@@ -154,74 +148,25 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> { @@ -154,74 +148,25 @@ class _AddSecurityEmailViewState extends State<AddSecurityEmailView> {
154 } 148 }
155 } 149 }
156 150
157 -class SecurityEmailVerificationView extends StatefulWidget {  
158 - const SecurityEmailVerificationView({  
159 - super.key,  
160 - required this.email,  
161 - });  
162 -  
163 - final String email;  
164 -  
165 - @override  
166 - State<SecurityEmailVerificationView> createState() =>  
167 - _SecurityEmailVerificationViewState();  
168 -}  
169 -  
170 -class _SecurityEmailVerificationViewState  
171 - extends State<SecurityEmailVerificationView> {  
172 - static const _resendSeconds = 60;  
173 -  
174 - final _codeController = TextEditingController();  
175 - Timer? _resendTimer;  
176 - int _secondsRemaining = _resendSeconds;  
177 -  
178 - bool get _canSubmit => _codeController.text.trim().isNotEmpty;  
179 -  
180 - @override  
181 - void initState() {  
182 - super.initState();  
183 - _startResendCountdown();  
184 - }  
185 -  
186 - @override  
187 - void dispose() {  
188 - _resendTimer?.cancel();  
189 - _codeController.dispose();  
190 - super.dispose();  
191 - }  
192 -  
193 - void _startResendCountdown() {  
194 - _resendTimer?.cancel();  
195 - setState(() => _secondsRemaining = _resendSeconds);  
196 - _resendTimer = Timer.periodic(const Duration(seconds: 1), (timer) {  
197 - if (_secondsRemaining <= 1) {  
198 - timer.cancel();  
199 - setState(() => _secondsRemaining = 0);  
200 - return;  
201 - }  
202 - setState(() => _secondsRemaining -= 1);  
203 - });  
204 - }  
205 -  
206 - void _submitCode() {  
207 - if (!_canSubmit) return;  
208 - FocusScope.of(context).unfocus();  
209 - } 151 +class SecurityEmailVerificationView
  152 + extends GetView<AddSecurityEmailController> {
  153 + const SecurityEmailVerificationView({super.key});
210 154
211 @override 155 @override
212 Widget build(BuildContext context) { 156 Widget build(BuildContext context) {
213 final l10n = context.l10n; 157 final l10n = context.l10n;
  158 + final colors = context.colors;
214 159
215 return AnnotatedRegion<SystemUiOverlayStyle>( 160 return AnnotatedRegion<SystemUiOverlayStyle>(
216 - value: const SystemUiOverlayStyle( 161 + value: SystemUiOverlayStyle(
217 statusBarColor: Colors.transparent, 162 statusBarColor: Colors.transparent,
218 statusBarIconBrightness: Brightness.dark, 163 statusBarIconBrightness: Brightness.dark,
219 statusBarBrightness: Brightness.light, 164 statusBarBrightness: Brightness.light,
220 - systemNavigationBarColor: _securityEmailPageColor, 165 + systemNavigationBarColor: colors.backgroundPage,
221 systemNavigationBarIconBrightness: Brightness.dark, 166 systemNavigationBarIconBrightness: Brightness.dark,
222 ), 167 ),
223 child: Scaffold( 168 child: Scaffold(
224 - backgroundColor: _securityEmailPageColor, 169 + backgroundColor: colors.backgroundPage,
225 appBar: const _SecurityEmailAppBar(), 170 appBar: const _SecurityEmailAppBar(),
226 body: SafeArea( 171 body: SafeArea(
227 top: false, 172 top: false,
@@ -232,20 +177,23 @@ class _SecurityEmailVerificationViewState @@ -232,20 +177,23 @@ class _SecurityEmailVerificationViewState
232 const SizedBox(height: 16), 177 const SizedBox(height: 16),
233 Text( 178 Text(
234 l10n.confirmYourEmail, 179 l10n.confirmYourEmail,
235 - style: const TextStyle(  
236 - color: _securityEmailTitleColor, 180 + style: TextStyle(
  181 + color: colors.textPrimary,
237 fontSize: 24, 182 fontSize: 24,
238 fontWeight: FontWeight.w600, 183 fontWeight: FontWeight.w600,
239 height: 1.25, 184 height: 1.25,
240 ), 185 ),
241 ), 186 ),
242 const SizedBox(height: 7), 187 const SizedBox(height: 7),
243 - Text.rich(  
244 - _verificationInstruction(  
245 - l10n.enterCodeSentTo(widget.email),  
246 - widget.email, 188 + Obx(
  189 + () => Text.rich(
  190 + _verificationInstruction(
  191 + context,
  192 + l10n.enterCodeSentTo(controller.emailInput.value),
  193 + controller.emailInput.value,
  194 + ),
  195 + textAlign: TextAlign.center,
247 ), 196 ),
248 - textAlign: TextAlign.center,  
249 ), 197 ),
250 const SizedBox(height: 47), 198 const SizedBox(height: 47),
251 Container( 199 Container(
@@ -257,17 +205,15 @@ class _SecurityEmailVerificationViewState @@ -257,17 +205,15 @@ class _SecurityEmailVerificationViewState
257 child: Stack( 205 child: Stack(
258 children: [ 206 children: [
259 TextField( 207 TextField(
260 - controller: _codeController,  
261 - keyboardType: TextInputType.number, 208 + controller: controller.codeController,
  209 + // keyboardType: TextInputType.number,
262 textInputAction: TextInputAction.done, 210 textInputAction: TextInputAction.done,
263 - maxLength: 6,  
264 - inputFormatters: [  
265 - FilteringTextInputFormatter.digitsOnly  
266 - ],  
267 - onChanged: (_) => setState(() {}),  
268 - onSubmitted: (_) => _submitCode(),  
269 - style: const TextStyle(  
270 - color: _securityEmailTitleColor, 211 + // inputFormatters: const [
  212 + // _DigitsOnlyFormatter(),
  213 + // ],
  214 + onSubmitted: (_) => controller.submitCode(context),
  215 + style: TextStyle(
  216 + color: colors.textPrimary,
271 fontSize: 16, 217 fontSize: 16,
272 fontWeight: FontWeight.w400, 218 fontWeight: FontWeight.w400,
273 ), 219 ),
@@ -289,60 +235,71 @@ class _SecurityEmailVerificationViewState @@ -289,60 +235,71 @@ class _SecurityEmailVerificationViewState
289 top: 0, 235 top: 0,
290 right: 0, 236 right: 0,
291 bottom: 0, 237 bottom: 0,
292 - child: TextButton(  
293 - onPressed: _secondsRemaining == 0  
294 - ? _startResendCountdown  
295 - : null,  
296 - style: TextButton.styleFrom(  
297 - foregroundColor: _securityEmailBrandColor,  
298 - disabledForegroundColor: const Color(0xFFD9D9D9),  
299 - padding: const EdgeInsets.symmetric(horizontal: 20),  
300 - textStyle: const TextStyle(  
301 - fontSize: 16,  
302 - fontWeight: FontWeight.w500,  
303 - ),  
304 - ),  
305 - child: Text(  
306 - _secondsRemaining == 0  
307 - ? l10n.resend  
308 - : l10n.resendWithSeconds(_secondsRemaining),  
309 - ), 238 + child: Obx(
  239 + () {
  240 + final seconds = controller.secondsRemaining.value;
  241 + return TextButton(
  242 + onPressed: seconds == 0
  243 + ? controller.startResendCountdown
  244 + : null,
  245 + style: TextButton.styleFrom(
  246 + foregroundColor: colors.primary,
  247 + disabledForegroundColor:
  248 + const Color(0xFFD9D9D9),
  249 + padding:
  250 + const EdgeInsets.symmetric(horizontal: 20),
  251 + textStyle: const TextStyle(
  252 + fontSize: 16,
  253 + fontWeight: FontWeight.w500,
  254 + ),
  255 + ),
  256 + child: Text(
  257 + seconds == 0
  258 + ? l10n.resend
  259 + : l10n.resendWithSeconds(seconds),
  260 + ),
  261 + );
  262 + },
310 ), 263 ),
311 ), 264 ),
312 ], 265 ],
313 ), 266 ),
314 ), 267 ),
315 - const SizedBox(height: 8), 268 + const SizedBox(height: 60),
316 Text( 269 Text(
317 l10n.emailVerificationHelp, 270 l10n.emailVerificationHelp,
318 textAlign: TextAlign.center, 271 textAlign: TextAlign.center,
319 - style: const TextStyle(  
320 - color: _securityEmailDescriptionColor, 272 + style: TextStyle(
  273 + color: colors.textSecondary,
321 fontSize: 14, 274 fontSize: 14,
322 fontWeight: FontWeight.w400, 275 fontWeight: FontWeight.w400,
323 height: 1.2, 276 height: 1.2,
324 ), 277 ),
325 ), 278 ),
326 - const SizedBox(height: 64), 279 + const SizedBox(height: 11),
327 SizedBox( 280 SizedBox(
328 width: double.infinity, 281 width: double.infinity,
329 height: 48, 282 height: 48,
330 - child: ElevatedButton(  
331 - onPressed: _canSubmit ? _submitCode : null,  
332 - style: ElevatedButton.styleFrom(  
333 - backgroundColor: _securityEmailBrandColor,  
334 - disabledBackgroundColor:  
335 - _securityEmailBrandColor.withValues(alpha: 0.4),  
336 - foregroundColor: Colors.white,  
337 - disabledForegroundColor: Colors.white,  
338 - elevation: 0,  
339 - shape: const StadiumBorder(),  
340 - textStyle: const TextStyle(  
341 - fontSize: 16,  
342 - fontWeight: FontWeight.w600, 283 + child: Obx(
  284 + () => ElevatedButton(
  285 + onPressed: controller.canSubmitCode
  286 + ? () => controller.submitCode(context)
  287 + : null,
  288 + style: ElevatedButton.styleFrom(
  289 + backgroundColor: colors.primary,
  290 + disabledBackgroundColor:
  291 + colors.primary.withValues(alpha: 0.4),
  292 + foregroundColor: Colors.white,
  293 + disabledForegroundColor: Colors.white,
  294 + elevation: 0,
  295 + shape: const StadiumBorder(),
  296 + textStyle: const TextStyle(
  297 + fontSize: 16,
  298 + fontWeight: FontWeight.w600,
  299 + ),
343 ), 300 ),
  301 + child: Text(l10n.submit),
344 ), 302 ),
345 - child: Text(l10n.submit),  
346 ), 303 ),
347 ), 304 ),
348 ], 305 ],
@@ -354,9 +311,14 @@ class _SecurityEmailVerificationViewState @@ -354,9 +311,14 @@ class _SecurityEmailVerificationViewState
354 } 311 }
355 } 312 }
356 313
357 -TextSpan _verificationInstruction(String instruction, String email) {  
358 - const textStyle = TextStyle(  
359 - color: _securityEmailDescriptionColor, 314 +TextSpan _verificationInstruction(
  315 + BuildContext context,
  316 + String instruction,
  317 + String email,
  318 +) {
  319 + final colors = context.colors;
  320 + final textStyle = TextStyle(
  321 + color: colors.textSecondary,
360 fontSize: 14, 322 fontSize: 14,
361 fontWeight: FontWeight.w400, 323 fontWeight: FontWeight.w400,
362 height: 1.43, 324 height: 1.43,
@@ -371,7 +333,7 @@ TextSpan _verificationInstruction(String instruction, String email) { @@ -371,7 +333,7 @@ TextSpan _verificationInstruction(String instruction, String email) {
371 TextSpan(text: instruction.substring(0, emailStart)), 333 TextSpan(text: instruction.substring(0, emailStart)),
372 TextSpan( 334 TextSpan(
373 text: instruction.substring(emailStart, emailEnd), 335 text: instruction.substring(emailStart, emailEnd),
374 - style: const TextStyle(color: _securityEmailTitleColor), 336 + style: TextStyle(color: colors.textPrimary),
375 ), 337 ),
376 TextSpan(text: instruction.substring(emailEnd)), 338 TextSpan(text: instruction.substring(emailEnd)),
377 ], 339 ],
@@ -380,7 +342,10 @@ TextSpan _verificationInstruction(String instruction, String email) { @@ -380,7 +342,10 @@ TextSpan _verificationInstruction(String instruction, String email) {
380 342
381 class _SecurityEmailAppBar extends StatelessWidget 343 class _SecurityEmailAppBar extends StatelessWidget
382 implements PreferredSizeWidget { 344 implements PreferredSizeWidget {
383 - const _SecurityEmailAppBar(); 345 + const _SecurityEmailAppBar({
  346 + this.onPressed,
  347 + });
  348 + final VoidCallback? onPressed;
384 349
385 @override 350 @override
386 Size get preferredSize => const Size.fromHeight(44); 351 Size get preferredSize => const Size.fromHeight(44);
@@ -388,13 +353,13 @@ class _SecurityEmailAppBar extends StatelessWidget @@ -388,13 +353,13 @@ class _SecurityEmailAppBar extends StatelessWidget
388 @override 353 @override
389 Widget build(BuildContext context) { 354 Widget build(BuildContext context) {
390 return AppBar( 355 return AppBar(
391 - backgroundColor: _securityEmailPageColor, 356 + backgroundColor: context.colors.backgroundPage,
392 elevation: 0, 357 elevation: 0,
393 scrolledUnderElevation: 0, 358 scrolledUnderElevation: 0,
394 toolbarHeight: 44, 359 toolbarHeight: 44,
395 leadingWidth: 60, 360 leadingWidth: 60,
396 leading: IconButton( 361 leading: IconButton(
397 - onPressed: Get.back, 362 + onPressed: onPressed ?? Get.back,
398 splashColor: Colors.transparent, 363 splashColor: Colors.transparent,
399 highlightColor: Colors.transparent, 364 highlightColor: Colors.transparent,
400 icon: Image.asset( 365 icon: Image.asset(
@@ -413,3 +378,340 @@ OutlineInputBorder _inputBorder({Color color = Colors.transparent}) { @@ -413,3 +378,340 @@ OutlineInputBorder _inputBorder({Color color = Colors.transparent}) {
413 borderSide: BorderSide(color: color), 378 borderSide: BorderSide(color: color),
414 ); 379 );
415 } 380 }
  381 +
  382 +class SetSecurityEmailPasswordView extends GetView<AddSecurityEmailController> {
  383 + const SetSecurityEmailPasswordView({super.key});
  384 +
  385 + @override
  386 + Widget build(BuildContext context) {
  387 + final c = Get.isRegistered<AddSecurityEmailController>()
  388 + ? controller
  389 + : Get.put(AddSecurityEmailController());
  390 + final l10n = context.l10n;
  391 + final colors = context.colors;
  392 +
  393 + return AnnotatedRegion<SystemUiOverlayStyle>(
  394 + value: SystemUiOverlayStyle(
  395 + statusBarColor: Colors.transparent,
  396 + statusBarIconBrightness: Brightness.dark,
  397 + statusBarBrightness: Brightness.light,
  398 + systemNavigationBarColor: colors.backgroundPage,
  399 + systemNavigationBarIconBrightness: Brightness.dark,
  400 + ),
  401 + child: Scaffold(
  402 + backgroundColor: colors.backgroundPage,
  403 + appBar: _SecurityEmailAppBar(onPressed: () async {
  404 + final result = await DialogUtils.showCommonDialog(DialogMetaData(
  405 + title: context.l10n.setupIncomplete,
  406 + message: context.l10n
  407 + .setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup,
  408 + confirmText: context.l10n.setPassword,
  409 + cancelText: context.l10n.leave,
  410 + iconAsset: 'assets/images/common/ic_warning.png',
  411 + ));
  412 + if (result.action == DialogAction.cancel) {
  413 + Get.back();
  414 + Get.back();
  415 + Get.back();
  416 + }
  417 + }),
  418 + body: SafeArea(
  419 + top: false,
  420 + child: Padding(
  421 + padding: const EdgeInsets.symmetric(horizontal: 28),
  422 + child: Column(
  423 + children: [
  424 + const SizedBox(height: 16),
  425 + Text(
  426 + l10n.setAPassword,
  427 + style: TextStyle(
  428 + color: colors.textPrimary,
  429 + fontSize: 24,
  430 + fontWeight: FontWeight.w600,
  431 + height: 1.25,
  432 + ),
  433 + ),
  434 + const SizedBox(height: 8),
  435 + Text(
  436 + l10n.setAPasswordToSignInWithYourEmail,
  437 + textAlign: TextAlign.center,
  438 + style: TextStyle(
  439 + color: colors.textSecondary,
  440 + fontSize: 14,
  441 + fontWeight: FontWeight.w400,
  442 + height: 1.43,
  443 + ),
  444 + ),
  445 + const SizedBox(height: 48),
  446 + Row(
  447 + children: [
  448 + const SizedBox(width: 16),
  449 + Text(
  450 + context.l10n.enterYourPassword,
  451 + style: TextStyle(
  452 + color: const Color(0xFF141414),
  453 + fontSize: 14,
  454 + fontWeight: FontWeight.w500,
  455 + ),
  456 + ),
  457 + ],
  458 + ),
  459 + const SizedBox(height: 15),
  460 + AutofillGroup(
  461 + child: Column(
  462 + children: [
  463 + Offstage(
  464 + offstage: true,
  465 + child: TextField(
  466 + focusNode: FocusNode(canRequestFocus: false),
  467 + controller: c.emailController,
  468 + autofillHints: const [
  469 + AutofillHints.username,
  470 + AutofillHints.email
  471 + ],
  472 + // readOnly: true,
  473 + // enabled: false,
  474 + ),
  475 + ),
  476 + Obx(() {
  477 + final isObscured = c.isPasswordObscured.value;
  478 + return TextField(
  479 + textInputAction: TextInputAction.next,
  480 + controller: c.passwordController,
  481 + // keyboardType: TextInputType.visiblePassword,
  482 + obscureText: isObscured,
  483 + autofillHints: const [AutofillHints.newPassword],
  484 + obscuringCharacter: '*',
  485 + contextMenuBuilder: (BuildContext context,
  486 + EditableTextState editableTextState) {
  487 + List<ContextMenuButtonItem> buttonItems =
  488 + editableTextState.contextMenuButtonItems;
  489 + buttonItems.removeWhere((item) =>
  490 + item.type != ContextMenuButtonType.paste);
  491 + return AdaptiveTextSelectionToolbar.buttonItems(
  492 + anchors: editableTextState.contextMenuAnchors,
  493 + buttonItems: buttonItems,
  494 + );
  495 + },
  496 + inputFormatters: [
  497 + FilteringTextInputFormatter.deny(RegExp(r'\s')),
  498 + FilteringTextInputFormatter.allow(
  499 + RegExp(r'[\x21-\x7E]'),
  500 + ),
  501 + ],
  502 + style: TextStyle(
  503 + color: context.colors.textPrimary,
  504 + fontSize: 16,
  505 + fontWeight: FontWeight.w400,
  506 + ),
  507 + decoration: InputDecoration(
  508 + hintText: l10n.emailLoginYourPassword,
  509 + hintStyle: TextStyle(
  510 + color: context.colors.textTertiary,
  511 + fontSize: 16,
  512 + ),
  513 + counterText: '',
  514 + filled: true,
  515 + fillColor: Colors.white,
  516 + contentPadding: const EdgeInsets.symmetric(
  517 + horizontal: 20,
  518 + vertical: 16,
  519 + ),
  520 + border: OutlineInputBorder(
  521 + borderRadius: BorderRadius.circular(27),
  522 + borderSide: BorderSide.none,
  523 + ),
  524 + enabledBorder: OutlineInputBorder(
  525 + borderRadius: BorderRadius.circular(27),
  526 + borderSide: BorderSide.none,
  527 + ),
  528 + focusedBorder: OutlineInputBorder(
  529 + borderRadius: BorderRadius.circular(27),
  530 + borderSide: BorderSide(
  531 + color: context.colors.primary,
  532 + width: 1,
  533 + ),
  534 + ),
  535 + suffixIconConstraints: const BoxConstraints(
  536 + minWidth: 24,
  537 + minHeight: 24,
  538 + ),
  539 + suffixIcon: GestureDetector(
  540 + onTap: c.togglePasswordVisibility,
  541 + child: Padding(
  542 + padding: const EdgeInsets.only(right: 16),
  543 + child: Image.asset(
  544 + isObscured
  545 + ? 'assets/images/common/ic_hide_password.png'
  546 + : 'assets/images/common/ic_show_password.webp',
  547 + color: context.colors.textTertiary,
  548 + width: 24,
  549 + height: 24,
  550 + ),
  551 + ),
  552 + ),
  553 + ),
  554 + );
  555 + }),
  556 + const SizedBox(height: 8),
  557 + Padding(
  558 + padding: const EdgeInsets.symmetric(horizontal: 16),
  559 + child: Text(
  560 + textAlign: TextAlign.center,
  561 + context.l10n
  562 + .passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter,
  563 + style: TextStyle(
  564 + color: context.colors.textTertiary,
  565 + fontSize: 12,
  566 + fontWeight: FontWeight.w400,
  567 + ),
  568 + ),
  569 + ),
  570 + const SizedBox(height: 32),
  571 + Row(
  572 + children: [
  573 + const SizedBox(width: 16),
  574 + Text(
  575 + context.l10n.confirmNewPassword,
  576 + style: TextStyle(
  577 + color: const Color(0xFF141414),
  578 + fontSize: 14,
  579 + fontWeight: FontWeight.w500,
  580 + ),
  581 + ),
  582 + ],
  583 + ),
  584 + const SizedBox(height: 15),
  585 + Obx(() {
  586 + final isObscured = c.isConfirmPasswordObscured.value;
  587 + return TextField(
  588 + controller: c.confirmPasswordController,
  589 + // keyboardType: TextInputType.visiblePassword,
  590 + obscureText: isObscured,
  591 + autofillHints: const [AutofillHints.newPassword],
  592 + obscuringCharacter: '*',
  593 + contextMenuBuilder: (BuildContext context,
  594 + EditableTextState editableTextState) {
  595 + List<ContextMenuButtonItem> buttonItems =
  596 + editableTextState.contextMenuButtonItems;
  597 + buttonItems.removeWhere((item) =>
  598 + item.type != ContextMenuButtonType.paste);
  599 + return AdaptiveTextSelectionToolbar.buttonItems(
  600 + anchors: editableTextState.contextMenuAnchors,
  601 + buttonItems: buttonItems,
  602 + );
  603 + },
  604 + onSubmitted: (_) {
  605 + controller.setPassword(context);
  606 + },
  607 + inputFormatters: [
  608 + FilteringTextInputFormatter.deny(RegExp(r'\s')),
  609 + FilteringTextInputFormatter.allow(
  610 + RegExp(r'[\x21-\x7E]'),
  611 + ),
  612 + ],
  613 + style: TextStyle(
  614 + color: context.colors.textPrimary,
  615 + fontSize: 16,
  616 + fontWeight: FontWeight.w400,
  617 + ),
  618 + decoration: InputDecoration(
  619 + hintText: l10n.emailLoginYourPassword,
  620 + hintStyle: TextStyle(
  621 + color: context.colors.textTertiary,
  622 + fontSize: 16,
  623 + ),
  624 + counterText: '',
  625 + filled: true,
  626 + fillColor: Colors.white,
  627 + contentPadding: const EdgeInsets.symmetric(
  628 + horizontal: 20,
  629 + vertical: 16,
  630 + ),
  631 + border: OutlineInputBorder(
  632 + borderRadius: BorderRadius.circular(27),
  633 + borderSide: BorderSide.none,
  634 + ),
  635 + enabledBorder: OutlineInputBorder(
  636 + borderRadius: BorderRadius.circular(27),
  637 + borderSide: BorderSide.none,
  638 + ),
  639 + focusedBorder: OutlineInputBorder(
  640 + borderRadius: BorderRadius.circular(27),
  641 + borderSide: BorderSide(
  642 + color: context.colors.primary,
  643 + width: 1,
  644 + ),
  645 + ),
  646 + suffixIconConstraints: const BoxConstraints(
  647 + minWidth: 24,
  648 + minHeight: 24,
  649 + ),
  650 + suffixIcon: GestureDetector(
  651 + onTap: c.toggleConfirmPasswordVisibility,
  652 + child: Padding(
  653 + padding: const EdgeInsets.only(right: 16),
  654 + child: Image.asset(
  655 + isObscured
  656 + ? 'assets/images/common/ic_hide_password.png'
  657 + : 'assets/images/common/ic_show_password.webp',
  658 + color: context.colors.textTertiary,
  659 + width: 24,
  660 + height: 24,
  661 + ),
  662 + ),
  663 + ),
  664 + ),
  665 + );
  666 + }),
  667 + ],
  668 + ),
  669 + ),
  670 + const SizedBox(height: 8),
  671 + Padding(
  672 + padding: const EdgeInsets.symmetric(horizontal: 16),
  673 + child: Text(
  674 + textAlign: TextAlign.center,
  675 + context.l10n
  676 + .passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter,
  677 + style: TextStyle(
  678 + color: context.colors.textTertiary,
  679 + fontSize: 12,
  680 + fontWeight: FontWeight.w400,
  681 + ),
  682 + ),
  683 + ),
  684 + const SizedBox(height: 32),
  685 + SizedBox(
  686 + width: double.infinity,
  687 + height: 48,
  688 + child: Obx(
  689 + () => ElevatedButton(
  690 + onPressed: c.canSetPassword
  691 + ? () => c.setPassword(context)
  692 + : null,
  693 + style: ElevatedButton.styleFrom(
  694 + backgroundColor: colors.primary,
  695 + disabledBackgroundColor:
  696 + colors.primary.withValues(alpha: 0.4),
  697 + foregroundColor: Colors.white,
  698 + disabledForegroundColor: Colors.white,
  699 + elevation: 0,
  700 + shape: const StadiumBorder(),
  701 + textStyle: const TextStyle(
  702 + fontSize: 16,
  703 + fontWeight: FontWeight.w600,
  704 + ),
  705 + ),
  706 + child: Text(context.l10n.setPassword),
  707 + ),
  708 + ),
  709 + ),
  710 + ],
  711 + ),
  712 + ),
  713 + ),
  714 + ),
  715 + );
  716 + }
  717 +}
@@ -5,12 +5,13 @@ import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart @@ -5,12 +5,13 @@ import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart
5 import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; 5 import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
6 import 'package:doublefeel_flutter/core/constants/app_const.dart'; 6 import 'package:doublefeel_flutter/core/constants/app_const.dart';
7 import 'package:doublefeel_flutter/core/logging/app_logger.dart'; 7 import 'package:doublefeel_flutter/core/logging/app_logger.dart';
8 -import 'package:doublefeel_flutter/core/network/api/config_api.dart';  
9 import 'package:doublefeel_flutter/core/services/app_config_service.dart'; 8 import 'package:doublefeel_flutter/core/services/app_config_service.dart';
10 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart'; 9 import 'package:doublefeel_flutter/core/services/thinking_data_service.dart';
11 import 'package:doublefeel_flutter/data/models/local/user_preferences.dart'; 10 import 'package:doublefeel_flutter/data/models/local/user_preferences.dart';
12 import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart'; 11 import 'package:doublefeel_flutter/l10n/gen/app_localizations.dart';
  12 +import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
13 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart'; 13 import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
  14 +import 'package:flutter/services.dart';
14 import 'package:flutter/widgets.dart'; 15 import 'package:flutter/widgets.dart';
15 import 'package:get/get.dart'; 16 import 'package:get/get.dart';
16 import 'package:doublefeel_flutter/core/util/app_toast.dart'; 17 import 'package:doublefeel_flutter/core/util/app_toast.dart';
@@ -195,16 +196,22 @@ class LoginController extends GetxController { @@ -195,16 +196,22 @@ class LoginController extends GetxController {
195 } 196 }
196 197
197 void openUserTerms() { 198 void openUserTerms() {
  199 + String url = environmentConfig.region.value == AppRegion.china
  200 + ? AppConst.userTerms
  201 + : AppConst.userTermsGlobal;
198 Get.toNamed( 202 Get.toNamed(
199 AppRoutes.webview, 203 AppRoutes.webview,
200 - parameters: {'url': AppConst.userTerms}, 204 + parameters: {'url': url},
201 ); 205 );
202 } 206 }
203 207
204 void openPrivacyPolicy() { 208 void openPrivacyPolicy() {
  209 + String url = environmentConfig.region.value == AppRegion.china
  210 + ? AppConst.privacyPolicy
  211 + : AppConst.privacyPolicyGlobal;
205 Get.toNamed( 212 Get.toNamed(
206 AppRoutes.webview, 213 AppRoutes.webview,
207 - parameters: {'url': AppConst.privacyPolicy}, 214 + parameters: {'url': url},
208 ); 215 );
209 } 216 }
210 217
@@ -226,13 +233,21 @@ class LoginController extends GetxController { @@ -226,13 +233,21 @@ class LoginController extends GetxController {
226 passwordController.addListener(() { 233 passwordController.addListener(() {
227 passwordInput.value = passwordController.text; 234 passwordInput.value = passwordController.text;
228 }); 235 });
  236 + resetPasswordCodeController.addListener(() {
  237 + resetPasswordCode.value = resetPasswordCodeController.text.trim();
  238 + });
229 } 239 }
230 240
  241 + bool get canResetPassword =>
  242 + resetPasswordCode.value.isNotEmpty && isPasswordValid;
  243 +
231 @override 244 @override
232 void onClose() { 245 void onClose() {
233 _countdownTimer?.cancel(); 246 _countdownTimer?.cancel();
234 phoneController.dispose(); 247 phoneController.dispose();
235 codeController.dispose(); 248 codeController.dispose();
  249 + passwordController.dispose();
  250 + resetPasswordCodeController.dispose();
236 codeFocusNode.dispose(); 251 codeFocusNode.dispose();
237 super.onClose(); 252 super.onClose();
238 } 253 }
@@ -332,7 +347,7 @@ class LoginController extends GetxController { @@ -332,7 +347,7 @@ class LoginController extends GetxController {
332 hasTriedEmailLogin.value = true; 347 hasTriedEmailLogin.value = true;
333 348
334 if (!isEmailValid) { 349 if (!isEmailValid) {
335 - AppToast.show('Invalid email format'); 350 + AppToast.show(l10n.invalidEmailFormat);
336 return; 351 return;
337 } 352 }
338 353
@@ -356,11 +371,15 @@ class LoginController extends GetxController { @@ -356,11 +371,15 @@ class LoginController extends GetxController {
356 // } finally { 371 // } finally {
357 // isLoggingIn.value = false; 372 // isLoggingIn.value = false;
358 // } 373 // }
  374 + var success = true;
  375 + if (success) {
  376 + TextInput.finishAutofillContext();
  377 + }
359 } 378 }
360 379
361 Future<void> sendRestEmail() async { 380 Future<void> sendRestEmail() async {
362 if (!isEmailValid) { 381 if (!isEmailValid) {
363 - AppToast.show('Invalid email format'); 382 + AppToast.show(l10n.invalidEmailFormat);
364 return; 383 return;
365 } 384 }
366 385
@@ -380,22 +399,20 @@ class LoginController extends GetxController { @@ -380,22 +399,20 @@ class LoginController extends GetxController {
380 // } finally { 399 // } finally {
381 // isLoggingIn.value = false; 400 // isLoggingIn.value = false;
382 // } 401 // }
383 - Get.to(EmailLoginNewPasswordView()); 402 + Get.to(() => EmailLoginNewPasswordView());
384 } 403 }
385 404
386 Future<void> resetPassword() async { 405 Future<void> resetPassword() async {
387 if (!isEmailValid) { 406 if (!isEmailValid) {
388 - AppToast.show('Invalid email format');  
389 - return;  
390 - }  
391 - if (!isPasswordValid) {  
392 - AppToast.show('Invalid password format'); 407 + AppToast.show(l10n.invalidEmailFormat);
393 return; 408 return;
394 } 409 }
  410 +
395 if (resetPasswordCodeController.text.isEmpty) { 411 if (resetPasswordCodeController.text.isEmpty) {
396 - AppToast.show('Invalid code'); 412 + AppToast.show(l10n.invalidCode);
397 return; 413 return;
398 } 414 }
  415 +
399 // TODO: 邮箱登录接口待接入,API 路径由后端确认后在此调用 416 // TODO: 邮箱登录接口待接入,API 路径由后端确认后在此调用
400 // isLoggingIn.value = true; 417 // isLoggingIn.value = true;
401 // try { 418 // try {
@@ -412,6 +429,8 @@ class LoginController extends GetxController { @@ -412,6 +429,8 @@ class LoginController extends GetxController {
412 // } finally { 429 // } finally {
413 // isLoggingIn.value = false; 430 // isLoggingIn.value = false;
414 // } 431 // }
  432 +
  433 + AppToast.show(l10n.paswordHasBeenChanged);
415 Get.back(); 434 Get.back();
416 Get.back(); 435 Get.back();
417 } 436 }
  1 +import 'package:doublefeel_flutter/core/theme/app_theme.dart';
1 import 'package:doublefeel_flutter/core/util/app_toast.dart'; 2 import 'package:doublefeel_flutter/core/util/app_toast.dart';
2 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart'; 3 import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
3 import 'package:flutter/material.dart'; 4 import 'package:flutter/material.dart';
@@ -114,10 +115,15 @@ class EmailLoginContactUsBottomSheet extends StatelessWidget { @@ -114,10 +115,15 @@ class EmailLoginContactUsBottomSheet extends StatelessWidget {
114 } 115 }
115 116
116 Future<void> sendFeedbackEmail({ 117 Future<void> sendFeedbackEmail({
117 - required String userId, 118 + required String? userId,
118 String pageName = 'Setting page', 119 String pageName = 'Setting page',
119 }) async { 120 }) async {
120 - final String cleanUserId = userId.trim().isEmpty ? 'N/A' : userId.trim(); 121 + String cleanUserId;
  122 + if (userId == null || userId == '0') {
  123 + cleanUserId = 'N/A';
  124 + } else {
  125 + cleanUserId = userId.trim().isEmpty ? 'N/A' : userId.trim();
  126 + }
121 final String body = 127 final String body =
122 'This issue was reported from $pageName by User ID: $cleanUserId.'; 128 'This issue was reported from $pageName by User ID: $cleanUserId.';
123 129
@@ -152,6 +158,12 @@ class _UserIdField extends StatelessWidget { @@ -152,6 +158,12 @@ class _UserIdField extends StatelessWidget {
152 158
153 @override 159 @override
154 Widget build(BuildContext context) { 160 Widget build(BuildContext context) {
  161 + String cleanUserId = userId;
  162 + if (userId == '0') {
  163 + cleanUserId = 'N/A';
  164 + } else {
  165 + cleanUserId = userId.trim().isEmpty ? 'N/A' : userId.trim();
  166 + }
155 return Container( 167 return Container(
156 height: 48, 168 height: 48,
157 margin: const EdgeInsets.symmetric(horizontal: 16), 169 margin: const EdgeInsets.symmetric(horizontal: 16),
@@ -168,7 +180,7 @@ class _UserIdField extends StatelessWidget { @@ -168,7 +180,7 @@ class _UserIdField extends StatelessWidget {
168 child: Padding( 180 child: Padding(
169 padding: const EdgeInsets.only(left: 16, right: 8), 181 padding: const EdgeInsets.only(left: 16, right: 8),
170 child: Text( 182 child: Text(
171 - userId, 183 + cleanUserId,
172 maxLines: 1, 184 maxLines: 1,
173 overflow: TextOverflow.ellipsis, 185 overflow: TextOverflow.ellipsis,
174 style: const TextStyle( 186 style: const TextStyle(
@@ -183,8 +195,8 @@ class _UserIdField extends StatelessWidget { @@ -183,8 +195,8 @@ class _UserIdField extends StatelessWidget {
183 color: const Color(0xFF7B9BFB), 195 color: const Color(0xFF7B9BFB),
184 child: InkWell( 196 child: InkWell(
185 onTap: () { 197 onTap: () {
186 - Clipboard.setData(ClipboardData(text: userId));  
187 - AppToast.show('Copied'); 198 + Clipboard.setData(ClipboardData(text: cleanUserId));
  199 + AppToast.show(context.l10n.copiedSuccessfully);
188 }, 200 },
189 child: SizedBox( 201 child: SizedBox(
190 width: 48, 202 width: 48,
@@ -214,28 +226,28 @@ class _SendEmailButton extends StatelessWidget { @@ -214,28 +226,28 @@ class _SendEmailButton extends StatelessWidget {
214 @override 226 @override
215 Widget build(BuildContext context) { 227 Widget build(BuildContext context) {
216 return Material( 228 return Material(
217 - color: const Color(0xFF845EEE), 229 + color: context.colors.primary,
218 borderRadius: BorderRadius.circular(24), 230 borderRadius: BorderRadius.circular(24),
219 child: InkWell( 231 child: InkWell(
220 onTap: onTap, 232 onTap: onTap,
221 borderRadius: BorderRadius.circular(24), 233 borderRadius: BorderRadius.circular(24),
222 - child: const SizedBox( 234 + child: SizedBox(
223 width: 220, 235 width: 220,
224 height: 48, 236 height: 48,
225 child: Row( 237 child: Row(
226 mainAxisAlignment: MainAxisAlignment.center, 238 mainAxisAlignment: MainAxisAlignment.center,
227 children: [ 239 children: [
228 - Image( 240 + const Image(
229 image: AssetImage( 241 image: AssetImage(
230 'assets/images/common/ic_contact_us.png', 242 'assets/images/common/ic_contact_us.png',
231 ), 243 ),
232 width: 24, 244 width: 24,
233 height: 24, 245 height: 24,
234 ), 246 ),
235 - SizedBox(width: 6), 247 + const SizedBox(width: 6),
236 Text( 248 Text(
237 - 'Send Email',  
238 - style: TextStyle( 249 + context.l10n.sendEmail,
  250 + style: const TextStyle(
239 color: Colors.white, 251 color: Colors.white,
240 fontSize: 14, 252 fontSize: 14,
241 fontWeight: FontWeight.w500, 253 fontWeight: FontWeight.w500,
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart'; @@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
4 import 'package:flutter/services.dart'; 4 import 'package:flutter/services.dart';
5 import 'package:get/get.dart'; 5 import 'package:get/get.dart';
6 6
7 -import '../../../routes/app_pages.dart';  
8 import '../controllers/login_controller.dart'; 7 import '../controllers/login_controller.dart';
9 import 'email_login_contact_us_bottom_sheet.dart'; 8 import 'email_login_contact_us_bottom_sheet.dart';
10 9
@@ -73,7 +72,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -73,7 +72,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
73 Transform.translate( 72 Transform.translate(
74 offset: const Offset(0, -8), 73 offset: const Offset(0, -8),
75 child: Text( 74 child: Text(
76 - 'Check your email', 75 + context.l10n.checkYourEmail,
77 textAlign: TextAlign.center, 76 textAlign: TextAlign.center,
78 style: TextStyle( 77 style: TextStyle(
79 color: const Color(0xFF0F0F11), 78 color: const Color(0xFF0F0F11),
@@ -88,7 +87,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -88,7 +87,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
88 TextSpan( 87 TextSpan(
89 children: [ 88 children: [
90 TextSpan( 89 TextSpan(
91 - text: 'We’ve sent a code to ', 90 + text: l10n.weVeSentACodeTo,
92 style: TextStyle( 91 style: TextStyle(
93 color: const Color(0xFF78787D), 92 color: const Color(0xFF78787D),
94 fontSize: 14, 93 fontSize: 14,
@@ -96,6 +95,9 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -96,6 +95,9 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
96 ), 95 ),
97 ), 96 ),
98 TextSpan( 97 TextSpan(
  98 + text: ' ',
  99 + ),
  100 + TextSpan(
99 text: controller.emailInput.value, 101 text: controller.emailInput.value,
100 style: TextStyle( 102 style: TextStyle(
101 color: const Color(0xFF0F0F11), 103 color: const Color(0xFF0F0F11),
@@ -104,8 +106,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -104,8 +106,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
104 ), 106 ),
105 ), 107 ),
106 TextSpan( 108 TextSpan(
107 - text:  
108 - '. Didn’t get it? Check your spam folder or try again.', 109 + text: l10n.didnTGetItCheckYourSpamFolderOrTryAgain,
109 style: TextStyle( 110 style: TextStyle(
110 color: const Color(0xFF78787D), 111 color: const Color(0xFF78787D),
111 fontSize: 14, 112 fontSize: 14,
@@ -128,7 +129,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -128,7 +129,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
128 Padding( 129 Padding(
129 padding: const EdgeInsets.only(left: 16, bottom: 15), 130 padding: const EdgeInsets.only(left: 16, bottom: 15),
130 child: Text( 131 child: Text(
131 - 'Code from email', 132 + context.l10n.codeFromEmail,
132 style: TextStyle( 133 style: TextStyle(
133 color: const Color(0xFF141414), 134 color: const Color(0xFF141414),
134 fontSize: 14, 135 fontSize: 14,
@@ -136,67 +137,65 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -136,67 +137,65 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
136 ), 137 ),
137 ), 138 ),
138 ), 139 ),
139 - Obx(() {  
140 - controller.resetPasswordCode.value;  
141 - return TextField(  
142 - controller: controller.resetPasswordCodeController,  
143 - keyboardType: TextInputType.text,  
144 - contextMenuBuilder: (BuildContext context,  
145 - EditableTextState editableTextState) {  
146 - // 获取默认的菜单项列表  
147 - List<ContextMenuButtonItem> buttonItems =  
148 - editableTextState.contextMenuButtonItems; 140 + TextField(
  141 + controller: controller.resetPasswordCodeController,
  142 + keyboardType: TextInputType.text,
  143 + textInputAction: TextInputAction.next,
  144 + contextMenuBuilder: (BuildContext context,
  145 + EditableTextState editableTextState) {
  146 + // 获取默认的菜单项列表
  147 + List<ContextMenuButtonItem> buttonItems =
  148 + editableTextState.contextMenuButtonItems;
149 149
150 - // 例如:只保留“粘贴” (Paste)  
151 - buttonItems.removeWhere((item) =>  
152 - item.type != ContextMenuButtonType.paste);  
153 - return AdaptiveTextSelectionToolbar.buttonItems(  
154 - anchors: editableTextState.contextMenuAnchors,  
155 - buttonItems: buttonItems,  
156 - );  
157 - },  
158 - inputFormatters: [  
159 - FilteringTextInputFormatter.deny(RegExp(r'\s')),  
160 - FilteringTextInputFormatter.allow(  
161 - RegExp(r'[a-zA-Z0-9@._\-+]'),  
162 - ),  
163 - ],  
164 - style: TextStyle(  
165 - color: context.colors.textPrimary, 150 + // 例如:只保留“粘贴” (Paste)
  151 + buttonItems.removeWhere((item) =>
  152 + item.type != ContextMenuButtonType.paste);
  153 + return AdaptiveTextSelectionToolbar.buttonItems(
  154 + anchors: editableTextState.contextMenuAnchors,
  155 + buttonItems: buttonItems,
  156 + );
  157 + },
  158 + // inputFormatters: [
  159 + // FilteringTextInputFormatter.deny(RegExp(r'\s')),
  160 + // FilteringTextInputFormatter.allow(
  161 + // RegExp(r'[a-zA-Z0-9@._\-+]'),
  162 + // ),
  163 + // ],
  164 + style: TextStyle(
  165 + color: context.colors.textPrimary,
  166 + fontSize: 16,
  167 + fontWeight: FontWeight.w400,
  168 + ),
  169 + decoration: InputDecoration(
  170 + hintText: l10n.code,
  171 + hintStyle: TextStyle(
  172 + color: context.colors.textTertiary,
166 fontSize: 16, 173 fontSize: 16,
167 - fontWeight: FontWeight.w400,  
168 ), 174 ),
169 - decoration: InputDecoration(  
170 - hintText: 'Code',  
171 - hintStyle: TextStyle(  
172 - color: context.colors.textTertiary,  
173 - fontSize: 16,  
174 - ),  
175 - counterText: '',  
176 - filled: true,  
177 - fillColor: Color(0xFFF3F3F3),  
178 - contentPadding: const EdgeInsets.symmetric(  
179 - horizontal: 20,  
180 - vertical: 16,  
181 - ),  
182 - border: OutlineInputBorder(  
183 - borderRadius: BorderRadius.circular(27),  
184 - borderSide: BorderSide.none,  
185 - ),  
186 - enabledBorder: OutlineInputBorder(  
187 - borderRadius: BorderRadius.circular(27),  
188 - borderSide: BorderSide.none,  
189 - ),  
190 - focusedBorder: OutlineInputBorder(  
191 - borderRadius: BorderRadius.circular(27),  
192 - borderSide: BorderSide(  
193 - color: context.colors.primary,  
194 - width: 1,  
195 - ), 175 + counterText: '',
  176 + filled: true,
  177 + fillColor: Color(0xFFF3F3F3),
  178 + contentPadding: const EdgeInsets.symmetric(
  179 + horizontal: 20,
  180 + vertical: 16,
  181 + ),
  182 + border: OutlineInputBorder(
  183 + borderRadius: BorderRadius.circular(27),
  184 + borderSide: BorderSide.none,
  185 + ),
  186 + enabledBorder: OutlineInputBorder(
  187 + borderRadius: BorderRadius.circular(27),
  188 + borderSide: BorderSide.none,
  189 + ),
  190 + focusedBorder: OutlineInputBorder(
  191 + borderRadius: BorderRadius.circular(27),
  192 + borderSide: BorderSide(
  193 + color: context.colors.primary,
  194 + width: 1,
196 ), 195 ),
197 ), 196 ),
198 - );  
199 - }), 197 + ),
  198 + ),
200 199
201 SizedBox( 200 SizedBox(
202 height: 20, 201 height: 20,
@@ -204,7 +203,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -204,7 +203,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
204 Padding( 203 Padding(
205 padding: const EdgeInsets.only(left: 16, bottom: 15), 204 padding: const EdgeInsets.only(left: 16, bottom: 15),
206 child: Text( 205 child: Text(
207 - 'New password', 206 + l10n.newPassword,
208 style: TextStyle( 207 style: TextStyle(
209 color: const Color(0xFF141414), 208 color: const Color(0xFF141414),
210 fontSize: 14, 209 fontSize: 14,
@@ -219,6 +218,13 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -219,6 +218,13 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
219 controller: controller.passwordController, 218 controller: controller.passwordController,
220 keyboardType: TextInputType.visiblePassword, 219 keyboardType: TextInputType.visiblePassword,
221 obscureText: isObscured, 220 obscureText: isObscured,
  221 + autofillHints: const [AutofillHints.newPassword],
  222 + onSubmitted: (value) {
  223 + if (controller.canResetPassword) {
  224 + FocusScope.of(context).unfocus();
  225 + controller.resetPassword();
  226 + }
  227 + },
222 obscuringCharacter: '*', 228 obscuringCharacter: '*',
223 // maxLength: 13, 229 // maxLength: 13,
224 contextMenuBuilder: (BuildContext context, 230 contextMenuBuilder: (BuildContext context,
@@ -304,7 +310,8 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -304,7 +310,8 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
304 !pwOk && 310 !pwOk &&
305 controller.passwordInput.value.isNotEmpty; 311 controller.passwordInput.value.isNotEmpty;
306 return Text( 312 return Text(
307 - 'Password must be at least 6 characters and include 1 number and 1 uppercase letter.', 313 + context.l10n
  314 + .passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter,
308 style: TextStyle( 315 style: TextStyle(
309 color: isError 316 color: isError
310 ? Colors.red 317 ? Colors.red
@@ -318,11 +325,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -318,11 +325,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
318 325
319 const SizedBox(height: 39), 326 const SizedBox(height: 39),
320 Obx(() { 327 Obx(() {
321 - final canProceed = controller  
322 - .resetPasswordCodeController  
323 - .text  
324 - .isNotEmpty &&  
325 - controller.isPasswordValid; 328 + final canProceed = controller.canResetPassword;
326 return SizedBox( 329 return SizedBox(
327 height: 48, 330 height: 48,
328 child: ElevatedButton( 331 child: ElevatedButton(
@@ -343,7 +346,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -343,7 +346,7 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
343 fontWeight: FontWeight.w600, 346 fontWeight: FontWeight.w600,
344 ), 347 ),
345 ), 348 ),
346 - child: Text('Reset password'), 349 + child: Text(context.l10n.resetPassword),
347 ), 350 ),
348 ); 351 );
349 }), 352 }),
@@ -352,11 +355,11 @@ class EmailLoginNewPasswordView extends GetView<LoginController> { @@ -352,11 +355,11 @@ class EmailLoginNewPasswordView extends GetView<LoginController> {
352 Center( 355 Center(
353 child: GestureDetector( 356 child: GestureDetector(
354 onTap: () => showEmailLoginContactUsBottomSheet( 357 onTap: () => showEmailLoginContactUsBottomSheet(
355 - userId: controller.emailInput.value.trim(),  
356 - pageName: 'Reset password page', 358 + userId: '0',
  359 + pageName: 'Log In page',
357 ), 360 ),
358 child: Text( 361 child: Text(
359 - 'Having trouble? Contact us', 362 + l10n.havingTroubleContactUs,
360 style: TextStyle( 363 style: TextStyle(
361 color: const Color(0xFF39CAF4), 364 color: const Color(0xFF39CAF4),
362 fontSize: 14, 365 fontSize: 14,
@@ -71,7 +71,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -71,7 +71,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
71 Transform.translate( 71 Transform.translate(
72 offset: const Offset(0, -8), 72 offset: const Offset(0, -8),
73 child: Text( 73 child: Text(
74 - 'Reset password', 74 + context.l10n.resetPassword,
75 textAlign: TextAlign.center, 75 textAlign: TextAlign.center,
76 style: TextStyle( 76 style: TextStyle(
77 color: const Color(0xFF0F0F11), 77 color: const Color(0xFF0F0F11),
@@ -82,7 +82,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -82,7 +82,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
82 ), 82 ),
83 83
84 Text( 84 Text(
85 - '''You'll receive a code via email to reset your password.''', 85 + context.l10n.youLlReceiveACodeViaEmailToResetYourPassword,
86 textAlign: TextAlign.center, 86 textAlign: TextAlign.center,
87 style: TextStyle( 87 style: TextStyle(
88 color: const Color(0xFF78787D), 88 color: const Color(0xFF78787D),
@@ -103,6 +103,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -103,6 +103,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
103 final emailNotEmpty = 103 final emailNotEmpty =
104 controller.emailInput.value.isNotEmpty; 104 controller.emailInput.value.isNotEmpty;
105 return TextField( 105 return TextField(
  106 + autofillHints: const [AutofillHints.email],
106 controller: controller.emailController, 107 controller: controller.emailController,
107 keyboardType: TextInputType.emailAddress, 108 keyboardType: TextInputType.emailAddress,
108 // maxLength: 13, 109 // maxLength: 13,
@@ -120,12 +121,12 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -120,12 +121,12 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
120 buttonItems: buttonItems, 121 buttonItems: buttonItems,
121 ); 122 );
122 }, 123 },
123 - inputFormatters: [  
124 - FilteringTextInputFormatter.deny(RegExp(r'\s')),  
125 - FilteringTextInputFormatter.allow(  
126 - RegExp(r'[a-zA-Z0-9@._\-+]'),  
127 - ),  
128 - ], 124 + // inputFormatters: [
  125 + // FilteringTextInputFormatter.deny(RegExp(r'\s')),
  126 + // FilteringTextInputFormatter.allow(
  127 + // RegExp(r'[a-zA-Z0-9@._\-+]'),
  128 + // ),
  129 + // ],
129 style: TextStyle( 130 style: TextStyle(
130 color: context.colors.textPrimary, 131 color: context.colors.textPrimary,
131 fontSize: 16, 132 fontSize: 16,
@@ -187,7 +188,8 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -187,7 +188,8 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
187 child: Padding( 188 child: Padding(
188 padding: const EdgeInsets.only(left: 8), 189 padding: const EdgeInsets.only(left: 8),
189 child: Text( 190 child: Text(
190 - 'This email is not registered. Please check and try again.', 191 + context.l10n
  192 + .thisEmailIsNotRegisteredPleaseCheckAndTryAgain,
191 style: TextStyle( 193 style: TextStyle(
192 color: Colors.red, 194 color: Colors.red,
193 fontSize: 12, 195 fontSize: 12,
@@ -224,7 +226,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> { @@ -224,7 +226,7 @@ class EmailLoginResetPasswordView extends GetView<LoginController> {
224 fontWeight: FontWeight.w600, 226 fontWeight: FontWeight.w600,
225 ), 227 ),
226 ), 228 ),
227 - child: Text('Send reset email'), 229 + child: Text(context.l10n.sendResetEmail),
228 ), 230 ),
229 ); 231 );
230 }), 232 }),
@@ -83,7 +83,7 @@ class EmailLoginView extends GetView<LoginController> { @@ -83,7 +83,7 @@ class EmailLoginView extends GetView<LoginController> {
83 ), 83 ),
84 84
85 Text( 85 Text(
86 - 'Enter your email and password', 86 + l10n.enterYourEmailAndPassword,
87 textAlign: TextAlign.center, 87 textAlign: TextAlign.center,
88 style: TextStyle( 88 style: TextStyle(
89 color: const Color(0xFF78787D), 89 color: const Color(0xFF78787D),
@@ -96,117 +96,22 @@ class EmailLoginView extends GetView<LoginController> { @@ -96,117 +96,22 @@ class EmailLoginView extends GetView<LoginController> {
96 // 输入区 96 // 输入区
97 Padding( 97 Padding(
98 padding: const EdgeInsets.symmetric(horizontal: 28), 98 padding: const EdgeInsets.symmetric(horizontal: 28),
99 - child: Column(  
100 - crossAxisAlignment: CrossAxisAlignment.stretch,  
101 - children: [  
102 - // email输入框  
103 - Obx(() {  
104 - final emailNotEmpty =  
105 - controller.emailInput.value.isNotEmpty;  
106 - return TextField(  
107 - controller: controller.emailController,  
108 - keyboardType: TextInputType.emailAddress,  
109 - // maxLength: 13,  
110 - contextMenuBuilder: (BuildContext context,  
111 - EditableTextState editableTextState) {  
112 - // 获取默认的菜单项列表  
113 - List<ContextMenuButtonItem> buttonItems =  
114 - editableTextState.contextMenuButtonItems;  
115 -  
116 - // 例如:只保留“粘贴” (Paste)  
117 - buttonItems.removeWhere((item) =>  
118 - item.type != ContextMenuButtonType.paste);  
119 - return AdaptiveTextSelectionToolbar.buttonItems(  
120 - anchors: editableTextState.contextMenuAnchors,  
121 - buttonItems: buttonItems,  
122 - );  
123 - },  
124 - inputFormatters: [  
125 - FilteringTextInputFormatter.deny(RegExp(r'\s')),  
126 - FilteringTextInputFormatter.allow(  
127 - RegExp(r'[a-zA-Z0-9@._\-+]'),  
128 - ),  
129 - ],  
130 - style: TextStyle(  
131 - color: context.colors.textPrimary,  
132 - fontSize: 16,  
133 - fontWeight: FontWeight.w400,  
134 - ),  
135 - decoration: InputDecoration(  
136 - hintText: l10n.emailLoginEmailHint,  
137 - hintStyle: TextStyle(  
138 - color: context.colors.textTertiary,  
139 - fontSize: 16,  
140 - ),  
141 - counterText: '',  
142 - filled: true,  
143 - fillColor: Color(0xFFF3F3F3),  
144 - contentPadding: const EdgeInsets.symmetric(  
145 - horizontal: 20,  
146 - vertical: 16,  
147 - ),  
148 - border: OutlineInputBorder(  
149 - borderRadius: BorderRadius.circular(27),  
150 - borderSide: BorderSide.none,  
151 - ),  
152 - enabledBorder: OutlineInputBorder(  
153 - borderRadius: BorderRadius.circular(27),  
154 - borderSide: BorderSide.none,  
155 - ),  
156 - focusedBorder: OutlineInputBorder(  
157 - borderRadius: BorderRadius.circular(27),  
158 - borderSide: BorderSide(  
159 - color: context.colors.primary,  
160 - width: 1,  
161 - ),  
162 - ),  
163 - suffixIcon: emailNotEmpty  
164 - ? GestureDetector(  
165 - onTap: controller.emailController.clear,  
166 - child: Icon(  
167 - Icons.cancel,  
168 - color: context.colors.textTertiary,  
169 - size: 18,  
170 - ),  
171 - )  
172 - : null,  
173 - ),  
174 - );  
175 - }),  
176 -  
177 - SizedBox(  
178 - height: 20,  
179 - child: Obx(() {  
180 - final hasTried =  
181 - controller.hasTriedEmailLogin.value;  
182 - final emailOk = controller.isEmailValid;  
183 - final showError = hasTried && !emailOk;  
184 - return Visibility(  
185 - visible: showError,  
186 - maintainSize: true,  
187 - maintainAnimation: true,  
188 - maintainState: true,  
189 - child: Padding(  
190 - padding: const EdgeInsets.only(left: 8),  
191 - child: Text(  
192 - 'Invalid email format',  
193 - style: TextStyle(  
194 - color: Colors.red,  
195 - fontSize: 12,  
196 - fontWeight: FontWeight.w400,  
197 - ),  
198 - ),  
199 - ),  
200 - );  
201 - }),  
202 - ),  
203 - Obx(() {  
204 - final isObscured =  
205 - controller.isPasswordObscured.value;  
206 - return TextField(  
207 - controller: controller.passwordController,  
208 - keyboardType: TextInputType.visiblePassword,  
209 - obscureText: isObscured, 99 + child: AutofillGroup(
  100 + child: Column(
  101 + crossAxisAlignment: CrossAxisAlignment.stretch,
  102 + children: [
  103 + // email输入框
  104 + Obx(() {
  105 + final emailNotEmpty =
  106 + controller.emailInput.value.isNotEmpty;
  107 + return TextField(
  108 + autofillHints: const [
  109 + AutofillHints.email,
  110 + AutofillHints.username
  111 + ],
  112 + textInputAction: TextInputAction.next,
  113 + controller: controller.emailController,
  114 + keyboardType: TextInputType.emailAddress,
210 // maxLength: 13, 115 // maxLength: 13,
211 contextMenuBuilder: (BuildContext context, 116 contextMenuBuilder: (BuildContext context,
212 EditableTextState editableTextState) { 117 EditableTextState editableTextState) {
@@ -214,7 +119,7 @@ class EmailLoginView extends GetView<LoginController> { @@ -214,7 +119,7 @@ class EmailLoginView extends GetView<LoginController> {
214 List<ContextMenuButtonItem> buttonItems = 119 List<ContextMenuButtonItem> buttonItems =
215 editableTextState.contextMenuButtonItems; 120 editableTextState.contextMenuButtonItems;
216 121
217 - // 例如:只保留"粘贴" (Paste) 122 + // 例如:只保留“粘贴” (Paste)
218 buttonItems.removeWhere((item) => 123 buttonItems.removeWhere((item) =>
219 item.type != ContextMenuButtonType.paste); 124 item.type != ContextMenuButtonType.paste);
220 return AdaptiveTextSelectionToolbar.buttonItems( 125 return AdaptiveTextSelectionToolbar.buttonItems(
@@ -222,146 +127,271 @@ class EmailLoginView extends GetView<LoginController> { @@ -222,146 +127,271 @@ class EmailLoginView extends GetView<LoginController> {
222 buttonItems: buttonItems, 127 buttonItems: buttonItems,
223 ); 128 );
224 }, 129 },
225 - inputFormatters: [  
226 - FilteringTextInputFormatter.deny(RegExp(r'\s')),  
227 - FilteringTextInputFormatter.allow(  
228 - RegExp(r'[\x21-\x7E]'),  
229 - ),  
230 - ], 130 + // inputFormatters: [
  131 + // FilteringTextInputFormatter.deny(RegExp(r'\s')),
  132 + // FilteringTextInputFormatter.allow(
  133 + // RegExp(r'[a-zA-Z0-9@._\-+]'),
  134 + // ),
  135 + // ],
231 style: TextStyle( 136 style: TextStyle(
232 color: context.colors.textPrimary, 137 color: context.colors.textPrimary,
233 fontSize: 16, 138 fontSize: 16,
234 fontWeight: FontWeight.w400, 139 fontWeight: FontWeight.w400,
235 ), 140 ),
236 decoration: InputDecoration( 141 decoration: InputDecoration(
237 - hintText: l10n.emailLoginYourPassword,  
238 - hintStyle: TextStyle(  
239 - color: context.colors.textTertiary,  
240 - fontSize: 16,  
241 - ),  
242 - counterText: '',  
243 - filled: true,  
244 - fillColor: Color(0xFFF3F3F3),  
245 - contentPadding: const EdgeInsets.symmetric(  
246 - horizontal: 20,  
247 - vertical: 16,  
248 - ),  
249 - border: OutlineInputBorder(  
250 - borderRadius: BorderRadius.circular(27),  
251 - borderSide: BorderSide.none,  
252 - ),  
253 - enabledBorder: OutlineInputBorder(  
254 - borderRadius: BorderRadius.circular(27),  
255 - borderSide: BorderSide.none, 142 + hintText: l10n.emailLoginEmailHint,
  143 + hintStyle: TextStyle(
  144 + color: context.colors.textTertiary,
  145 + fontSize: 16,
  146 + ),
  147 + counterText: '',
  148 + filled: true,
  149 + fillColor: Color(0xFFF3F3F3),
  150 + contentPadding: const EdgeInsets.symmetric(
  151 + horizontal: 20,
  152 + vertical: 16,
  153 + ),
  154 + border: OutlineInputBorder(
  155 + borderRadius: BorderRadius.circular(27),
  156 + borderSide: BorderSide.none,
  157 + ),
  158 + enabledBorder: OutlineInputBorder(
  159 + borderRadius: BorderRadius.circular(27),
  160 + borderSide: BorderSide.none,
  161 + ),
  162 + focusedBorder: OutlineInputBorder(
  163 + borderRadius: BorderRadius.circular(27),
  164 + borderSide: BorderSide(
  165 + color: context.colors.primary,
  166 + width: 1,
256 ), 167 ),
257 - focusedBorder: OutlineInputBorder(  
258 - borderRadius: BorderRadius.circular(27),  
259 - borderSide: BorderSide(  
260 - color: context.colors.primary,  
261 - width: 1, 168 + ),
  169 + suffixIcon: emailNotEmpty
  170 + ? GestureDetector(
  171 + onTap: controller.emailController.clear,
  172 + child: Icon(
  173 + Icons.cancel,
  174 + color: context.colors.textTertiary,
  175 + size: 18,
  176 + ),
  177 + )
  178 + : null,
  179 + ),
  180 + );
  181 + }),
  182 +
  183 + SizedBox(
  184 + height: 20,
  185 + child: Obx(() {
  186 + final hasTried =
  187 + controller.hasTriedEmailLogin.value;
  188 + final emailOk = controller.isEmailValid;
  189 + final showError = hasTried && !emailOk;
  190 + return Visibility(
  191 + visible: showError,
  192 + maintainSize: true,
  193 + maintainAnimation: true,
  194 + maintainState: true,
  195 + child: Padding(
  196 + padding: const EdgeInsets.only(left: 8),
  197 + child: Text(
  198 + context.l10n.invalidEmailFormat,
  199 + style: TextStyle(
  200 + color: Colors.red,
  201 + fontSize: 12,
  202 + fontWeight: FontWeight.w400,
262 ), 203 ),
263 ), 204 ),
264 - suffixIconConstraints: const BoxConstraints(  
265 - minWidth: 24,  
266 - minHeight: 24, 205 + ),
  206 + );
  207 + }),
  208 + ),
  209 + Obx(() {
  210 + final isObscured =
  211 + controller.isPasswordObscured.value;
  212 + return TextField(
  213 + controller: controller.passwordController,
  214 + keyboardType: TextInputType.visiblePassword,
  215 + obscureText: isObscured,
  216 + autofillHints: const [
  217 + AutofillHints.password,
  218 + ],
  219 + // maxLength: 13,
  220 + contextMenuBuilder: (BuildContext context,
  221 + EditableTextState editableTextState) {
  222 + // 获取默认的菜单项列表
  223 + List<ContextMenuButtonItem> buttonItems =
  224 + editableTextState.contextMenuButtonItems;
  225 +
  226 + // 例如:只保留"粘贴" (Paste)
  227 + buttonItems.removeWhere((item) =>
  228 + item.type != ContextMenuButtonType.paste);
  229 + return AdaptiveTextSelectionToolbar
  230 + .buttonItems(
  231 + anchors:
  232 + editableTextState.contextMenuAnchors,
  233 + buttonItems: buttonItems,
  234 + );
  235 + },
  236 + inputFormatters: [
  237 + FilteringTextInputFormatter.deny(
  238 + RegExp(r'\s')),
  239 + FilteringTextInputFormatter.allow(
  240 + RegExp(r'[\x21-\x7E]'),
267 ), 241 ),
268 - suffixIcon: GestureDetector(  
269 - onTap: controller.togglePasswordVisibility,  
270 - child: Padding(  
271 - padding: const EdgeInsets.only(right: 16),  
272 - child: Image.asset(  
273 - isObscured  
274 - ? 'assets/images/common/ic_hide_password.png'  
275 - : 'assets/images/common/ic_show_password.webp',  
276 - color: context.colors.textTertiary,  
277 - width: 24,  
278 - height: 24, 242 + ],
  243 + style: TextStyle(
  244 + color: context.colors.textPrimary,
  245 + fontSize: 16,
  246 + fontWeight: FontWeight.w400,
  247 + ),
  248 + onSubmitted: (_) {
  249 + final isLoggingIn =
  250 + controller.isLoggingIn.value;
  251 + final canProceed =
  252 + controller.isPasswordValid &&
  253 + controller.isEmailValid;
  254 + if (!isLoggingIn && canProceed) {
  255 + FocusScope.of(context).nextFocus();
  256 + controller.emailLogin();
  257 + }
  258 + },
  259 + decoration: InputDecoration(
  260 + hintText: l10n.emailLoginYourPassword,
  261 + hintStyle: TextStyle(
  262 + color: context.colors.textTertiary,
  263 + fontSize: 16,
  264 + ),
  265 + counterText: '',
  266 + filled: true,
  267 + fillColor: Color(0xFFF3F3F3),
  268 + contentPadding: const EdgeInsets.symmetric(
  269 + horizontal: 20,
  270 + vertical: 16,
  271 + ),
  272 + border: OutlineInputBorder(
  273 + borderRadius: BorderRadius.circular(27),
  274 + borderSide: BorderSide.none,
  275 + ),
  276 + enabledBorder: OutlineInputBorder(
  277 + borderRadius: BorderRadius.circular(27),
  278 + borderSide: BorderSide.none,
  279 + ),
  280 + focusedBorder: OutlineInputBorder(
  281 + borderRadius: BorderRadius.circular(27),
  282 + borderSide: BorderSide(
  283 + color: context.colors.primary,
  284 + width: 1,
279 ), 285 ),
280 ), 286 ),
281 - )));  
282 - }),  
283 -  
284 - const SizedBox(height: 8), 287 + suffixIconConstraints: const BoxConstraints(
  288 + minWidth: 24,
  289 + minHeight: 24,
  290 + ),
  291 + suffixIcon: GestureDetector(
  292 + onTap:
  293 + controller.togglePasswordVisibility,
  294 + child: Padding(
  295 + padding:
  296 + const EdgeInsets.only(right: 16),
  297 + child: Image.asset(
  298 + isObscured
  299 + ? 'assets/images/common/ic_hide_password.png'
  300 + : 'assets/images/common/ic_show_password.webp',
  301 + color: context.colors.textTertiary,
  302 + width: 24,
  303 + height: 24,
  304 + ),
  305 + ),
  306 + )));
  307 + }),
285 308
286 - // 密码格式提示  
287 - Obx(() {  
288 - final hasTried = controller.hasTriedEmailLogin.value;  
289 - final pwOk = controller.isPasswordValid;  
290 - final isError = hasTried && !pwOk;  
291 - return Text(  
292 - 'Password must be at least 6 characters and include 1 number and 1 uppercase letter.',  
293 - style: TextStyle(  
294 - color: isError  
295 - ? Colors.red  
296 - : context.colors.textTertiary,  
297 - fontSize: 12,  
298 - fontWeight: FontWeight.w400,  
299 - ),  
300 - textAlign: TextAlign.center,  
301 - );  
302 - }), 309 + const SizedBox(height: 8),
303 310
304 - const SizedBox(height: 24),  
305 - Center(  
306 - child: GestureDetector(  
307 - onTap: () {  
308 - Get.to(() => const EmailLoginResetPasswordView());  
309 - },  
310 - child: Text(  
311 - 'Forgot password?', 311 + // 密码格式提示
  312 + Obx(() {
  313 + final hasTried =
  314 + controller.hasTriedEmailLogin.value;
  315 + final pwOk = controller.isPasswordValid;
  316 + final isError = hasTried && !pwOk;
  317 + return Text(
  318 + context.l10n
  319 + .passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter,
312 style: TextStyle( 320 style: TextStyle(
313 - color: const Color(0xFF7B9BFB),  
314 - fontSize: 14,  
315 - fontWeight: FontWeight.w500,  
316 - decoration: TextDecoration.underline,  
317 - decorationColor: const Color(0xFF7B9BFB), 321 + color: isError
  322 + ? Colors.red
  323 + : context.colors.textTertiary,
  324 + fontSize: 12,
  325 + fontWeight: FontWeight.w400,
  326 + ),
  327 + textAlign: TextAlign.center,
  328 + );
  329 + }),
  330 +
  331 + const SizedBox(height: 24),
  332 + Center(
  333 + child: GestureDetector(
  334 + onTap: () {
  335 + Get.to(
  336 + () => const EmailLoginResetPasswordView());
  337 + },
  338 + child: Text(
  339 + l10n.forgotPassword,
  340 + style: TextStyle(
  341 + color: const Color(0xFF7B9BFB),
  342 + fontSize: 14,
  343 + fontWeight: FontWeight.w500,
  344 + decoration: TextDecoration.underline,
  345 + decorationColor: const Color(0xFF7B9BFB),
  346 + ),
318 ), 347 ),
319 ), 348 ),
320 ), 349 ),
321 - ),  
322 - const SizedBox(height: 48), 350 + const SizedBox(height: 48),
323 351
324 - // Continue 按钮  
325 - Obx(() {  
326 - final isLoggingIn = controller.isLoggingIn.value;  
327 - final canProceed = controller.isPasswordValid &&  
328 - controller.isEmailValid;  
329 - return SizedBox(  
330 - height: 48,  
331 - child: ElevatedButton(  
332 - onPressed: (!isLoggingIn && canProceed)  
333 - ? controller.emailLogin  
334 - : null,  
335 - style: ElevatedButton.styleFrom(  
336 - backgroundColor: context.colors.primary,  
337 - foregroundColor: Colors.white,  
338 - disabledBackgroundColor: context.colors.primary  
339 - .withValues(alpha: 0.4),  
340 - disabledForegroundColor: Colors.white,  
341 - elevation: 0,  
342 - shape: RoundedRectangleBorder(  
343 - borderRadius: BorderRadius.circular(24),  
344 - ),  
345 - textStyle: const TextStyle(  
346 - fontSize: 16,  
347 - fontWeight: FontWeight.w600, 352 + // Continue 按钮
  353 + Obx(() {
  354 + final isLoggingIn = controller.isLoggingIn.value;
  355 + final canProceed = controller.isPasswordValid &&
  356 + controller.isEmailValid;
  357 + return SizedBox(
  358 + height: 48,
  359 + child: ElevatedButton(
  360 + onPressed: (!isLoggingIn && canProceed)
  361 + ? controller.emailLogin
  362 + : null,
  363 + style: ElevatedButton.styleFrom(
  364 + backgroundColor: context.colors.primary,
  365 + foregroundColor: Colors.white,
  366 + disabledBackgroundColor: context
  367 + .colors.primary
  368 + .withValues(alpha: 0.4),
  369 + disabledForegroundColor: Colors.white,
  370 + elevation: 0,
  371 + shape: RoundedRectangleBorder(
  372 + borderRadius: BorderRadius.circular(24),
  373 + ),
  374 + textStyle: const TextStyle(
  375 + fontSize: 16,
  376 + fontWeight: FontWeight.w600,
  377 + ),
348 ), 378 ),
  379 + child: isLoggingIn
  380 + ? Row(
  381 + mainAxisAlignment:
  382 + MainAxisAlignment.center,
  383 + children: [
  384 + const _SpinningLoader(),
  385 + const SizedBox(width: 8),
  386 + Text(l10n.phoneLoginLoggingIn),
  387 + ],
  388 + )
  389 + : Text(l10n.continueButton),
349 ), 390 ),
350 - child: isLoggingIn  
351 - ? Row(  
352 - mainAxisAlignment:  
353 - MainAxisAlignment.center,  
354 - children: [  
355 - const _SpinningLoader(),  
356 - const SizedBox(width: 8),  
357 - Text(l10n.phoneLoginLoggingIn),  
358 - ],  
359 - )  
360 - : Text(l10n.continueButton),  
361 - ),  
362 - );  
363 - }),  
364 - ], 391 + );
  392 + }),
  393 + ],
  394 + ),
365 ), 395 ),
366 ), 396 ),
367 ], 397 ],
@@ -427,34 +457,3 @@ class _SpinningLoaderState extends State<_SpinningLoader> @@ -427,34 +457,3 @@ class _SpinningLoaderState extends State<_SpinningLoader>
427 ); 457 );
428 } 458 }
429 } 459 }
430 -  
431 -// ── 手机号格式化 ───────────────────────────────────────────────────  
432 -class _PhoneTextInputFormatter extends TextInputFormatter {  
433 - @override  
434 - TextEditingValue formatEditUpdate(  
435 - TextEditingValue oldValue,  
436 - TextEditingValue newValue,  
437 - ) {  
438 - final text = newValue.text.replaceAll(' ', '');  
439 - if (text.isEmpty) {  
440 - return newValue;  
441 - }  
442 -  
443 - final buffer = StringBuffer();  
444 - for (int i = 0; i < text.length; i++) {  
445 - buffer.write(text[i]);  
446 - final nonLeadingIndex = i + 1;  
447 - if (nonLeadingIndex == 3 || nonLeadingIndex == 7) {  
448 - if (nonLeadingIndex < text.length) {  
449 - buffer.write(' ');  
450 - }  
451 - }  
452 - }  
453 -  
454 - final string = buffer.toString();  
455 - return newValue.copyWith(  
456 - text: string,  
457 - selection: TextSelection.collapsed(offset: string.length),  
458 - );  
459 - }  
460 -}  
1 import 'dart:async'; 1 import 'dart:async';
2 2
3 import 'package:doublefeel_flutter/app/routes/app_pages.dart'; 3 import 'package:doublefeel_flutter/app/routes/app_pages.dart';
  4 +import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
4 import 'package:doublefeel_flutter/core/constants/app_const.dart'; 5 import 'package:doublefeel_flutter/core/constants/app_const.dart';
5 import 'package:doublefeel_flutter/core/constants/intent_keys.dart'; 6 import 'package:doublefeel_flutter/core/constants/intent_keys.dart';
6 import 'package:doublefeel_flutter/core/network/api/pay_api.dart'; 7 import 'package:doublefeel_flutter/core/network/api/pay_api.dart';
@@ -60,6 +61,7 @@ class PurchaseController extends GetxController { @@ -60,6 +61,7 @@ class PurchaseController extends GetxController {
60 61
61 bool isFromOnboard = false; 62 bool isFromOnboard = false;
62 String channelType = ''; 63 String channelType = '';
  64 + final environmentConfig = Get.find<AppEnvironmentConfig>();
63 65
64 @override 66 @override
65 void onInit() { 67 void onInit() {
@@ -396,16 +398,22 @@ class PurchaseController extends GetxController { @@ -396,16 +398,22 @@ class PurchaseController extends GetxController {
396 } 398 }
397 399
398 void openUserTerms() { 400 void openUserTerms() {
  401 + String url = environmentConfig.region.value == AppRegion.china
  402 + ? AppConst.userTerms
  403 + : AppConst.userTermsGlobal;
399 Get.toNamed( 404 Get.toNamed(
400 AppRoutes.webview, 405 AppRoutes.webview,
401 - parameters: {'url': AppConst.userTerms}, 406 + parameters: {'url': url},
402 ); 407 );
403 } 408 }
404 409
405 void openPrivacyPolicy() { 410 void openPrivacyPolicy() {
  411 + String url = environmentConfig.region.value == AppRegion.china
  412 + ? AppConst.privacyPolicy
  413 + : AppConst.privacyPolicyGlobal;
406 Get.toNamed( 414 Get.toNamed(
407 AppRoutes.webview, 415 AppRoutes.webview,
408 - parameters: {'url': AppConst.privacyPolicy}, 416 + parameters: {'url': url},
409 ); 417 );
410 } 418 }
411 419
1 import 'package:doublefeel_flutter/app/modules/friends/bindings/friend_home_binding.dart'; 1 import 'package:doublefeel_flutter/app/modules/friends/bindings/friend_home_binding.dart';
2 import 'package:doublefeel_flutter/app/modules/friends/views/friend_home_page.dart'; 2 import 'package:doublefeel_flutter/app/modules/friends/views/friend_home_page.dart';
3 -import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart'; 3 +import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_controller.dart';
4 import 'package:doublefeel_flutter/app/modules/membership_offer/bindings/membership_detail_binding.dart'; 4 import 'package:doublefeel_flutter/app/modules/membership_offer/bindings/membership_detail_binding.dart';
5 import 'package:doublefeel_flutter/app/modules/membership_offer/views/membership_detail_view.dart'; 5 import 'package:doublefeel_flutter/app/modules/membership_offer/views/membership_detail_view.dart';
6 import 'package:doublefeel_flutter/core/config/app_environment_config.dart'; 6 import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
@@ -50,6 +50,7 @@ import '../modules/watch_theme/views/watch_theme_preview_view.dart'; @@ -50,6 +50,7 @@ import '../modules/watch_theme/views/watch_theme_preview_view.dart';
50 import '../modules/watch_theme/views/watch_theme_view.dart'; 50 import '../modules/watch_theme/views/watch_theme_view.dart';
51 import '../modules/account_settings/bindings/account_settings_binding.dart'; 51 import '../modules/account_settings/bindings/account_settings_binding.dart';
52 import '../modules/account_settings/views/account_settings_view.dart'; 52 import '../modules/account_settings/views/account_settings_view.dart';
  53 +import '../modules/home/widgets/my/security_email_views.dart';
53 import '../modules/webview/bindings/webview_binding.dart'; 54 import '../modules/webview/bindings/webview_binding.dart';
54 import '../modules/webview/views/webview_page.dart'; 55 import '../modules/webview/views/webview_page.dart';
55 56
@@ -216,5 +217,14 @@ abstract final class AppPages { @@ -216,5 +217,14 @@ abstract final class AppPages {
216 page: () => const AccountSettingsView(), 217 page: () => const AccountSettingsView(),
217 binding: AccountSettingsBinding(), 218 binding: AccountSettingsBinding(),
218 ), 219 ),
  220 + GetPage(
  221 + name: Routes.ADD_SECURITY_EMAIL,
  222 + page: () => const AddSecurityEmailView(),
  223 + binding: BindingsBuilder(() {
  224 + Get.lazyPut<AddSecurityEmailController>(
  225 + () => AddSecurityEmailController(),
  226 + );
  227 + }),
  228 + ),
219 ]; 229 ];
220 } 230 }
@@ -26,6 +26,7 @@ abstract class Routes { @@ -26,6 +26,7 @@ abstract class Routes {
26 static const FRIEND_TREND = _Paths.FRIEND_TREND; 26 static const FRIEND_TREND = _Paths.FRIEND_TREND;
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 } 30 }
30 31
31 abstract class _Paths { 32 abstract class _Paths {
@@ -53,4 +54,5 @@ abstract class _Paths { @@ -53,4 +54,5 @@ abstract class _Paths {
53 static const FRIEND_TREND = '/friend-trend'; 54 static const FRIEND_TREND = '/friend-trend';
54 static const FRIEND_HOME = '/friend-home'; 55 static const FRIEND_HOME = '/friend-home';
55 static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test'; 56 static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test';
  57 + static const ADD_SECURITY_EMAIL = '/add-security-email';
56 } 58 }
@@ -25,6 +25,10 @@ abstract final class AppConst { @@ -25,6 +25,10 @@ abstract final class AppConst {
25 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E7%94%A8%E6%88%B7%E5%8D%8F%E8%AE%AE.html'; 25 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E7%94%A8%E6%88%B7%E5%8D%8F%E8%AE%AE.html';
26 static const String privacyPolicy = 26 static const String privacyPolicy =
27 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E9%9A%90%E7%A7%81%E5%8D%8F%E8%AE%AE.html'; 27 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E9%9A%90%E7%A7%81%E5%8D%8F%E8%AE%AE.html';
  28 + static const String userTermsGlobal =
  29 + 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E7%94%A8%E6%88%B7%E5%8D%8F%E8%AE%AE.html';
  30 + static const String privacyPolicyGlobal =
  31 + 'https://cdn.doublefeel.cn/doublefeel/protocol/DoubleFeel%E9%9A%90%E7%A7%81%E5%8D%8F%E8%AE%AE.html';
28 static const String userSubmissionAgreement = 32 static const String userSubmissionAgreement =
29 'https://cdn.doublefeel.cn/doublefeel/protocol/creator.html'; 33 'https://cdn.doublefeel.cn/doublefeel/protocol/creator.html';
30 } 34 }
@@ -263,12 +263,11 @@ @@ -263,12 +263,11 @@
263 "appleAccount": "Apple Account", 263 "appleAccount": "Apple Account",
264 "googleAccount": "Google Account", 264 "googleAccount": "Google Account",
265 "securityEmail": "Email", 265 "securityEmail": "Email",
266 - "addSecurityEmail": "Add security email",  
267 - "securityEmailDescription": "Use a security email to help protect your account.", 266 + "addSecurityEmail": "Add an email",
  267 + "securityEmailDescription": "Adding an email address makes it easier to recover your account. For your account security, please use an email address you own.",
268 "securityEmailHint": "Email address", 268 "securityEmailHint": "Email address",
269 - "sendVerificationEmail": "Send verification email",  
270 "confirmYourEmail": "Confirm your email", 269 "confirmYourEmail": "Confirm your email",
271 - "enterCodeSentTo": "Enter the code sent to {email}", 270 + "enterCodeSentTo": "Enter the code sent to \n{email}",
272 "@enterCodeSentTo": { 271 "@enterCodeSentTo": {
273 "placeholders": { 272 "placeholders": {
274 "email": {} 273 "email": {}
@@ -824,5 +823,32 @@ @@ -824,5 +823,32 @@
824 "yourAccountWasSignedOutDueToAnotherDeviceLoginOrTokenExpirationPleaseLogInAgainToContinue": "Your account was signed out due to another device login or token expiration. Please log in again to continue.", 823 "yourAccountWasSignedOutDueToAnotherDeviceLoginOrTokenExpirationPleaseLogInAgainToContinue": "Your account was signed out due to another device login or token expiration. Please log in again to continue.",
825 "contactUs": "Contact us", 824 "contactUs": "Contact us",
826 "pleaseDescribeTheProblemClearlyAndIncludeScreenRecordingsIfPossible": "Please describe the problem clearly and include screen recordings if possible.", 825 "pleaseDescribeTheProblemClearlyAndIncludeScreenRecordingsIfPossible": "Please describe the problem clearly and include screen recordings if possible.",
827 - "sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster": "Send us your User ID as it will help us identify the problem faster." 826 + "sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster": "Send us your User ID as it will help us identify the problem faster.",
  827 + "setAPassword": "Set a Password",
  828 + "setAPasswordToSignInWithYourEmail": "Set a password to sign in with your email.",
  829 + "settingsSaved": "Settings saved",
  830 + "leave": "Leave",
  831 + "setPassword": "Set Password",
  832 + "setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup": "Set a password to add this email successfully. Leaving now will cancel this setup.",
  833 + "setupIncomplete": "Setup Incomplete",
  834 + "enterYourPassword": "Enter your password",
  835 + "confirmNewPassword": "Confirm new password",
  836 + "passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter": "Password must be at least 6 characters and include 1 number and 1 uppercase letter.",
  837 + "forgotPassword": "Forgot password?",
  838 + "enterYourEmailAndPassword": "Enter your email and password",
  839 + "newPassword": "New password",
  840 + "weVeSentACodeTo": "We’ve sent a code to",
  841 + "didnTGetItCheckYourSpamFolderOrTryAgain": ". Didn’t get it? Check your spam folder or try again.",
  842 + "checkYourEmail": "Check your email",
  843 + "code": "Code",
  844 + "resetPassword": "Reset password",
  845 + "havingTroubleContactUs": "Having trouble? Contact us",
  846 + "sendEmail": "Send Email",
  847 + "thisEmailIsNotRegisteredPleaseCheckAndTryAgain": "This email is not registered. Please check and try again.",
  848 + "youLlReceiveACodeViaEmailToResetYourPassword": "You'll receive a code via email to reset your password.",
  849 + "codeFromEmail": "Code from email",
  850 + "sendResetEmail": "Send reset email",
  851 + "invalidCode": "Invalid code",
  852 + "paswordHasBeenChanged": "Pasword has been changed",
  853 + "copiedSuccessfully": "Copied successfully"
828 } 854 }
@@ -349,7 +349,6 @@ @@ -349,7 +349,6 @@
349 "addSecurityEmail": "添加安全邮箱", 349 "addSecurityEmail": "添加安全邮箱",
350 "securityEmailDescription": "添加安全邮箱,帮助保护你的账号。", 350 "securityEmailDescription": "添加安全邮箱,帮助保护你的账号。",
351 "securityEmailHint": "请输入邮箱地址", 351 "securityEmailHint": "请输入邮箱地址",
352 - "sendVerificationEmail": "发送验证邮件",  
353 "confirmYourEmail": "验证你的邮箱", 352 "confirmYourEmail": "验证你的邮箱",
354 "enterCodeSentTo": "请输入发送至 {email} 的验证码", 353 "enterCodeSentTo": "请输入发送至 {email} 的验证码",
355 "@enterCodeSentTo": { 354 "@enterCodeSentTo": {
@@ -1217,5 +1216,32 @@ @@ -1217,5 +1216,32 @@
1217 "yourAccountWasSignedOutDueToAnotherDeviceLoginOrTokenExpirationPleaseLogInAgainToContinue": "由于其他设备登录或令牌过期,您的账户已被注销。请重新登录以继续操作。", 1216 "yourAccountWasSignedOutDueToAnotherDeviceLoginOrTokenExpirationPleaseLogInAgainToContinue": "由于其他设备登录或令牌过期,您的账户已被注销。请重新登录以继续操作。",
1218 "contactUs": "联系我们", 1217 "contactUs": "联系我们",
1219 "pleaseDescribeTheProblemClearlyAndIncludeScreenRecordingsIfPossible": "请清楚地描述问题,并尽可能附上屏幕录像。", 1218 "pleaseDescribeTheProblemClearlyAndIncludeScreenRecordingsIfPossible": "请清楚地描述问题,并尽可能附上屏幕录像。",
1220 - "sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster": "请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。" 1219 + "sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster": "请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。",
  1220 + "setAPassword": "设置密码",
  1221 + "setAPasswordToSignInWithYourEmail": "设置密码,以便使用您的电子邮箱登录。",
  1222 + "settingsSaved": "设置已保存",
  1223 + "leave": "离开",
  1224 + "setPassword": "设置密码",
  1225 + "setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup": "请设置密码以成功添加此电子邮箱。如果现在退出,将取消此设置。",
  1226 + "setupIncomplete": "设置未完成",
  1227 + "enterYourPassword": "请输入密码",
  1228 + "confirmNewPassword": "确认新密码",
  1229 + "passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter": "密码必须至少包含 6 个字符,并包含 1 个数字和 1 个大写字母。",
  1230 + "forgotPassword": "忘记密码了吗?",
  1231 + "enterYourEmailAndPassword": "请输入您的电子邮箱和密码",
  1232 + "newPassword": "新密码",
  1233 + "weVeSentACodeTo": "我们已向 ",
  1234 + "didnTGetItCheckYourSpamFolderOrTryAgain": ". 没收到吗?请检查您的垃圾邮件文件夹或重试一下。",
  1235 + "checkYourEmail": "请查看您的电子邮件",
  1236 + "code": "代码",
  1237 + "resetPassword": "重置密码",
  1238 + "havingTroubleContactUs": "遇到问题了吗?请联系我们",
  1239 + "sendEmail": "发送电子邮件",
  1240 + "thisEmailIsNotRegisteredPleaseCheckAndTryAgain": "该邮箱未注册。请检查后重试。",
  1241 + "youLlReceiveACodeViaEmailToResetYourPassword": "您将通过电子邮件收到一个用于重置密码的验证码。",
  1242 + "codeFromEmail": "来自电子邮件的代码",
  1243 + "sendResetEmail": "发送重置邮件",
  1244 + "invalidCode": "无效代码",
  1245 + "paswordHasBeenChanged": "密码已更改",
  1246 + "copiedSuccessfully": "复制成功"
1221 } 1247 }
@@ -1695,12 +1695,6 @@ abstract class AppLocalizations { @@ -1695,12 +1695,6 @@ abstract class AppLocalizations {
1695 /// **'请输入邮箱地址'** 1695 /// **'请输入邮箱地址'**
1696 String get securityEmailHint; 1696 String get securityEmailHint;
1697 1697
1698 - /// No description provided for @sendVerificationEmail.  
1699 - ///  
1700 - /// In zh, this message translates to:  
1701 - /// **'发送验证邮件'**  
1702 - String get sendVerificationEmail;  
1703 -  
1704 /// No description provided for @confirmYourEmail. 1698 /// No description provided for @confirmYourEmail.
1705 /// 1699 ///
1706 /// In zh, this message translates to: 1700 /// In zh, this message translates to:
@@ -4349,6 +4343,170 @@ abstract class AppLocalizations { @@ -4349,6 +4343,170 @@ abstract class AppLocalizations {
4349 /// In zh, this message translates to: 4343 /// In zh, this message translates to:
4350 /// **'请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。'** 4344 /// **'请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。'**
4351 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster; 4345 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster;
  4346 +
  4347 + /// No description provided for @setAPassword.
  4348 + ///
  4349 + /// In zh, this message translates to:
  4350 + /// **'设置密码'**
  4351 + String get setAPassword;
  4352 +
  4353 + /// No description provided for @setAPasswordToSignInWithYourEmail.
  4354 + ///
  4355 + /// In zh, this message translates to:
  4356 + /// **'设置密码,以便使用您的电子邮箱登录。'**
  4357 + String get setAPasswordToSignInWithYourEmail;
  4358 +
  4359 + /// No description provided for @settingsSaved.
  4360 + ///
  4361 + /// In zh, this message translates to:
  4362 + /// **'设置已保存'**
  4363 + String get settingsSaved;
  4364 +
  4365 + /// No description provided for @leave.
  4366 + ///
  4367 + /// In zh, this message translates to:
  4368 + /// **'离开'**
  4369 + String get leave;
  4370 +
  4371 + /// No description provided for @setPassword.
  4372 + ///
  4373 + /// In zh, this message translates to:
  4374 + /// **'设置密码'**
  4375 + String get setPassword;
  4376 +
  4377 + /// No description provided for @setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup.
  4378 + ///
  4379 + /// In zh, this message translates to:
  4380 + /// **'请设置密码以成功添加此电子邮箱。如果现在退出,将取消此设置。'**
  4381 + String
  4382 + get setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup;
  4383 +
  4384 + /// No description provided for @setupIncomplete.
  4385 + ///
  4386 + /// In zh, this message translates to:
  4387 + /// **'设置未完成'**
  4388 + String get setupIncomplete;
  4389 +
  4390 + /// No description provided for @enterYourPassword.
  4391 + ///
  4392 + /// In zh, this message translates to:
  4393 + /// **'请输入密码'**
  4394 + String get enterYourPassword;
  4395 +
  4396 + /// No description provided for @confirmNewPassword.
  4397 + ///
  4398 + /// In zh, this message translates to:
  4399 + /// **'确认新密码'**
  4400 + String get confirmNewPassword;
  4401 +
  4402 + /// No description provided for @passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter.
  4403 + ///
  4404 + /// In zh, this message translates to:
  4405 + /// **'密码必须至少包含 6 个字符,并包含 1 个数字和 1 个大写字母。'**
  4406 + String
  4407 + get passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter;
  4408 +
  4409 + /// No description provided for @forgotPassword.
  4410 + ///
  4411 + /// In zh, this message translates to:
  4412 + /// **'忘记密码了吗?'**
  4413 + String get forgotPassword;
  4414 +
  4415 + /// No description provided for @enterYourEmailAndPassword.
  4416 + ///
  4417 + /// In zh, this message translates to:
  4418 + /// **'请输入您的电子邮箱和密码'**
  4419 + String get enterYourEmailAndPassword;
  4420 +
  4421 + /// No description provided for @newPassword.
  4422 + ///
  4423 + /// In zh, this message translates to:
  4424 + /// **'新密码'**
  4425 + String get newPassword;
  4426 +
  4427 + /// No description provided for @weVeSentACodeTo.
  4428 + ///
  4429 + /// In zh, this message translates to:
  4430 + /// **'我们已向 '**
  4431 + String get weVeSentACodeTo;
  4432 +
  4433 + /// No description provided for @didnTGetItCheckYourSpamFolderOrTryAgain.
  4434 + ///
  4435 + /// In zh, this message translates to:
  4436 + /// **'. 没收到吗?请检查您的垃圾邮件文件夹或重试一下。'**
  4437 + String get didnTGetItCheckYourSpamFolderOrTryAgain;
  4438 +
  4439 + /// No description provided for @checkYourEmail.
  4440 + ///
  4441 + /// In zh, this message translates to:
  4442 + /// **'请查看您的电子邮件'**
  4443 + String get checkYourEmail;
  4444 +
  4445 + /// No description provided for @code.
  4446 + ///
  4447 + /// In zh, this message translates to:
  4448 + /// **'代码'**
  4449 + String get code;
  4450 +
  4451 + /// No description provided for @resetPassword.
  4452 + ///
  4453 + /// In zh, this message translates to:
  4454 + /// **'重置密码'**
  4455 + String get resetPassword;
  4456 +
  4457 + /// No description provided for @havingTroubleContactUs.
  4458 + ///
  4459 + /// In zh, this message translates to:
  4460 + /// **'遇到问题了吗?请联系我们'**
  4461 + String get havingTroubleContactUs;
  4462 +
  4463 + /// No description provided for @sendEmail.
  4464 + ///
  4465 + /// In zh, this message translates to:
  4466 + /// **'发送电子邮件'**
  4467 + String get sendEmail;
  4468 +
  4469 + /// No description provided for @thisEmailIsNotRegisteredPleaseCheckAndTryAgain.
  4470 + ///
  4471 + /// In zh, this message translates to:
  4472 + /// **'该邮箱未注册。请检查后重试。'**
  4473 + String get thisEmailIsNotRegisteredPleaseCheckAndTryAgain;
  4474 +
  4475 + /// No description provided for @youLlReceiveACodeViaEmailToResetYourPassword.
  4476 + ///
  4477 + /// In zh, this message translates to:
  4478 + /// **'您将通过电子邮件收到一个用于重置密码的验证码。'**
  4479 + String get youLlReceiveACodeViaEmailToResetYourPassword;
  4480 +
  4481 + /// No description provided for @codeFromEmail.
  4482 + ///
  4483 + /// In zh, this message translates to:
  4484 + /// **'来自电子邮件的代码'**
  4485 + String get codeFromEmail;
  4486 +
  4487 + /// No description provided for @sendResetEmail.
  4488 + ///
  4489 + /// In zh, this message translates to:
  4490 + /// **'发送重置邮件'**
  4491 + String get sendResetEmail;
  4492 +
  4493 + /// No description provided for @invalidCode.
  4494 + ///
  4495 + /// In zh, this message translates to:
  4496 + /// **'无效代码'**
  4497 + String get invalidCode;
  4498 +
  4499 + /// No description provided for @paswordHasBeenChanged.
  4500 + ///
  4501 + /// In zh, this message translates to:
  4502 + /// **'密码已更改'**
  4503 + String get paswordHasBeenChanged;
  4504 +
  4505 + /// No description provided for @copiedSuccessfully.
  4506 + ///
  4507 + /// In zh, this message translates to:
  4508 + /// **'复制成功'**
  4509 + String get copiedSuccessfully;
4352 } 4510 }
4353 4511
4354 class _AppLocalizationsDelegate 4512 class _AppLocalizationsDelegate
@@ -911,24 +911,21 @@ class AppLocalizationsEn extends AppLocalizations { @@ -911,24 +911,21 @@ class AppLocalizationsEn extends AppLocalizations {
911 String get securityEmail => 'Email'; 911 String get securityEmail => 'Email';
912 912
913 @override 913 @override
914 - String get addSecurityEmail => 'Add security email'; 914 + String get addSecurityEmail => 'Add an email';
915 915
916 @override 916 @override
917 String get securityEmailDescription => 917 String get securityEmailDescription =>
918 - 'Use a security email to help protect your account.'; 918 + 'Adding an email address makes it easier to recover your account. For your account security, please use an email address you own.';
919 919
920 @override 920 @override
921 String get securityEmailHint => 'Email address'; 921 String get securityEmailHint => 'Email address';
922 922
923 @override 923 @override
924 - String get sendVerificationEmail => 'Send verification email';  
925 -  
926 - @override  
927 String get confirmYourEmail => 'Confirm your email'; 924 String get confirmYourEmail => 'Confirm your email';
928 925
929 @override 926 @override
930 String enterCodeSentTo(Object email) { 927 String enterCodeSentTo(Object email) {
931 - return 'Enter the code sent to $email'; 928 + return 'Enter the code sent to \n$email';
932 } 929 }
933 930
934 @override 931 @override
@@ -2443,4 +2440,91 @@ class AppLocalizationsEn extends AppLocalizations { @@ -2443,4 +2440,91 @@ class AppLocalizationsEn extends AppLocalizations {
2443 @override 2440 @override
2444 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster => 2441 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster =>
2445 'Send us your User ID as it will help us identify the problem faster.'; 2442 'Send us your User ID as it will help us identify the problem faster.';
  2443 +
  2444 + @override
  2445 + String get setAPassword => 'Set a Password';
  2446 +
  2447 + @override
  2448 + String get setAPasswordToSignInWithYourEmail =>
  2449 + 'Set a password to sign in with your email.';
  2450 +
  2451 + @override
  2452 + String get settingsSaved => 'Settings saved';
  2453 +
  2454 + @override
  2455 + String get leave => 'Leave';
  2456 +
  2457 + @override
  2458 + String get setPassword => 'Set Password';
  2459 +
  2460 + @override
  2461 + String get setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup =>
  2462 + 'Set a password to add this email successfully. Leaving now will cancel this setup.';
  2463 +
  2464 + @override
  2465 + String get setupIncomplete => 'Setup Incomplete';
  2466 +
  2467 + @override
  2468 + String get enterYourPassword => 'Enter your password';
  2469 +
  2470 + @override
  2471 + String get confirmNewPassword => 'Confirm new password';
  2472 +
  2473 + @override
  2474 + String get passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter =>
  2475 + 'Password must be at least 6 characters and include 1 number and 1 uppercase letter.';
  2476 +
  2477 + @override
  2478 + String get forgotPassword => 'Forgot password?';
  2479 +
  2480 + @override
  2481 + String get enterYourEmailAndPassword => 'Enter your email and password';
  2482 +
  2483 + @override
  2484 + String get newPassword => 'New password';
  2485 +
  2486 + @override
  2487 + String get weVeSentACodeTo => 'We’ve sent a code to';
  2488 +
  2489 + @override
  2490 + String get didnTGetItCheckYourSpamFolderOrTryAgain =>
  2491 + '. Didn’t get it? Check your spam folder or try again.';
  2492 +
  2493 + @override
  2494 + String get checkYourEmail => 'Check your email';
  2495 +
  2496 + @override
  2497 + String get code => 'Code';
  2498 +
  2499 + @override
  2500 + String get resetPassword => 'Reset password';
  2501 +
  2502 + @override
  2503 + String get havingTroubleContactUs => 'Having trouble? Contact us';
  2504 +
  2505 + @override
  2506 + String get sendEmail => 'Send Email';
  2507 +
  2508 + @override
  2509 + String get thisEmailIsNotRegisteredPleaseCheckAndTryAgain =>
  2510 + 'This email is not registered. Please check and try again.';
  2511 +
  2512 + @override
  2513 + String get youLlReceiveACodeViaEmailToResetYourPassword =>
  2514 + 'You\'ll receive a code via email to reset your password.';
  2515 +
  2516 + @override
  2517 + String get codeFromEmail => 'Code from email';
  2518 +
  2519 + @override
  2520 + String get sendResetEmail => 'Send reset email';
  2521 +
  2522 + @override
  2523 + String get invalidCode => 'Invalid code';
  2524 +
  2525 + @override
  2526 + String get paswordHasBeenChanged => 'Pasword has been changed';
  2527 +
  2528 + @override
  2529 + String get copiedSuccessfully => 'Copied successfully';
2446 } 2530 }
@@ -870,9 +870,6 @@ class AppLocalizationsZh extends AppLocalizations { @@ -870,9 +870,6 @@ class AppLocalizationsZh extends AppLocalizations {
870 String get securityEmailHint => '请输入邮箱地址'; 870 String get securityEmailHint => '请输入邮箱地址';
871 871
872 @override 872 @override
873 - String get sendVerificationEmail => '发送验证邮件';  
874 -  
875 - @override  
876 String get confirmYourEmail => '验证你的邮箱'; 873 String get confirmYourEmail => '验证你的邮箱';
877 874
878 @override 875 @override
@@ -2344,4 +2341,91 @@ class AppLocalizationsZh extends AppLocalizations { @@ -2344,4 +2341,91 @@ class AppLocalizationsZh extends AppLocalizations {
2344 @override 2341 @override
2345 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster => 2342 String get sendUsYourUserIdAsItWillHelpUsIdentifyTheProblemFaster =>
2346 '请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。'; 2343 '请将您的用户 ID 发送给我们,这将有助于我们更快地查明问题所在。';
  2344 +
  2345 + @override
  2346 + String get setAPassword => '设置密码';
  2347 +
  2348 + @override
  2349 + String get setAPasswordToSignInWithYourEmail => '设置密码,以便使用您的电子邮箱登录。';
  2350 +
  2351 + @override
  2352 + String get settingsSaved => '设置已保存';
  2353 +
  2354 + @override
  2355 + String get leave => '离开';
  2356 +
  2357 + @override
  2358 + String get setPassword => '设置密码';
  2359 +
  2360 + @override
  2361 + String
  2362 + get setAPasswordToAddThisEmailSuccessfullyLeavingNowWillCancelThisSetup =>
  2363 + '请设置密码以成功添加此电子邮箱。如果现在退出,将取消此设置。';
  2364 +
  2365 + @override
  2366 + String get setupIncomplete => '设置未完成';
  2367 +
  2368 + @override
  2369 + String get enterYourPassword => '请输入密码';
  2370 +
  2371 + @override
  2372 + String get confirmNewPassword => '确认新密码';
  2373 +
  2374 + @override
  2375 + String
  2376 + get passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter =>
  2377 + '密码必须至少包含 6 个字符,并包含 1 个数字和 1 个大写字母。';
  2378 +
  2379 + @override
  2380 + String get forgotPassword => '忘记密码了吗?';
  2381 +
  2382 + @override
  2383 + String get enterYourEmailAndPassword => '请输入您的电子邮箱和密码';
  2384 +
  2385 + @override
  2386 + String get newPassword => '新密码';
  2387 +
  2388 + @override
  2389 + String get weVeSentACodeTo => '我们已向 ';
  2390 +
  2391 + @override
  2392 + String get didnTGetItCheckYourSpamFolderOrTryAgain =>
  2393 + '. 没收到吗?请检查您的垃圾邮件文件夹或重试一下。';
  2394 +
  2395 + @override
  2396 + String get checkYourEmail => '请查看您的电子邮件';
  2397 +
  2398 + @override
  2399 + String get code => '代码';
  2400 +
  2401 + @override
  2402 + String get resetPassword => '重置密码';
  2403 +
  2404 + @override
  2405 + String get havingTroubleContactUs => '遇到问题了吗?请联系我们';
  2406 +
  2407 + @override
  2408 + String get sendEmail => '发送电子邮件';
  2409 +
  2410 + @override
  2411 + String get thisEmailIsNotRegisteredPleaseCheckAndTryAgain => '该邮箱未注册。请检查后重试。';
  2412 +
  2413 + @override
  2414 + String get youLlReceiveACodeViaEmailToResetYourPassword =>
  2415 + '您将通过电子邮件收到一个用于重置密码的验证码。';
  2416 +
  2417 + @override
  2418 + String get codeFromEmail => '来自电子邮件的代码';
  2419 +
  2420 + @override
  2421 + String get sendResetEmail => '发送重置邮件';
  2422 +
  2423 + @override
  2424 + String get invalidCode => '无效代码';
  2425 +
  2426 + @override
  2427 + String get paswordHasBeenChanged => '密码已更改';
  2428 +
  2429 + @override
  2430 + String get copiedSuccessfully => '复制成功';
2347 } 2431 }