Commit 4ec8a24339594f7fdd658285a34b43856ecd7aa1

Authored by 常守达
1 parent 2aff1294

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

... ... @@ -69,7 +69,11 @@ class AccountSettingView extends GetView<MyController> {
_SecurityEmailRow(
email: securityEmail,
onTap: () async {
await Get.toNamed(Routes.ADD_SECURITY_EMAIL);
if (securityEmail.isEmpty) {
await Get.toNamed(Routes.ADD_SECURITY_EMAIL);
} else {
await Get.toNamed(Routes.CHANGE_SECURITY_EMAIL);
}
controller.refreshEmail();
},
),
... ...
import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:doublefeel_flutter/app/modules/home/widgets/my/security_email_views.dart';
import 'package:doublefeel_flutter/core/network/api/user_api.dart';
import 'package:doublefeel_flutter/core/result/app_result.dart';
import 'package:doublefeel_flutter/core/services/loading_service.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/data/local/user_preferences_storage.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
class AddSecurityEmailController extends GetxController {
final UserApi _userApi = Get.find<UserApi>();
final userPrefs = Get.find<UserPreferencesStorage>();
static final _emailPattern = RegExp(
r'^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$',
);
... ... @@ -85,12 +93,19 @@ class AddSecurityEmailController extends GetxController {
isConfirmPasswordObscured.value = !isConfirmPasswordObscured.value;
}
void sendVerificationEmail() {
Future<void> sendVerificationEmail({bool isChangeEmail = false}) async {
hasSubmittedEmail.value = true;
if (!isEmailValid) return;
startResendCountdown();
Get.to(() => const SecurityEmailVerificationView());
try {
await LoadingService.instance.run(() async {
final result = await _userApi.requestEmailVerifyCode(emailInput.value);
// if (result is AppSuccess<void>) {
startResendCountdown();
Get.to(
() => SecurityEmailVerificationView(isChangeEmail: isChangeEmail));
// }
});
} catch (_) {}
}
void startResendCountdown() {
... ... @@ -106,20 +121,71 @@ class AddSecurityEmailController extends GetxController {
});
}
void submitCode(BuildContext context) {
Future<void> submitCode(BuildContext context,
{bool isChangeEmail = false}) async {
if (!canSubmitCode) return;
FocusScope.of(context).unfocus();
Get.to(() => const SetSecurityEmailPasswordView());
if (isChangeEmail) {
try {
await LoadingService.instance.run(() async {
final result = await _userApi.verifyEmailVerifyCode(
emailInput.value,
codeController.text,
);
if (result is AppSuccess<void>) {
AppToast.show(context.l10n.successChanged);
Get.back();
Get.back();
Get.back();
} else {
AppToast.show(context.l10n.verifyEmailFailed);
Get.back();
Get.back();
Get.back();
}
});
} catch (_) {}
} else {
try {
await LoadingService.instance.run(() async {
final result = await _userApi.verifyEmailVerifyCode(
emailInput.value,
codeController.text,
);
// if (result is AppSuccess<void>) {
Get.to(() => const SetSecurityEmailPasswordView());
// }
});
} catch (_) {}
}
}
void setPassword(BuildContext context) {
Future<void> setPassword(BuildContext context) async {
if (!canSetPassword) return;
FocusScope.of(context).unfocus();
AppToast.show(context.l10n.settingsSaved);
TextInput.finishAutofillContext();
Get.back();
Get.back();
Get.back();
try {
final md5Password =
md5.convert(utf8.encode(passwordInput.value)).toString();
await LoadingService.instance.run(() async {
final result = await _userApi.addSefetyEmail(
emailInput.value,
codeController.text,
md5Password,
);
// if (result is AppSuccess<void>) {
FocusScope.of(context).unfocus();
AppToast.show(context.l10n.settingsSaved);
TextInput.finishAutofillContext();
Get.back();
Get.back();
Get.back();
// }
});
} catch (_) {}
}
@override
... ...
... ... @@ -9,7 +9,9 @@ import 'package:flutter/services.dart';
import 'package:get/get.dart';
class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
const AddSecurityEmailView({super.key});
const AddSecurityEmailView({super.key, this.isChangeEmail = false});
final bool isChangeEmail;
@override
Widget build(BuildContext context) {
... ... @@ -38,7 +40,7 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
children: [
const SizedBox(height: 16),
Text(
l10n.addSecurityEmail,
isChangeEmail ? l10n.changeEmail : l10n.addSecurityEmail,
style: TextStyle(
color: colors.textPrimary,
fontSize: 24,
... ... @@ -47,22 +49,59 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
),
),
const SizedBox(height: 8),
Text(
l10n.securityEmailDescription,
textAlign: TextAlign.center,
style: TextStyle(
color: colors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.43,
if (isChangeEmail)
Text.rich(
TextSpan(
children: [
TextSpan(
text: l10n.yourCurrentEmailIs,
style: TextStyle(
color: colors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
TextSpan(
text: controller.userPrefs.preferences.value
.meUserInfo?.securityEmail
?.trim() ??
'',
style: TextStyle(
color: const Color(0xFF0F0F11),
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
TextSpan(
text: l10n.whatWouldYouLikeToUpdateItTo,
style: TextStyle(
color: colors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w400,
),
),
],
),
textAlign: TextAlign.center,
)
else
Text(
l10n.securityEmailDescription,
textAlign: TextAlign.center,
style: TextStyle(
color: colors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.43,
),
),
),
const SizedBox(height: 32),
TextField(
controller: c.emailController,
keyboardType: TextInputType.emailAddress,
autofillHints: const [AutofillHints.email],
onSubmitted: (_) => c.sendVerificationEmail(),
onSubmitted: (_) =>
c.sendVerificationEmail(isChangeEmail: isChangeEmail),
style: TextStyle(
color: colors.textPrimary,
fontSize: 16,
... ... @@ -120,8 +159,10 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
height: 48,
child: Obx(
() => ElevatedButton(
onPressed:
c.isEmailValid ? c.sendVerificationEmail : null,
onPressed: c.isEmailValid
? () => c.sendVerificationEmail(
isChangeEmail: isChangeEmail)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: colors.primary,
disabledBackgroundColor:
... ... @@ -150,7 +191,9 @@ class AddSecurityEmailView extends GetView<AddSecurityEmailController> {
class SecurityEmailVerificationView
extends GetView<AddSecurityEmailController> {
const SecurityEmailVerificationView({super.key});
const SecurityEmailVerificationView({super.key, this.isChangeEmail = false});
final bool isChangeEmail;
@override
Widget build(BuildContext context) {
... ... @@ -211,7 +254,8 @@ class SecurityEmailVerificationView
// inputFormatters: const [
// _DigitsOnlyFormatter(),
// ],
onSubmitted: (_) => controller.submitCode(context),
onSubmitted: (_) => controller.submitCode(context,
isChangeEmail: isChangeEmail),
style: TextStyle(
color: colors.textPrimary,
fontSize: 16,
... ... @@ -283,7 +327,8 @@ class SecurityEmailVerificationView
child: Obx(
() => ElevatedButton(
onPressed: controller.canSubmitCode
? () => controller.submitCode(context)
? () => controller.submitCode(context,
isChangeEmail: isChangeEmail)
: null,
style: ElevatedButton.styleFrom(
backgroundColor: colors.primary,
... ... @@ -715,3 +760,185 @@ class SetSecurityEmailPasswordView extends GetView<AddSecurityEmailController> {
);
}
}
class VerifyEmailPwdView extends GetView<AddSecurityEmailController> {
const VerifyEmailPwdView({super.key});
@override
Widget build(BuildContext context) {
final c = Get.isRegistered<AddSecurityEmailController>()
? controller
: Get.put(AddSecurityEmailController());
final l10n = context.l10n;
final colors = context.colors;
return AnnotatedRegion<SystemUiOverlayStyle>(
value: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: Brightness.dark,
statusBarBrightness: Brightness.light,
systemNavigationBarColor: colors.backgroundPage,
systemNavigationBarIconBrightness: Brightness.dark,
),
child: Scaffold(
backgroundColor: colors.backgroundPage,
appBar: const _SecurityEmailAppBar(),
body: SafeArea(
top: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 28),
child: Column(
children: [
const SizedBox(height: 16),
Text(
context.l10n.verifyYourPassword,
style: TextStyle(
color: colors.textPrimary,
fontSize: 24,
fontWeight: FontWeight.w600,
height: 1.25,
),
),
const SizedBox(height: 8),
Text(
context.l10n.reEnterYourDoublefeelPasswordToContinue,
textAlign: TextAlign.center,
style: TextStyle(
color: colors.textSecondary,
fontSize: 14,
fontWeight: FontWeight.w400,
height: 1.43,
),
),
const SizedBox(height: 32),
Obx(() {
final isObscured = c.isPasswordObscured.value;
return TextField(
textInputAction: TextInputAction.next,
controller: c.passwordController,
// keyboardType: TextInputType.visiblePassword,
obscureText: isObscured,
autofillHints: const [AutofillHints.password],
obscuringCharacter: '*',
contextMenuBuilder: (BuildContext context,
EditableTextState editableTextState) {
List<ContextMenuButtonItem> buttonItems =
editableTextState.contextMenuButtonItems;
buttonItems.removeWhere(
(item) => item.type != ContextMenuButtonType.paste);
return AdaptiveTextSelectionToolbar.buttonItems(
anchors: editableTextState.contextMenuAnchors,
buttonItems: buttonItems,
);
},
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp(r'\s')),
FilteringTextInputFormatter.allow(
RegExp(r'[\x21-\x7E]'),
),
],
style: TextStyle(
color: context.colors.textPrimary,
fontSize: 16,
fontWeight: FontWeight.w400,
),
decoration: InputDecoration(
hintText: l10n.emailLoginYourPassword,
hintStyle: TextStyle(
color: context.colors.textTertiary,
fontSize: 16,
),
counterText: '',
filled: true,
fillColor: Colors.white,
contentPadding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 16,
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide.none,
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(27),
borderSide: BorderSide(
color: context.colors.primary,
width: 1,
),
),
suffixIconConstraints: const BoxConstraints(
minWidth: 24,
minHeight: 24,
),
suffixIcon: GestureDetector(
onTap: c.togglePasswordVisibility,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Image.asset(
isObscured
? 'assets/images/common/ic_hide_password.png'
: 'assets/images/common/ic_show_password.webp',
color: context.colors.textTertiary,
width: 24,
height: 24,
),
),
),
),
onSubmitted: (_) =>
Get.to(() => AddSecurityEmailView(isChangeEmail: true)),
);
}),
const SizedBox(height: 8),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
textAlign: TextAlign.center,
context.l10n
.passwordMustBeAtLeast6CharactersAndInclude1NumberAnd1UppercaseLetter,
style: TextStyle(
color: context.colors.textTertiary,
fontSize: 12,
fontWeight: FontWeight.w400,
),
),
),
const SizedBox(height: 86),
SizedBox(
width: double.infinity,
height: 48,
child: Obx(
() => ElevatedButton(
onPressed: c.isPasswordValid
? () => Get.to(
() => AddSecurityEmailView(isChangeEmail: true))
: null,
style: ElevatedButton.styleFrom(
backgroundColor: colors.primary,
disabledBackgroundColor:
colors.primary.withValues(alpha: 0.4),
foregroundColor: Colors.white,
disabledForegroundColor: Colors.white,
elevation: 0,
shape: const StadiumBorder(),
textStyle: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
),
),
child: Text(l10n.watchThemeNext),
),
),
),
],
),
),
),
),
);
}
}
... ...
import 'dart:async';
import 'dart:convert';
import 'package:crypto/crypto.dart';
import 'package:doublefeel_flutter/app/modules/login/views/email_login_new_password_view.dart';
import 'package:doublefeel_flutter/app/modules/login/views/email_login_view.dart';
import 'package:doublefeel_flutter/core/config/app_environment_config.dart';
... ... @@ -86,6 +87,7 @@ class LoginController extends GetxController {
bool get canEmailLogin => isEmailValid && isPasswordValid;
AppleSignInModel? appleSignInModel;
GoogleSignInModel? googleSignInModel;
bool _isToggling = false;
... ... @@ -169,19 +171,19 @@ class LoginController extends GetxController {
void onGoogleLoginPressed() async {
try {
final result = await LoadingService.instance.run(() async {
// return await PlatformHostApi().requestGoogleSignIn();
return await PlatformHostApi().requestGoogleSignIn();
}, type: LoadingType.circular);
// result 为 null 或 identityToken 为空,说明用户取消或苹果授权失败
if (result == null ||
result.identityToken?.isNotEmpty != true ||
result.userId.isNotEmpty != true) {
appleSignInModel = null;
result.idToken.isNotEmpty != true ||
result.clientID?.isNotEmpty != true) {
googleSignInModel = null;
return;
}
appleSignInModel = result;
await _loginWithApple(result);
googleSignInModel = result;
await _loginWithGoogle(result);
} catch (e) {
appleSignInModel = null;
googleSignInModel = null;
AppToast.show(e.toString());
}
}
... ... @@ -335,6 +337,7 @@ class LoginController extends GetxController {
if (loginRes is AppSuccess<LoginResponse>) {
await _handleLoginSuccess(loginRes.data);
await Get.find<LocalStorage>().setLastLoginMethod('phone');
await environmentConfig.updateHideChooseRegion(true);
}
});
} finally {
... ... @@ -355,22 +358,23 @@ class LoginController extends GetxController {
return;
}
// TODO: 邮箱登录接口待接入,API 路径由后端确认后在此调用
// isLoggingIn.value = true;
// try {
// await LoadingService.instance.run(() async {
// final loginRes = await _userApi.loginWithEmail(
// email: emailInput.value.trim(),
// password: passwordInput.value,
// );
// if (loginRes is AppSuccess<LoginResponse>) {
// await _handleLoginSuccess(loginRes.data);
// await Get.find<LocalStorage>().setLastLoginMethod('email');
// }
// });
// } finally {
// isLoggingIn.value = false;
// }
isLoggingIn.value = true;
try {
final md5Password =
md5.convert(utf8.encode(passwordInput.value)).toString();
await LoadingService.instance.run(() async {
final loginRes = await _userApi.loginWithEmail(
email: emailInput.value.trim(),
password: md5Password,
);
if (loginRes is AppSuccess<LoginResponse>) {
await _handleLoginSuccess(loginRes.data, emailPassword: md5Password);
await Get.find<LocalStorage>().setLastLoginMethod('email');
}
});
} finally {
isLoggingIn.value = false;
}
var success = true;
if (success) {
TextInput.finishAutofillContext();
... ... @@ -452,25 +456,62 @@ class LoginController extends GetxController {
}
}
Future<void> _loginWithGoogle(GoogleSignInModel googleModel) async {
isLoggingIn.value = true;
try {
await LoadingService.instance.run(() async {
final loginRes = await _userApi.loginWithGoogle(googleModel);
if (loginRes is AppSuccess<LoginResponse>) {
await _handleLoginSuccess(loginRes.data, googleModel: googleModel);
await Get.find<LocalStorage>().setLastLoginMethod('google');
await environmentConfig.updateHideChooseRegion(true);
}
});
} finally {
isLoggingIn.value = false;
}
}
/// 登录/注册成功后的公共处理逻辑(手机号登录与苹果登录共用)
Future<void> _handleLoginSuccess(LoginResponse loginData,
{AppleSignInModel? appleModel}) async {
{AppleSignInModel? appleModel,
GoogleSignInModel? googleModel,
String? emailPassword}) async {
bool isApple = appleModel != null;
bool isGoogle = googleModel != null;
bool isEmail = emailPassword != null;
final isRegister = loginData.isNewUser == true;
String accessToken = loginData.accessToken;
if (isRegister) {
final regRes = await _userApi.register(isApple
? {
'login_type': 'apple',
'apple_user_id': appleModel.userId,
'identity_token': appleModel.identityToken,
}
: {
'login_type': 'verification_code',
'telephone': cleanPhone,
'verification_code': codeInput.value,
});
Object? registerData;
if (isApple) {
registerData = {
'login_type': 'apple',
'apple_user_id': appleModel.userId,
'identity_token': appleModel.identityToken,
};
} else if (isGoogle) {
registerData = {
'login_type': 'google',
'identity_token': googleModel.idToken,
'expected_client_id': googleModel.clientID,
};
} else if (isEmail) {
registerData = {
'login_type': 'email',
'email': emailInput.value.trim(),
'password': emailPassword,
};
} else {
registerData = {
'login_type': 'verification_code',
'telephone': cleanPhone,
'verification_code': codeInput.value,
};
}
final regRes = await _userApi.register(registerData);
if (regRes is AppSuccess<RegisterResponse>) {
accessToken = regRes.data.accessToken;
} else {
... ...
import 'package:doublefeel_flutter/core/theme/app_theme.dart';
import 'package:doublefeel_flutter/core/util/app_toast.dart';
import 'package:doublefeel_flutter/l10n/l10n_extensions.dart';
import 'package:doublefeel_flutter/pigeon/platform_api.g.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
... ... @@ -127,28 +128,10 @@ Future<void> sendFeedbackEmail({
final String body =
'This issue was reported from $pageName by User ID: $cleanUserId.';
final Uri emailUri = Uri(
scheme: 'mailto',
path: 'support@doublefeel.cn',
query: _encodeQueryParameters({
'subject': 'DoubleFeel Feedback',
'body': body,
}),
);
// try {
// await launchUrl(
// emailUri,
// mode: LaunchMode.externalApplication,
// );
// } catch (_) {}
}
String _encodeQueryParameters(Map<String, String> params) {
return params.entries
.map((e) =>
'${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}')
.join('&');
try {
await PlatformHostApi()
.sendEmail('support@doublefeel.cn', 'DoubleFeel Feedback', body);
} catch (_) {}
}
class _UserIdField extends StatelessWidget {
... ...
... ... @@ -226,5 +226,14 @@ abstract final class AppPages {
);
}),
),
GetPage(
name: Routes.CHANGE_SECURITY_EMAIL,
page: () => const VerifyEmailPwdView(),
binding: BindingsBuilder(() {
Get.lazyPut<AddSecurityEmailController>(
() => AddSecurityEmailController(),
);
}),
),
];
}
... ...
... ... @@ -27,6 +27,7 @@ abstract class Routes {
static const FRIEND_HOME = _Paths.FRIEND_HOME;
static const APPLE_HEALTH_UPLOAD_TEST = _Paths.APPLE_HEALTH_UPLOAD_TEST;
static const ADD_SECURITY_EMAIL = _Paths.ADD_SECURITY_EMAIL;
static const CHANGE_SECURITY_EMAIL = _Paths.CHANGE_SECURITY_EMAIL;
}
abstract class _Paths {
... ... @@ -55,4 +56,5 @@ abstract class _Paths {
static const FRIEND_HOME = '/friend-home';
static const APPLE_HEALTH_UPLOAD_TEST = '/apple-health-upload-test';
static const ADD_SECURITY_EMAIL = '/add-security-email';
static const CHANGE_SECURITY_EMAIL = '/change-security-email';
}
... ...
... ... @@ -45,6 +45,26 @@ class UserApi {
);
}
Future<AppResult<LoginResponse>> loginWithEmail({
required String email,
required String password,
}) {
return safeCall(
call: () async {
final response = await _dioClient.dio.post(
ApiPaths.userLogin,
data: {
'login_type': 'email',
'email': email,
'password': password,
},
options: _dioClient.noTokenOptions(),
);
return LoginResponse.fromJson(response.data as Map<String, dynamic>);
},
);
}
Future<AppResult<LoginResponse>> loginWithApple(AppleSignInModel appleModel) {
return safeCall(
call: () async {
... ... @@ -67,6 +87,30 @@ class UserApi {
);
}
Future<AppResult<LoginResponse>> loginWithGoogle(
GoogleSignInModel googleModel) {
return safeCall(
call: () async {
var googleLoginRequest = GoogleLoginRequest(
identityToken: googleModel.idToken,
userId: googleModel.clientID ?? '');
if (googleModel.email != null) {
googleLoginRequest.email = googleModel.email;
}
if (googleModel.nickname != null) {
googleLoginRequest.nickname = googleModel.nickname;
}
final response = await _dioClient.dio.post(
ApiPaths.userLogin,
data: googleLoginRequest.toJson(),
options: _dioClient.noTokenOptions(),
);
return LoginResponse.fromJson(response.data as Map<String, dynamic>);
},
);
}
Future<AppResult<RegisterResponse>> register(Object? registerData) {
return safeCall(
call: () async {
... ... @@ -238,4 +282,46 @@ class UserApi {
},
);
}
Future<AppResult<void>> requestEmailVerifyCode(String email) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.emailSendCode,
data: {
'safety_email': email,
},
);
},
);
}
Future<AppResult<void>> verifyEmailVerifyCode(String email, String code) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.emailVerifyCode,
data: {
'safety_email': email,
'safety_email_code': code,
},
);
},
);
}
Future<AppResult<void>> addSefetyEmail(
String email, String code, String pwd) {
return safeCall(
call: () async {
await _dioClient.dio.post(
ApiPaths.addSafetyEmail,
data: {
'safety_email': email,
'safety_email_code': code,
},
);
},
);
}
}
... ...
... ... @@ -4,6 +4,11 @@ abstract final class ApiPaths {
static const userRegister = '/client/doublefeel/user/register/';
static const userLogin = '/client/doublefeel/user/login/';
static const smsSendCode = '/client/doublefeel/sms/send_code/';
static const emailSendCode =
'/client/doublefeel/safety_email/send_verify_code/';
static const emailVerifyCode = '/client/doublefeel/safety_email/verify_code/';
static const addSafetyEmail =
'/client/doublefeel/safety_email/add_safety_email/';
static const userPair = '/client/doublefeel/user/pair/';
static const userLogout = '/client/doublefeel/user/logout/';
static const userDeleteAccount = '/client/doublefeel/user/account/delete/';
... ...
... ... @@ -42,6 +42,30 @@ class AppleLoginRequest {
};
}
class GoogleLoginRequest {
GoogleLoginRequest({
this.loginType = 'google',
required this.identityToken,
required this.userId,
this.email,
this.nickname,
});
final String loginType;
final String identityToken;
final String userId;
String? email;
String? nickname;
Map<String, dynamic> toJson() => {
'login_type': loginType,
'expected_client_id': userId,
'identity_token': identityToken,
if (email != null) 'email': email,
if (nickname != null) 'nickname': nickname,
};
}
class VerifyCodeRequest {
const VerifyCodeRequest({required this.telephone, required this.scene});
... ...
... ... @@ -353,9 +353,9 @@
"sleepQualityGreat": "Great sleep",
"sleepQualityGood": "Good sleep",
"sleepQualityPoor": "Poor sleep",
"sleepQualityExcellent": "Excellent",
"sleepQualityNormal": "Normal",
"sleepQualityAttention": "Needs attention",
"sleepQualityExcellent": "Great",
"sleepQualityNormal": "Good",
"sleepQualityAttention": "Poor",
"hrvDailyStressTrend": "Daily Stress Trend",
"hrvMonthlyStressTrend": "Monthly Stress Trend",
"hrvMoreRelaxed": "More relaxed",
... ... @@ -460,14 +460,14 @@
"friendsWaitingForData": "Waiting for data",
"friendsSleepQuality": "Sleep quality",
"friendsTodaySteps": "Steps today",
"friendsSleepQualityExcellent": "Slept great",
"friendsSleepQualityNormal": "Slept well",
"friendsSleepQualityAttention": "Slept poorly",
"friendsSleepQualityExcellent": "Great Sleep",
"friendsSleepQualityNormal": "Good Sleep",
"friendsSleepQualityAttention": "Poor Sleep",
"friendsRemove": "Remove",
"friendsEditRemark": "Edit Note",
"friendsShowOnWatchFace": "Show on Watch",
"friendsShownOnWatchFace": "Shown on Watch",
"friendsSelect": "Select a friend",
"friendsSelect": "Choose a Loved One",
"friendsSelectAndSync": "Select and sync to Watch",
"friendsBack": "Back",
"friendsTrendTitle": "{name}'s Trends",
... ... @@ -577,7 +577,7 @@
"membersCanViewTheCompleteData": "Unlock Pro to view",
"unlockNow": "Unlock",
"pressureOverload": "Overload",
"beMindfulOfStress": "Stressful",
"beMindfulOfStress": "Pay Attention",
"statusNormal": "Normal",
"inExcellentCondition": "Excellent",
"waitingForData": "Waiting for data",
... ... @@ -868,5 +868,12 @@
"monthlyMembership": "Monthly",
"quarterlyMembership": "Quarterly",
"annualMembership": "Annual",
"lifetimeMembership": "Lifetime"
"lifetimeMembership": "Lifetime",
"verifyYourPassword": "Verify your password",
"reEnterYourDoublefeelPasswordToContinue": "Re-enter your DoubleFeel password to continue.",
"changeEmail": "Change Email",
"yourCurrentEmailIs": "Your current email is",
"whatWouldYouLikeToUpdateItTo": ". What would you like to update it to?",
"successChanged": "Success changed",
"verifyEmailFailed": "Verify email failed"
}
... ...
... ... @@ -1261,5 +1261,12 @@
"monthlyMembership": "月度会员",
"quarterlyMembership": "季度会员",
"annualMembership": "年度会员",
"lifetimeMembership": "终身会员"
"lifetimeMembership": "终身会员",
"verifyYourPassword": "请确认您的密码",
"reEnterYourDoublefeelPasswordToContinue": "请重新输入您的 DoubleFeel 密码以继续。",
"changeEmail": "更改电子邮件地址",
"yourCurrentEmailIs": "您当前的电子邮箱是",
"whatWouldYouLikeToUpdateItTo": ". 您想将其更新为什么内容?",
"successChanged": "成功发生了变化",
"verifyEmailFailed": "邮箱验证失败"
}
... ...
... ... @@ -4573,6 +4573,48 @@ abstract class AppLocalizations {
/// In zh, this message translates to:
/// **'终身会员'**
String get lifetimeMembership;
/// No description provided for @verifyYourPassword.
///
/// In zh, this message translates to:
/// **'请确认您的密码'**
String get verifyYourPassword;
/// No description provided for @reEnterYourDoublefeelPasswordToContinue.
///
/// In zh, this message translates to:
/// **'请重新输入您的 DoubleFeel 密码以继续。'**
String get reEnterYourDoublefeelPasswordToContinue;
/// No description provided for @changeEmail.
///
/// In zh, this message translates to:
/// **'更改电子邮件地址'**
String get changeEmail;
/// No description provided for @yourCurrentEmailIs.
///
/// In zh, this message translates to:
/// **'您当前的电子邮箱是'**
String get yourCurrentEmailIs;
/// No description provided for @whatWouldYouLikeToUpdateItTo.
///
/// In zh, this message translates to:
/// **'. 您想将其更新为什么内容?'**
String get whatWouldYouLikeToUpdateItTo;
/// No description provided for @successChanged.
///
/// In zh, this message translates to:
/// **'成功发生了变化'**
String get successChanged;
/// No description provided for @verifyEmailFailed.
///
/// In zh, this message translates to:
/// **'邮箱验证失败'**
String get verifyEmailFailed;
}
class _AppLocalizationsDelegate
... ...
... ... @@ -1177,13 +1177,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get sleepQualityPoor => 'Poor sleep';
@override
String get sleepQualityExcellent => 'Excellent';
String get sleepQualityExcellent => 'Great';
@override
String get sleepQualityNormal => 'Normal';
String get sleepQualityNormal => 'Good';
@override
String get sleepQualityAttention => 'Needs attention';
String get sleepQualityAttention => 'Poor';
@override
String get hrvDailyStressTrend => 'Daily Stress Trend';
... ... @@ -1553,13 +1553,13 @@ class AppLocalizationsEn extends AppLocalizations {
String get friendsTodaySteps => 'Steps today';
@override
String get friendsSleepQualityExcellent => 'Slept great';
String get friendsSleepQualityExcellent => 'Great Sleep';
@override
String get friendsSleepQualityNormal => 'Slept well';
String get friendsSleepQualityNormal => 'Good Sleep';
@override
String get friendsSleepQualityAttention => 'Slept poorly';
String get friendsSleepQualityAttention => 'Poor Sleep';
@override
String get friendsRemove => 'Remove';
... ... @@ -1574,7 +1574,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get friendsShownOnWatchFace => 'Shown on Watch';
@override
String get friendsSelect => 'Select a friend';
String get friendsSelect => 'Choose a Loved One';
@override
String get friendsSelectAndSync => 'Select and sync to Watch';
... ... @@ -1952,7 +1952,7 @@ class AppLocalizationsEn extends AppLocalizations {
String get pressureOverload => 'Overload';
@override
String get beMindfulOfStress => 'Stressful';
String get beMindfulOfStress => 'Pay Attention';
@override
String get statusNormal => 'Normal';
... ... @@ -2562,4 +2562,27 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get lifetimeMembership => 'Lifetime';
@override
String get verifyYourPassword => 'Verify your password';
@override
String get reEnterYourDoublefeelPasswordToContinue =>
'Re-enter your DoubleFeel password to continue.';
@override
String get changeEmail => 'Change Email';
@override
String get yourCurrentEmailIs => 'Your current email is';
@override
String get whatWouldYouLikeToUpdateItTo =>
'. What would you like to update it to?';
@override
String get successChanged => 'Success changed';
@override
String get verifyEmailFailed => 'Verify email failed';
}
... ...
... ... @@ -2463,4 +2463,26 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get lifetimeMembership => '终身会员';
@override
String get verifyYourPassword => '请确认您的密码';
@override
String get reEnterYourDoublefeelPasswordToContinue =>
'请重新输入您的 DoubleFeel 密码以继续。';
@override
String get changeEmail => '更改电子邮件地址';
@override
String get yourCurrentEmailIs => '您当前的电子邮箱是';
@override
String get whatWouldYouLikeToUpdateItTo => '. 您想将其更新为什么内容?';
@override
String get successChanged => '成功发生了变化';
@override
String get verifyEmailFailed => '邮箱验证失败';
}
... ...
... ... @@ -13,12 +13,13 @@ class AppleSignInModel {
this.identityToken,
});
}
class GoogleSignInModel{
final String? email;
final String? clientID;
final String idToken;
final String? nickname;
final String? avatarUrl;
class GoogleSignInModel {
final String? email = null;
final String? clientID = null;
final String idToken = '';
final String? nickname = null;
final String? avatarUrl = null;
}
class WatchAppOtherInfo {
... ... @@ -122,7 +123,7 @@ abstract class PlatformHostApi {
/// 返回完整的 User-Agent 字符串,由 native 侧组装:
/// `{systemWebViewUA} {appName}/{versionCode}({versionName})({manufacturer}##{brand}##{model}; {OS}{osVersion}; {height}x{width})(huawei)`
String getFullUserAgent();
/// 是否是中国大陆地区
@async
bool isChinaRegion();
... ... @@ -170,6 +171,7 @@ abstract class PlatformHostApi {
/// 请求苹果登录
@async
AppleSignInModel? requestAppleSignIn();
/// google登录
@async
GoogleSignInModel? requestGoogleSignIn();
... ...
... ... @@ -194,7 +194,7 @@ packages:
source: hosted
version: "0.3.4+2"
crypto:
dependency: transitive
dependency: "direct main"
description:
name: crypto
sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf
... ...
... ... @@ -62,6 +62,7 @@ dependencies:
url: "https://gitcode.com/CPF-Flutter/flutter_sqflite.git"
ref: 2.4.2-ohos-1.0.0-beta.2
path: sqflite
crypto: ^3.0.7
# share_plus (git) 间接依赖 path_provider 的 git 版本,
... ...