diff --git a/apps/lib/features/settings/presentation/screens/language_settings_screen.dart b/apps/lib/features/settings/presentation/screens/language_settings_screen.dart index b2082f0..2b8ae5d 100644 --- a/apps/lib/features/settings/presentation/screens/language_settings_screen.dart +++ b/apps/lib/features/settings/presentation/screens/language_settings_screen.dart @@ -13,10 +13,8 @@ class LanguageSettingsScreen extends StatelessWidget { Widget build(BuildContext context) { final l10n = AppLocalizations.of(context)!; final colors = Theme.of(context).colorScheme; - final options = <({String tag, String label})>[ - (tag: 'zh-CN', label: l10n.chinese), - (tag: 'en-US', label: l10n.english), - ]; + + final options = _buildLanguageOptions(l10n); return Scaffold( backgroundColor: colors.surfaceContainerLow, @@ -40,12 +38,10 @@ class LanguageSettingsScreen extends StatelessWidget { tint: colors.primary, background: colors.surfaceContainerHighest, showDivider: i != options.length - 1, + showChevron: false, trailing: selectedLanguageTag == options[i].tag ? Icon(Icons.check_rounded, color: colors.primary) - : Icon( - Icons.chevron_right_rounded, - color: colors.outline, - ), + : null, onTap: () => Navigator.of(context).pop(options[i].tag), ), ], @@ -54,4 +50,41 @@ class LanguageSettingsScreen extends StatelessWidget { ), ); } + + List<({String tag, String label})> _buildLanguageOptions( + AppLocalizations l10n, + ) { + final supportedLocales = AppLocalizations.supportedLocales; + return supportedLocales.map((locale) { + final tag = _localeToTag(locale); + final label = _getLocaleLabel(locale, l10n); + return (tag: tag, label: label); + }).toList(); + } + + String _localeToTag(Locale locale) { + final lang = locale.languageCode; + final script = locale.scriptCode; + final country = locale.countryCode; + if (script != null && country != null) { + return '$lang-$script-$country'; + } else if (country != null) { + return '$lang-$country'; + } + return _mapToBackendTag(lang); + } + + String _mapToBackendTag(String flutterTag) { + const mapping = {'zh': 'zh-CN', 'en': 'en-US'}; + return mapping[flutterTag] ?? flutterTag; + } + + String _getLocaleLabel(Locale locale, AppLocalizations l10n) { + if (locale.languageCode == 'zh') { + return l10n.chinese; + } else if (locale.languageCode == 'en') { + return l10n.english; + } + return locale.languageCode; + } } diff --git a/apps/lib/features/settings/presentation/screens/profile_edit_screen.dart b/apps/lib/features/settings/presentation/screens/profile_edit_screen.dart index 6d1f633..209a380 100644 --- a/apps/lib/features/settings/presentation/screens/profile_edit_screen.dart +++ b/apps/lib/features/settings/presentation/screens/profile_edit_screen.dart @@ -14,11 +14,13 @@ class ProfileEditScreen extends StatefulWidget { required this.account, required this.settings, required this.onUploadAvatar, + required this.onSave, }); final String account; final ProfileSettingsV1 settings; final Future Function(String filePath) onUploadAvatar; + final Future Function(ProfileSettingsV1 updated) onSave; @override State createState() => _ProfileEditScreenState(); @@ -191,7 +193,7 @@ class _ProfileEditScreenState extends State { ); } - void _save() { + Future _save() async { final l10n = AppLocalizations.of(context)!; final name = _nameController.text.trim(); if (name.isEmpty) { @@ -202,14 +204,33 @@ class _ProfileEditScreenState extends State { ); return; } - Navigator.of(context).pop( - widget.settings.copyWith( - displayName: name, - bio: _bioController.text.trim(), - avatarPath: _avatarPath, - avatarUrl: _avatarPreviewUrl, - ), + final updated = widget.settings.copyWith( + displayName: name, + bio: _bioController.text.trim(), + avatarPath: _avatarPath, + avatarUrl: _avatarPreviewUrl, ); + try { + final saved = await widget.onSave(updated); + if (!mounted) { + return; + } + Navigator.of(context).pop(saved); + } catch (error, stackTrace) { + _logger.error( + message: 'Failed to save profile', + error: error, + stackTrace: stackTrace, + ); + if (!mounted) { + return; + } + Toast.show( + context, + AppLocalizations.of(context)!.errorRequestGeneric, + type: ToastType.error, + ); + } } Future _pickAndUploadAvatar() async { diff --git a/apps/lib/features/settings/presentation/screens/settings_screen.dart b/apps/lib/features/settings/presentation/screens/settings_screen.dart index 57516ca..84ab40e 100644 --- a/apps/lib/features/settings/presentation/screens/settings_screen.dart +++ b/apps/lib/features/settings/presentation/screens/settings_screen.dart @@ -1,16 +1,14 @@ import 'package:flutter/material.dart'; import '../../../../l10n/app_localizations.dart'; -import '../../../../core/logging/logger.dart'; import '../../../../shared/theme/design_tokens.dart'; import '../../../../shared/widgets/app_modal_dialog.dart'; import '../../../../shared/widgets/gua_icon.dart'; -import '../../../../shared/widgets/toast/toast.dart'; -import '../../../../shared/widgets/toast/toast_type.dart'; import '../../data/models/profile_settings.dart'; import '../widgets/settings_section_widgets.dart'; import 'coin_center_screen.dart'; import 'general_settings_screen.dart'; +import 'invite_screen.dart'; import 'legal_center_screen.dart'; import 'profile_edit_screen.dart'; @@ -24,6 +22,7 @@ class SettingsScreen extends StatefulWidget { required this.onSettingsChanged, required this.onUploadAvatar, required this.onLogout, + required this.onSaveProfile, }); final String account; @@ -33,13 +32,14 @@ class SettingsScreen extends StatefulWidget { final Future Function(ProfileSettingsV1 settings) onSettingsChanged; final Future Function(String filePath) onUploadAvatar; final Future Function() onLogout; + final Future Function(ProfileSettingsV1 updated) + onSaveProfile; @override State createState() => _SettingsScreenState(); } class _SettingsScreenState extends State { - final Logger _logger = getLogger('features.settings.settings_screen'); late ProfileSettingsV1 _settings; @override @@ -102,6 +102,13 @@ class _SettingsScreenState extends State { background: colors.surfaceContainerHighest, onTap: _openGeneralSettings, ), + SettingsMenuTile( + icon: Icons.card_giftcard_rounded, + title: l10n.settingsInviteTitle, + tint: colors.primary, + background: colors.surfaceContainerHighest, + onTap: _openInvite, + ), SettingsMenuTile( icon: Icons.description_outlined, title: l10n.settingsLegalCenterTitle, @@ -140,20 +147,26 @@ class _SettingsScreenState extends State { } Future _openGeneralSettings() async { - final result = await Navigator.of(context).push( - MaterialPageRoute( + await Navigator.of(context).push( + MaterialPageRoute( builder: (_) => GeneralSettingsScreen( settings: _settings, - onInterfaceLanguageChanged: widget.onInterfaceLanguageChanged, + onSettingsChanged: (newSettings) async { + await widget.onSettingsChanged(newSettings); + if (!mounted) return; + setState(() { + _settings = newSettings; + }); + }, ), ), ); - if (result == null || !mounted) { - return; - } - setState(() { - _settings = result; - }); + } + + Future _openInvite() async { + await Navigator.of( + context, + ).push(MaterialPageRoute(builder: (_) => const InviteScreen())); } Future _openProfileEdit() async { @@ -163,35 +176,16 @@ class _SettingsScreenState extends State { account: widget.account, settings: _settings, onUploadAvatar: widget.onUploadAvatar, + onSave: widget.onSaveProfile, ), ), ); if (result == null || !mounted) { return; } - try { - await widget.onSettingsChanged(result); - if (!mounted) { - return; - } - setState(() { - _settings = result; - }); - } catch (error, stackTrace) { - _logger.error( - message: 'Failed to save profile settings', - error: error, - stackTrace: stackTrace, - ); - if (!mounted) { - return; - } - Toast.show( - context, - AppLocalizations.of(context)!.errorRequestGeneric, - type: ToastType.error, - ); - } + setState(() { + _settings = result; + }); } Future _openLegalCenter() async { diff --git a/apps/test/features/divination/divination_result_screen_test.dart b/apps/test/features/divination/divination_result_screen_test.dart index 18bf956..4ed1de7 100644 --- a/apps/test/features/divination/divination_result_screen_test.dart +++ b/apps/test/features/divination/divination_result_screen_test.dart @@ -27,6 +27,7 @@ void main() { lowerName: '离', signType: '中上签', keywords: '稳中求进、审时度势、蓄势待发', + focusPoints: const ['世应有情,主事可成', '动爻化退,宜稳不宜急'], conclusion: '1. 方向可行\n2. 节奏宜稳', analysis: '当前阶段需先稳住节奏,再做关键推进。', suggestion: '1. 控节奏\n2. 重复盘',