feat: 添加账号删除功能
This commit is contained in:
@@ -305,6 +305,21 @@ class _EryaoAppState extends State<EryaoApp> {
|
||||
return saved;
|
||||
}
|
||||
|
||||
Future<void> _deleteAccount() async {
|
||||
await _profileApi.deleteAccount();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_profileSettings = ProfileSettingsV1.defaultsForLocale(_locale);
|
||||
_historyRecords = const <DivinationResultData>[];
|
||||
_creditsBalance = 0;
|
||||
_loadedProfileUserEmail = null;
|
||||
_loadedHistoryUserEmail = null;
|
||||
_loadedCreditsUserEmail = null;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _saveProfileSettings(ProfileSettingsV1 next) async {
|
||||
try {
|
||||
final oldLanguage = _profileSettings.preferences.interfaceLanguage;
|
||||
@@ -415,6 +430,7 @@ class _EryaoAppState extends State<EryaoApp> {
|
||||
onDivinationCompleted: _handleDivinationCompleted,
|
||||
onDeleteHistorySession: _handleHistorySessionDeleted,
|
||||
onLogout: _authBloc.logout,
|
||||
onDeleteAccount: _deleteAccount,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +29,8 @@ String mapApiProblemToMessage(ApiProblem problem, AppLocalizations l10n) {
|
||||
return l10n.errorAudioEmpty;
|
||||
case 'AGENT_ASR_UNAVAILABLE':
|
||||
return l10n.errorAsrUnavailable;
|
||||
case 'PROFILE_DELETE_FAILED':
|
||||
return l10n.errorProfileDeleteFailed;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,13 @@ class ApiClient {
|
||||
final authHeader =
|
||||
error.requestOptions.headers['Authorization'] as String?;
|
||||
final hasAuthHeader = authHeader != null && authHeader.isNotEmpty;
|
||||
if (status == 401 && hasAuthHeader && onUnauthorized != null) {
|
||||
final isLogoutEndpoint =
|
||||
error.requestOptions.method.toUpperCase() == 'DELETE' &&
|
||||
error.requestOptions.path == '/api/v1/auth/sessions';
|
||||
if (status == 401 &&
|
||||
hasAuthHeader &&
|
||||
onUnauthorized != null &&
|
||||
!isLogoutEndpoint) {
|
||||
await onUnauthorized();
|
||||
}
|
||||
handler.next(error);
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../../../../core/network/api_problem.dart';
|
||||
import '../../../../data/network/api_client.dart';
|
||||
import '../models/session_response.dart';
|
||||
|
||||
@@ -25,9 +28,32 @@ class AuthApi {
|
||||
}
|
||||
|
||||
Future<void> deleteSession({required String refreshToken}) async {
|
||||
await _apiClient.deleteNoContent(
|
||||
final response = await _apiClient.rawDio.delete<Map<String, dynamic>>(
|
||||
'/api/v1/auth/sessions',
|
||||
data: {'refresh_token': refreshToken},
|
||||
options: Options(
|
||||
validateStatus: (status) => status != null && status < 500,
|
||||
),
|
||||
);
|
||||
final status = response.statusCode ?? 500;
|
||||
if (status == 204 || status == 401) {
|
||||
return;
|
||||
}
|
||||
|
||||
final data = response.data;
|
||||
if (data is Map<String, dynamic>) {
|
||||
throw ApiProblem(
|
||||
status: status,
|
||||
title: (data['title'] as String?) ?? 'Request failed',
|
||||
detail: (data['detail'] as String?) ?? '',
|
||||
code: data['code'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
throw ApiProblem(
|
||||
status: status,
|
||||
title: 'Request failed',
|
||||
detail: 'Failed to delete session',
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import '../../../../core/auth/session_store.dart';
|
||||
import '../../../../core/network/api_problem.dart';
|
||||
import '../apis/auth_api.dart';
|
||||
import '../models/auth_user.dart';
|
||||
|
||||
@@ -70,7 +71,14 @@ class AuthRepositoryImpl implements AuthRepository {
|
||||
try {
|
||||
final refreshToken = await _sessionStore.getRefreshToken();
|
||||
if (refreshToken != null && refreshToken.isNotEmpty) {
|
||||
await _authApi.deleteSession(refreshToken: refreshToken);
|
||||
try {
|
||||
await _authApi.deleteSession(refreshToken: refreshToken);
|
||||
} on ApiProblem catch (problem) {
|
||||
if (problem.status != 401 ||
|
||||
problem.code != 'AUTH_REFRESH_TOKEN_INVALID') {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
await clearLocalSession();
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../../../../core/logging/logger.dart';
|
||||
@@ -56,18 +58,19 @@ class AuthBloc extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> logout() async {
|
||||
try {
|
||||
await _repository.logout();
|
||||
} catch (error, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'User logout failed: ${error.runtimeType}',
|
||||
error: error.runtimeType.toString(),
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}
|
||||
_logger.info(message: 'User logged out');
|
||||
_state = const AuthState(status: AuthStatus.unauthenticated);
|
||||
notifyListeners();
|
||||
|
||||
unawaited(
|
||||
_repository.logout().catchError((Object error, StackTrace stackTrace) {
|
||||
_logger.error(
|
||||
message: 'User logout failed: ${error.runtimeType}',
|
||||
error: error.runtimeType.toString(),
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> handleUnauthorized401() async {
|
||||
|
||||
@@ -35,6 +35,7 @@ class HomeScreen extends StatefulWidget {
|
||||
required this.onDivinationCompleted,
|
||||
required this.onDeleteHistorySession,
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
});
|
||||
|
||||
final String account;
|
||||
@@ -54,6 +55,7 @@ class HomeScreen extends StatefulWidget {
|
||||
onDivinationCompleted;
|
||||
final Future<void> Function(String threadId) onDeleteHistorySession;
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
|
||||
@override
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
@@ -116,6 +118,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
onSaveProfile: widget.onSaveProfile,
|
||||
onUploadAvatar: widget.onUploadAvatar,
|
||||
onLogout: widget.onLogout,
|
||||
onDeleteAccount: widget.onDeleteAccount,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -505,6 +508,7 @@ class _ProfileTab extends StatelessWidget {
|
||||
required this.onSaveProfile,
|
||||
required this.onUploadAvatar,
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
});
|
||||
|
||||
final String account;
|
||||
@@ -516,6 +520,7 @@ class _ProfileTab extends StatelessWidget {
|
||||
onSaveProfile;
|
||||
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -528,6 +533,7 @@ class _ProfileTab extends StatelessWidget {
|
||||
onSaveProfile: onSaveProfile,
|
||||
onUploadAvatar: onUploadAvatar,
|
||||
onLogout: onLogout,
|
||||
onDeleteAccount: onDeleteAccount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,10 @@ class ProfileApi {
|
||||
return _toSettings(data);
|
||||
}
|
||||
|
||||
Future<void> deleteAccount() async {
|
||||
await _apiClient.deleteNoContent('/api/v1/users/me');
|
||||
}
|
||||
|
||||
ProfileSettingsV1 _toSettings(Map<String, dynamic> json) {
|
||||
final settingsRaw = json['settings'];
|
||||
final preferencesRaw = settingsRaw is Map<String, dynamic>
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../core/logging/logger.dart';
|
||||
import '../../../../core/network/api_problem.dart';
|
||||
import '../../../../core/network/api_problem_mapper.dart';
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class AccountDeleteScreen extends StatefulWidget {
|
||||
const AccountDeleteScreen({super.key, required this.onDeleteAccount});
|
||||
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
|
||||
@override
|
||||
State<AccountDeleteScreen> createState() => _AccountDeleteScreenState();
|
||||
}
|
||||
|
||||
class _AccountDeleteScreenState extends State<AccountDeleteScreen> {
|
||||
final Logger _logger = getLogger('features.settings.account_delete');
|
||||
bool _isDeleting = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsAccountAndDataTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.delete_outline_rounded,
|
||||
title: l10n.settingsDeleteAccountTitle,
|
||||
tint: colors.error,
|
||||
background: colors.surfaceContainerHighest,
|
||||
titleColor: colors.error,
|
||||
showDivider: false,
|
||||
onTap: _isDeleting ? () {} : _confirmDelete,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmDelete() async {
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return const _DeleteConfirmDialog();
|
||||
},
|
||||
);
|
||||
if (confirmed != true || !mounted) {
|
||||
return;
|
||||
}
|
||||
await _deleteAccount();
|
||||
}
|
||||
|
||||
Future<void> _deleteAccount() async {
|
||||
if (_isDeleting) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_isDeleting = true;
|
||||
});
|
||||
try {
|
||||
await widget.onDeleteAccount();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(true);
|
||||
} catch (error, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'Delete account request failed',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final message = error is ApiProblem
|
||||
? mapApiProblemToMessage(error, l10n)
|
||||
: l10n.errorRequestGeneric;
|
||||
Toast.show(context, message, type: ToastType.error);
|
||||
setState(() {
|
||||
_isDeleting = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _DeleteConfirmDialog extends StatefulWidget {
|
||||
const _DeleteConfirmDialog();
|
||||
|
||||
@override
|
||||
State<_DeleteConfirmDialog> createState() => _DeleteConfirmDialogState();
|
||||
}
|
||||
|
||||
class _DeleteConfirmDialogState extends State<_DeleteConfirmDialog> {
|
||||
static const int _coolDownSeconds = 5;
|
||||
int _secondsLeft = _coolDownSeconds;
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
if (!mounted) {
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
if (_secondsLeft <= 1) {
|
||||
setState(() {
|
||||
_secondsLeft = 0;
|
||||
});
|
||||
timer.cancel();
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_secondsLeft -= 1;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_timer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return Dialog(
|
||||
insetPadding: const EdgeInsets.symmetric(
|
||||
horizontal: AppSpacing.lg,
|
||||
vertical: AppSpacing.xl,
|
||||
),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.lg,
|
||||
AppSpacing.lg,
|
||||
AppSpacing.lg,
|
||||
AppSpacing.md,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surface,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: colors.outlineVariant),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: colors.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(AppRadius.md),
|
||||
),
|
||||
alignment: Alignment.center,
|
||||
child: Icon(
|
||||
Icons.warning_rounded,
|
||||
color: colors.error,
|
||||
size: 22,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: Text(
|
||||
l10n.settingsDeleteAccountDialogTitle,
|
||||
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.w700,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
l10n.settingsDeleteAccountWarningBody,
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
color: colors.onSurfaceVariant,
|
||||
height: 1.45,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
_secondsLeft > 0
|
||||
? l10n.settingsDeleteAccountWaitAction(_secondsLeft)
|
||||
: l10n.settingsDeleteAccountDialogBody,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colors.error,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: OutlinedButton(
|
||||
onPressed: () => Navigator.of(context).pop(false),
|
||||
style: OutlinedButton.styleFrom(
|
||||
foregroundColor: colors.onSurface,
|
||||
side: BorderSide(color: colors.outline),
|
||||
minimumSize: const Size.fromHeight(44),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
),
|
||||
child: Text(l10n.settingsCancel),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: AppSpacing.sm),
|
||||
Expanded(
|
||||
child: FilledButton(
|
||||
onPressed: _secondsLeft <= 0
|
||||
? () => Navigator.of(context).pop(true)
|
||||
: null,
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: colors.error,
|
||||
foregroundColor: colors.onError,
|
||||
disabledBackgroundColor: colors.error.withValues(
|
||||
alpha: 0.4,
|
||||
),
|
||||
disabledForegroundColor: colors.onError.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
minimumSize: const Size.fromHeight(44),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
),
|
||||
child: Text(l10n.settingsDeleteAccountAction),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_modal_dialog.dart';
|
||||
import '../../../../shared/widgets/gua_icon.dart';
|
||||
import '../../data/models/profile_settings.dart';
|
||||
import 'account_delete_screen.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
import 'coin_center_screen.dart';
|
||||
import 'general_settings_screen.dart';
|
||||
@@ -22,6 +23,7 @@ class SettingsScreen extends StatefulWidget {
|
||||
required this.onSettingsChanged,
|
||||
required this.onUploadAvatar,
|
||||
required this.onLogout,
|
||||
required this.onDeleteAccount,
|
||||
required this.onSaveProfile,
|
||||
});
|
||||
|
||||
@@ -32,6 +34,7 @@ class SettingsScreen extends StatefulWidget {
|
||||
final Future<void> Function(ProfileSettingsV1 settings) onSettingsChanged;
|
||||
final Future<ProfileSettingsV1> Function(String filePath) onUploadAvatar;
|
||||
final Future<void> Function() onLogout;
|
||||
final Future<void> Function() onDeleteAccount;
|
||||
final Future<ProfileSettingsV1> Function(ProfileSettingsV1 updated)
|
||||
onSaveProfile;
|
||||
|
||||
@@ -119,6 +122,18 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.person_rounded,
|
||||
title: l10n.settingsAccountAndDataTitle,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: _openAccountDelete,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
FilledButton(
|
||||
onPressed: _confirmLogout,
|
||||
@@ -194,6 +209,23 @@ class _SettingsScreenState extends State<SettingsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openAccountDelete() async {
|
||||
final deleted = await Navigator.of(context).push<bool>(
|
||||
MaterialPageRoute<bool>(
|
||||
builder: (_) =>
|
||||
AccountDeleteScreen(onDeleteAccount: widget.onDeleteAccount),
|
||||
),
|
||||
);
|
||||
if (deleted != true) {
|
||||
return;
|
||||
}
|
||||
await widget.onLogout();
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
}
|
||||
|
||||
Future<void> _confirmLogout() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
|
||||
@@ -62,6 +62,8 @@ class SettingsMenuTile extends StatelessWidget {
|
||||
this.showChevron = true,
|
||||
this.trailing,
|
||||
this.subtitle,
|
||||
this.titleColor,
|
||||
this.subtitleColor,
|
||||
});
|
||||
|
||||
final IconData icon;
|
||||
@@ -73,6 +75,8 @@ class SettingsMenuTile extends StatelessWidget {
|
||||
final bool showDivider;
|
||||
final bool showChevron;
|
||||
final Widget? trailing;
|
||||
final Color? titleColor;
|
||||
final Color? subtitleColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -94,12 +98,27 @@ class SettingsMenuTile extends StatelessWidget {
|
||||
),
|
||||
child: Icon(icon, color: tint),
|
||||
),
|
||||
title: Text(title),
|
||||
title: Text(
|
||||
title,
|
||||
style: titleColor == null
|
||||
? null
|
||||
: Theme.of(context).textTheme.bodyLarge?.copyWith(
|
||||
color: titleColor,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
subtitle: subtitle == null
|
||||
? null
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(top: AppSpacing.xs),
|
||||
child: Text(subtitle!),
|
||||
child: Text(
|
||||
subtitle!,
|
||||
style: subtitleColor == null
|
||||
? null
|
||||
: Theme.of(
|
||||
context,
|
||||
).textTheme.bodySmall?.copyWith(color: subtitleColor),
|
||||
),
|
||||
),
|
||||
trailing:
|
||||
trailing ??
|
||||
@@ -162,7 +181,7 @@ class SettingsSwitchTile extends StatelessWidget {
|
||||
trailing: Switch(
|
||||
value: value,
|
||||
onChanged: onChanged,
|
||||
activeColor: colors.primary,
|
||||
activeThumbColor: colors.primary,
|
||||
),
|
||||
),
|
||||
if (showDivider)
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"settingsSectionAccount": "Account",
|
||||
"settingsSectionPrivacy": "Privacy",
|
||||
"settingsSectionNotification": "Notification Settings",
|
||||
"settingsAccountAndDataTitle": "Account Data",
|
||||
"settingsInterfaceLanguage": "Interface Language",
|
||||
"settingsAiLanguage": "AI Response Language",
|
||||
"settingsNotificationAllow": "Allow Notifications",
|
||||
@@ -141,6 +142,25 @@
|
||||
"settingsLogoutSubtitle": "Sign out from the current account",
|
||||
"settingsLogoutDialogTitle": "Confirm logout?",
|
||||
"settingsLogoutDialogBody": "You will need to sign in again to continue with this account.",
|
||||
"settingsDeleteAccountTitle": "Delete Account",
|
||||
"settingsDeleteAccountSubtitle": "Permanently delete your account and personal data",
|
||||
"settingsDeleteAccountWarningTitle": "Please confirm before deleting",
|
||||
"settingsDeleteAccountWarningBody": "After deletion, related data including profile, history, and points will be permanently removed and cannot be restored.",
|
||||
"settingsDeleteAccountScopeProfile": "Profile and account information will be deleted",
|
||||
"settingsDeleteAccountScopeHistory": "Divination history records will be deleted",
|
||||
"settingsDeleteAccountScopePoints": "Points account and ledger records will be deleted",
|
||||
"settingsDeleteAccountDialogTitle": "Permanently delete this account?",
|
||||
"settingsDeleteAccountDialogBody": "This action cannot be undone. Deletion will start immediately after confirmation.",
|
||||
"settingsDeleteAccountAction": "Delete Account",
|
||||
"settingsDeleteAccountProcessing": "Deleting...",
|
||||
"settingsDeleteAccountWaitAction": "Wait {seconds}s before confirming deletion",
|
||||
"@settingsDeleteAccountWaitAction": {
|
||||
"placeholders": {
|
||||
"seconds": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsCancel": "Cancel",
|
||||
"settingsLogoutConfirmHint": "Tap again to confirm logout",
|
||||
"settingsLogoutConfirmAction": "Tap again to logout",
|
||||
@@ -202,6 +222,7 @@
|
||||
"errorServiceUnavailable": "Service unavailable, please try again later",
|
||||
"errorServerGeneric": "Server error, please try again later",
|
||||
"errorRequestGeneric": "Request failed, please try again",
|
||||
"errorProfileDeleteFailed": "Failed to delete account, please try again",
|
||||
"errorRunLimitExceeded": "Run limit reached in this session. Please start a new divination.",
|
||||
"errorDivinationPayloadRequired": "Missing divination payload. Please cast again.",
|
||||
"divinationScreenTitle": "Cast Hexagram",
|
||||
|
||||
@@ -506,6 +506,12 @@ abstract class AppLocalizations {
|
||||
/// **'通知设置'**
|
||||
String get settingsSectionNotification;
|
||||
|
||||
/// No description provided for @settingsAccountAndDataTitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'账号数据'**
|
||||
String get settingsAccountAndDataTitle;
|
||||
|
||||
/// No description provided for @settingsInterfaceLanguage.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
@@ -728,6 +734,78 @@ abstract class AppLocalizations {
|
||||
/// **'退出后需要重新登录才能继续使用当前账户。'**
|
||||
String get settingsLogoutDialogBody;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountTitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'删除账号'**
|
||||
String get settingsDeleteAccountTitle;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountSubtitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'永久删除账号及相关个人数据'**
|
||||
String get settingsDeleteAccountSubtitle;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountWarningTitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'删除前请确认'**
|
||||
String get settingsDeleteAccountWarningTitle;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountWarningBody.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'删除账号后,个人资料、历史记录、点数信息等相关数据将被永久删除,且不可恢复。'**
|
||||
String get settingsDeleteAccountWarningBody;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountScopeProfile.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'个人资料和账号信息会被删除'**
|
||||
String get settingsDeleteAccountScopeProfile;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountScopeHistory.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'历史解卦记录会被删除'**
|
||||
String get settingsDeleteAccountScopeHistory;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountScopePoints.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'点数账户与流水记录会被删除'**
|
||||
String get settingsDeleteAccountScopePoints;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountDialogTitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'确认永久删除账号?'**
|
||||
String get settingsDeleteAccountDialogTitle;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountDialogBody.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'此操作无法撤销。确认后将立即发起删除。'**
|
||||
String get settingsDeleteAccountDialogBody;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountAction.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'确认删除账号'**
|
||||
String get settingsDeleteAccountAction;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountProcessing.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'正在删除...'**
|
||||
String get settingsDeleteAccountProcessing;
|
||||
|
||||
/// No description provided for @settingsDeleteAccountWaitAction.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'请等待 {seconds} 秒后确认删除'**
|
||||
String settingsDeleteAccountWaitAction(int seconds);
|
||||
|
||||
/// No description provided for @settingsCancel.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
@@ -1010,6 +1088,12 @@ abstract class AppLocalizations {
|
||||
/// **'请求失败,请稍后重试'**
|
||||
String get errorRequestGeneric;
|
||||
|
||||
/// No description provided for @errorProfileDeleteFailed.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'删除账号失败,请稍后重试'**
|
||||
String get errorProfileDeleteFailed;
|
||||
|
||||
/// No description provided for @errorRunLimitExceeded.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
|
||||
@@ -222,6 +222,9 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get settingsSectionNotification => 'Notification Settings';
|
||||
|
||||
@override
|
||||
String get settingsAccountAndDataTitle => 'Account Data';
|
||||
|
||||
@override
|
||||
String get settingsInterfaceLanguage => 'Interface Language';
|
||||
|
||||
@@ -349,6 +352,52 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
String get settingsLogoutDialogBody =>
|
||||
'You will need to sign in again to continue with this account.';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountTitle => 'Delete Account';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountSubtitle =>
|
||||
'Permanently delete your account and personal data';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountWarningTitle =>
|
||||
'Please confirm before deleting';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountWarningBody =>
|
||||
'After deletion, related data including profile, history, and points will be permanently removed and cannot be restored.';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopeProfile =>
|
||||
'Profile and account information will be deleted';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopeHistory =>
|
||||
'Divination history records will be deleted';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopePoints =>
|
||||
'Points account and ledger records will be deleted';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountDialogTitle =>
|
||||
'Permanently delete this account?';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountDialogBody =>
|
||||
'This action cannot be undone. Deletion will start immediately after confirmation.';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountAction => 'Delete Account';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountProcessing => 'Deleting...';
|
||||
|
||||
@override
|
||||
String settingsDeleteAccountWaitAction(int seconds) {
|
||||
return 'Wait ${seconds}s before confirming deletion';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsCancel => 'Cancel';
|
||||
|
||||
@@ -501,6 +550,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get errorRequestGeneric => 'Request failed, please try again';
|
||||
|
||||
@override
|
||||
String get errorProfileDeleteFailed =>
|
||||
'Failed to delete account, please try again';
|
||||
|
||||
@override
|
||||
String get errorRunLimitExceeded =>
|
||||
'Run limit reached in this session. Please start a new divination.';
|
||||
|
||||
@@ -220,6 +220,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get settingsSectionNotification => '通知设置';
|
||||
|
||||
@override
|
||||
String get settingsAccountAndDataTitle => '账号数据';
|
||||
|
||||
@override
|
||||
String get settingsInterfaceLanguage => '界面语言';
|
||||
|
||||
@@ -343,6 +346,45 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get settingsLogoutDialogBody => '退出后需要重新登录才能继续使用当前账户。';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountTitle => '删除账号';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountSubtitle => '永久删除账号及相关个人数据';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountWarningTitle => '删除前请确认';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountWarningBody =>
|
||||
'删除账号后,个人资料、历史记录、点数信息等相关数据将被永久删除,且不可恢复。';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopeProfile => '个人资料和账号信息会被删除';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopeHistory => '历史解卦记录会被删除';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountScopePoints => '点数账户与流水记录会被删除';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountDialogTitle => '确认永久删除账号?';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountDialogBody => '此操作无法撤销。确认后将立即发起删除。';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountAction => '确认删除账号';
|
||||
|
||||
@override
|
||||
String get settingsDeleteAccountProcessing => '正在删除...';
|
||||
|
||||
@override
|
||||
String settingsDeleteAccountWaitAction(int seconds) {
|
||||
return '请等待 $seconds 秒后确认删除';
|
||||
}
|
||||
|
||||
@override
|
||||
String get settingsCancel => '取消';
|
||||
|
||||
@@ -491,6 +533,9 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get errorRequestGeneric => '请求失败,请稍后重试';
|
||||
|
||||
@override
|
||||
String get errorProfileDeleteFailed => '删除账号失败,请稍后重试';
|
||||
|
||||
@override
|
||||
String get errorRunLimitExceeded => '本次会话追问次数已达上限,请新起一卦';
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"settingsSectionAccount": "账户操作",
|
||||
"settingsSectionPrivacy": "隐私设置",
|
||||
"settingsSectionNotification": "通知设置",
|
||||
"settingsAccountAndDataTitle": "账号数据",
|
||||
"settingsInterfaceLanguage": "界面语言",
|
||||
"settingsAiLanguage": "AI回复语言",
|
||||
"settingsNotificationAllow": "允许通知",
|
||||
@@ -141,6 +142,25 @@
|
||||
"settingsLogoutSubtitle": "退出当前登录账户",
|
||||
"settingsLogoutDialogTitle": "确认退出登录?",
|
||||
"settingsLogoutDialogBody": "退出后需要重新登录才能继续使用当前账户。",
|
||||
"settingsDeleteAccountTitle": "删除账号",
|
||||
"settingsDeleteAccountSubtitle": "永久删除账号及相关个人数据",
|
||||
"settingsDeleteAccountWarningTitle": "删除前请确认",
|
||||
"settingsDeleteAccountWarningBody": "删除账号后,个人资料、历史记录、点数信息等相关数据将被永久删除,且不可恢复。",
|
||||
"settingsDeleteAccountScopeProfile": "个人资料和账号信息会被删除",
|
||||
"settingsDeleteAccountScopeHistory": "历史解卦记录会被删除",
|
||||
"settingsDeleteAccountScopePoints": "点数账户与流水记录会被删除",
|
||||
"settingsDeleteAccountDialogTitle": "确认永久删除账号?",
|
||||
"settingsDeleteAccountDialogBody": "此操作无法撤销。确认后将立即发起删除。",
|
||||
"settingsDeleteAccountAction": "确认删除账号",
|
||||
"settingsDeleteAccountProcessing": "正在删除...",
|
||||
"settingsDeleteAccountWaitAction": "请等待 {seconds} 秒后确认删除",
|
||||
"@settingsDeleteAccountWaitAction": {
|
||||
"placeholders": {
|
||||
"seconds": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"settingsCancel": "取消",
|
||||
"settingsLogoutConfirmHint": "再次点击确认退出登录",
|
||||
"settingsLogoutConfirmAction": "再次点击确认退出",
|
||||
@@ -202,6 +222,7 @@
|
||||
"errorServiceUnavailable": "服务暂时不可用,请稍后重试",
|
||||
"errorServerGeneric": "服务异常,请稍后重试",
|
||||
"errorRequestGeneric": "请求失败,请稍后重试",
|
||||
"errorProfileDeleteFailed": "删除账号失败,请稍后重试",
|
||||
"errorRunLimitExceeded": "本次会话追问次数已达上限,请新起一卦",
|
||||
"errorDivinationPayloadRequired": "缺少六爻输入数据,请重新起卦",
|
||||
"divinationScreenTitle": "起卦",
|
||||
|
||||
Reference in New Issue
Block a user