feat: 实现起卦、设置与积分系统
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/app_color_palette.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class CoinCenterScreen extends StatelessWidget {
|
||||
const CoinCenterScreen({super.key, required this.balance});
|
||||
|
||||
final int balance;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final palette = Theme.of(context).extension<AppColorPalette>()!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsCoinCenterTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(AppSpacing.xl),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
gradient: LinearGradient(
|
||||
colors: [colors.primary, palette.accentPurple],
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.monetization_on_rounded,
|
||||
color: colors.onPrimary,
|
||||
size: 34,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
Text(
|
||||
l10n.settingsCoinBalanceLabel,
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(color: colors.onPrimary),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
l10n.settingsCoinBalanceValue(balance),
|
||||
style: Theme.of(
|
||||
context,
|
||||
).textTheme.headlineMedium?.copyWith(color: colors.onPrimary),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.sm),
|
||||
Text(
|
||||
l10n.settingsCoinCenterDescription,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colors.onPrimary.withValues(alpha: 0.88),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SectionLabel(text: l10n.settingsCoinRechargeSection),
|
||||
CoinPackageCard(
|
||||
title: l10n.settingsCoinPackBasic,
|
||||
price: '\$4.99',
|
||||
amount: 100,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
CoinPackageCard(
|
||||
title: l10n.settingsCoinPackPopular,
|
||||
price: '\$7.99',
|
||||
amount: 210,
|
||||
badge: l10n.settingsCoinPackPopularBadge,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
CoinPackageCard(
|
||||
title: l10n.settingsCoinPackPremium,
|
||||
price: '\$12.99',
|
||||
amount: 415,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../data/models/profile_settings.dart';
|
||||
import 'language_settings_screen.dart';
|
||||
import 'settings_placeholder_screen.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class GeneralSettingsScreen extends StatefulWidget {
|
||||
const GeneralSettingsScreen({
|
||||
super.key,
|
||||
required this.settings,
|
||||
required this.onInterfaceLanguageChanged,
|
||||
});
|
||||
|
||||
final ProfileSettingsV1 settings;
|
||||
final Future<void> Function(String languageTag) onInterfaceLanguageChanged;
|
||||
|
||||
@override
|
||||
State<GeneralSettingsScreen> createState() => _GeneralSettingsScreenState();
|
||||
}
|
||||
|
||||
class _GeneralSettingsScreenState extends State<GeneralSettingsScreen> {
|
||||
late ProfileSettingsV1 _settings;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_settings = widget.settings;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
return PopScope<ProfileSettingsV1>(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (didPop, result) {
|
||||
if (didPop) {
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pop(_settings);
|
||||
},
|
||||
child: Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
leading: IconButton(
|
||||
onPressed: () => Navigator.of(context).pop(_settings),
|
||||
icon: const Icon(Icons.arrow_back_ios_new_rounded),
|
||||
),
|
||||
title: Text(l10n.settingsGeneralTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SectionLabel(text: l10n.settingsSectionGeneral),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.language_rounded,
|
||||
title: l10n.language,
|
||||
subtitle: displayLanguageLabel(
|
||||
l10n,
|
||||
_settings.preferences.interfaceLanguage,
|
||||
),
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: _openLanguageSettings,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.auto_awesome_rounded,
|
||||
title: l10n.settingsAiLanguage,
|
||||
subtitle: displayLanguageLabel(
|
||||
l10n,
|
||||
_settings.preferences.aiLanguage,
|
||||
),
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
title: l10n.settingsAiLanguage,
|
||||
value: displayLanguageLabel(
|
||||
l10n,
|
||||
_settings.preferences.aiLanguage,
|
||||
),
|
||||
description: l10n.settingsAiLanguageHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.public_rounded,
|
||||
title: l10n.settingsTimezone,
|
||||
subtitle: _settings.preferences.timezone,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
title: l10n.settingsTimezone,
|
||||
value: _settings.preferences.timezone,
|
||||
description: l10n.settingsTimezoneHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.flag_outlined,
|
||||
title: l10n.settingsCountry,
|
||||
subtitle: _settings.preferences.country,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: () => _openPlaceholder(
|
||||
title: l10n.settingsCountry,
|
||||
value: _settings.preferences.country,
|
||||
description: l10n.settingsCountryHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openLanguageSettings() async {
|
||||
final result = await Navigator.of(context).push<String>(
|
||||
MaterialPageRoute<String>(
|
||||
builder: (_) => LanguageSettingsScreen(
|
||||
selectedLanguageTag: _settings.preferences.interfaceLanguage,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (result == null || result == _settings.preferences.interfaceLanguage) {
|
||||
return;
|
||||
}
|
||||
await widget.onInterfaceLanguageChanged(result);
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_settings = _settings.copyWith(
|
||||
preferences: _settings.preferences.copyWith(interfaceLanguage: result),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openPlaceholder({
|
||||
required String title,
|
||||
required String value,
|
||||
required String description,
|
||||
}) async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => SettingsPlaceholderScreen(
|
||||
title: title,
|
||||
value: value,
|
||||
description: description,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class LanguageSettingsScreen extends StatelessWidget {
|
||||
const LanguageSettingsScreen({super.key, required this.selectedLanguageTag});
|
||||
|
||||
final String selectedLanguageTag;
|
||||
|
||||
@override
|
||||
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),
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.language),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SectionLabel(text: l10n.settingsLanguageSection),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
for (int i = 0; i < options.length; i++)
|
||||
SettingsMenuTile(
|
||||
icon: Icons.language_rounded,
|
||||
title: options[i].label,
|
||||
subtitle: options[i].tag,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: i != options.length - 1,
|
||||
trailing: selectedLanguageTag == options[i].tag
|
||||
? Icon(Icons.check_rounded, color: colors.primary)
|
||||
: Icon(
|
||||
Icons.chevron_right_rounded,
|
||||
color: colors.outline,
|
||||
),
|
||||
onTap: () => Navigator.of(context).pop(options[i].tag),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../models/legal_document_type.dart';
|
||||
import '../utils/legal_document_assets.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
import 'legal_document_screen.dart';
|
||||
|
||||
class LegalCenterScreen extends StatelessWidget {
|
||||
const LegalCenterScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final locale = Localizations.localeOf(context);
|
||||
final documents = [
|
||||
LegalDocumentType.aboutUs,
|
||||
LegalDocumentType.privacyPolicy,
|
||||
LegalDocumentType.termsOfService,
|
||||
];
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsLegalCenterTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SectionLabel(text: l10n.settingsSectionAbout),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
for (int i = 0; i < documents.length; i++)
|
||||
SettingsMenuTile(
|
||||
icon: legalDocumentIcon(documents[i]),
|
||||
title: legalDocumentTitle(l10n, documents[i]),
|
||||
subtitle: legalDocumentSubtitle(l10n, documents[i]),
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: i != documents.length - 1,
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => LegalDocumentScreen(
|
||||
title: legalDocumentTitle(l10n, documents[i]),
|
||||
assetPath: legalDocumentAssetPath(locale, documents[i]),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/app_loading_indicator.dart';
|
||||
|
||||
class LegalDocumentScreen extends StatelessWidget {
|
||||
const LegalDocumentScreen({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.assetPath,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String assetPath;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(title),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: FutureBuilder<String>(
|
||||
future: rootBundle.loadString(assetPath),
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Center(
|
||||
child: AppLoadingIndicator(variant: AppLoadingVariant.surface),
|
||||
);
|
||||
}
|
||||
|
||||
return Markdown(
|
||||
data: snapshot.data!,
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(Theme.of(context))
|
||||
.copyWith(
|
||||
p: Theme.of(
|
||||
context,
|
||||
).textTheme.bodyMedium?.copyWith(height: 1.7),
|
||||
h1: Theme.of(context).textTheme.titleLarge,
|
||||
h2: Theme.of(context).textTheme.titleMedium,
|
||||
h3: Theme.of(
|
||||
context,
|
||||
).textTheme.titleMedium?.copyWith(color: colors.primary),
|
||||
blockSpacing: AppSpacing.lg,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../data/models/profile_settings.dart';
|
||||
import 'settings_placeholder_screen.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class PrivacyNotificationSettingsScreen extends StatelessWidget {
|
||||
const PrivacyNotificationSettingsScreen({super.key, required this.settings});
|
||||
|
||||
final ProfileSettingsV1 settings;
|
||||
|
||||
@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.settingsPrivacyAndNotificationTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SectionLabel(text: l10n.settingsSectionPrivacy),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.visibility_outlined,
|
||||
title: l10n.settingsPrivacyProfileVisibility,
|
||||
subtitle: l10n.settingsPlaceholderState(
|
||||
settings.privacy.length,
|
||||
),
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsPrivacyProfileVisibility,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsPrivacyHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.psychology_alt_outlined,
|
||||
title: l10n.settingsPrivacyPersonalization,
|
||||
subtitle: l10n.settingsComingSoon,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsPrivacyPersonalization,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsPrivacyHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.history_toggle_off_rounded,
|
||||
title: l10n.settingsPrivacyHistoryVisibility,
|
||||
subtitle: l10n.settingsComingSoon,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsPrivacyHistoryVisibility,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsPrivacyHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SectionLabel(text: l10n.settingsSectionNotification),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.notifications_outlined,
|
||||
title: l10n.settingsNotificationSystem,
|
||||
subtitle: l10n.settingsPlaceholderState(
|
||||
settings.notification.length,
|
||||
),
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsNotificationSystem,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsNotificationHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.campaign_outlined,
|
||||
title: l10n.settingsNotificationActivity,
|
||||
subtitle: l10n.settingsComingSoon,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsNotificationActivity,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsNotificationHint,
|
||||
),
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.auto_graph_outlined,
|
||||
title: l10n.settingsNotificationResult,
|
||||
subtitle: l10n.settingsComingSoon,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: () => _openPlaceholder(
|
||||
context,
|
||||
title: l10n.settingsNotificationResult,
|
||||
value: l10n.settingsComingSoon,
|
||||
description: l10n.settingsNotificationHint,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openPlaceholder(
|
||||
BuildContext context, {
|
||||
required String title,
|
||||
required String value,
|
||||
required String description,
|
||||
}) async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => SettingsPlaceholderScreen(
|
||||
title: title,
|
||||
value: value,
|
||||
description: description,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
|
||||
class SettingsPlaceholderScreen extends StatelessWidget {
|
||||
const SettingsPlaceholderScreen({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.description,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String value;
|
||||
final String description;
|
||||
|
||||
@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(title),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
SectionLabel(text: l10n.settingsCurrentValue),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.info_outline_rounded,
|
||||
title: title,
|
||||
subtitle: value,
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
showChevron: false,
|
||||
onTap: () {},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
Card(
|
||||
margin: EdgeInsets.zero,
|
||||
elevation: 0,
|
||||
color: colors.surface,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.lg),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
child: Text(
|
||||
description,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/app_color_palette.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../data/models/profile_settings.dart';
|
||||
import '../widgets/settings_section_widgets.dart';
|
||||
import 'coin_center_screen.dart';
|
||||
import 'general_settings_screen.dart';
|
||||
import 'legal_center_screen.dart';
|
||||
import 'privacy_notification_settings_screen.dart';
|
||||
|
||||
class SettingsScreen extends StatefulWidget {
|
||||
const SettingsScreen({
|
||||
super.key,
|
||||
required this.account,
|
||||
required this.settings,
|
||||
required this.coinBalance,
|
||||
required this.onInterfaceLanguageChanged,
|
||||
required this.onLogout,
|
||||
});
|
||||
|
||||
final String account;
|
||||
final ProfileSettingsV1 settings;
|
||||
final int coinBalance;
|
||||
final Future<void> Function(String languageTag) onInterfaceLanguageChanged;
|
||||
final Future<void> Function() onLogout;
|
||||
|
||||
@override
|
||||
State<SettingsScreen> createState() => _SettingsScreenState();
|
||||
}
|
||||
|
||||
class _SettingsScreenState extends State<SettingsScreen> {
|
||||
late ProfileSettingsV1 _settings;
|
||||
bool _isLoggingOut = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_settings = widget.settings;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
final palette = Theme.of(context).extension<AppColorPalette>()!;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
AppSpacing.lg,
|
||||
AppSpacing.md,
|
||||
AppSpacing.lg,
|
||||
AppSpacing.xl,
|
||||
),
|
||||
children: [
|
||||
ProfileHeaderCard(
|
||||
account: widget.account,
|
||||
version: _settings.version,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
WalletHeroCard(
|
||||
balance: widget.coinBalance,
|
||||
subtitle: l10n.settingsCoinHeroSubtitle,
|
||||
onTap: _openCoinCenter,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SectionLabel(text: l10n.settingsSectionQuickAccess),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.toll_rounded,
|
||||
title: l10n.settingsCoinCenterTitle,
|
||||
subtitle: l10n.settingsCoinCenterSubtitle(widget.coinBalance),
|
||||
tint: palette.historyGoldText,
|
||||
background: palette.historyGoldBg,
|
||||
onTap: _openCoinCenter,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.tune_rounded,
|
||||
title: l10n.settingsGeneralTitle,
|
||||
subtitle: l10n.settingsGeneralSubtitle(
|
||||
displayLanguageLabel(
|
||||
l10n,
|
||||
_settings.preferences.interfaceLanguage,
|
||||
),
|
||||
),
|
||||
tint: colors.primary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
onTap: _openGeneralSettings,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.privacy_tip_outlined,
|
||||
title: l10n.settingsPrivacyAndNotificationTitle,
|
||||
subtitle: l10n.settingsPrivacyAndNotificationSubtitle,
|
||||
tint: palette.warning,
|
||||
background: palette.warningContainer,
|
||||
onTap: _openPrivacyAndNotification,
|
||||
),
|
||||
SettingsMenuTile(
|
||||
icon: Icons.description_outlined,
|
||||
title: l10n.settingsLegalCenterTitle,
|
||||
subtitle: l10n.settingsLegalCenterSubtitle,
|
||||
tint: colors.secondary,
|
||||
background: colors.surfaceContainerHighest,
|
||||
showDivider: false,
|
||||
onTap: _openLegalCenter,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
SectionLabel(text: l10n.settingsSectionAccount),
|
||||
SettingsGroupCard(
|
||||
children: [
|
||||
SettingsMenuTile(
|
||||
icon: Icons.logout_rounded,
|
||||
title: l10n.logout,
|
||||
subtitle: l10n.settingsLogoutSubtitle,
|
||||
tint: colors.error,
|
||||
background: colors.error.withValues(alpha: 0.08),
|
||||
showDivider: false,
|
||||
onTap: _confirmLogout,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
FilledButton(
|
||||
onPressed: _isLoggingOut ? null : _confirmLogout,
|
||||
style: FilledButton.styleFrom(
|
||||
elevation: 0,
|
||||
backgroundColor: colors.error,
|
||||
foregroundColor: colors.onError,
|
||||
padding: const EdgeInsets.symmetric(vertical: AppSpacing.lg),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(AppRadius.full),
|
||||
),
|
||||
),
|
||||
child: Text(l10n.logout),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openCoinCenter() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => CoinCenterScreen(balance: widget.coinBalance),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openGeneralSettings() async {
|
||||
final result = await Navigator.of(context).push<ProfileSettingsV1>(
|
||||
MaterialPageRoute<ProfileSettingsV1>(
|
||||
builder: (_) => GeneralSettingsScreen(
|
||||
settings: _settings,
|
||||
onInterfaceLanguageChanged: widget.onInterfaceLanguageChanged,
|
||||
),
|
||||
),
|
||||
);
|
||||
if (result == null || !mounted) {
|
||||
return;
|
||||
}
|
||||
setState(() {
|
||||
_settings = result;
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _openPrivacyAndNotification() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(
|
||||
builder: (_) => PrivacyNotificationSettingsScreen(settings: _settings),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _openLegalCenter() async {
|
||||
await Navigator.of(context).push<void>(
|
||||
MaterialPageRoute<void>(builder: (_) => const LegalCenterScreen()),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _confirmLogout() async {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (dialogContext) {
|
||||
return AlertDialog(
|
||||
title: Text(l10n.settingsLogoutDialogTitle),
|
||||
content: Text(l10n.settingsLogoutDialogBody),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(false),
|
||||
child: Text(l10n.settingsCancel),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => Navigator.of(dialogContext).pop(true),
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Theme.of(dialogContext).colorScheme.error,
|
||||
foregroundColor: Theme.of(dialogContext).colorScheme.onError,
|
||||
),
|
||||
child: Text(l10n.logout),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (confirmed != true) {
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_isLoggingOut = true;
|
||||
});
|
||||
try {
|
||||
await widget.onLogout();
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoggingOut = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user