435 lines
16 KiB
Dart
435 lines
16 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import '../providers/auth_provider.dart';
|
|
import '../providers/settings_provider.dart';
|
|
import '../theme.dart';
|
|
import '../l10n/app_localizations.dart';
|
|
|
|
class SettingsPage extends ConsumerWidget {
|
|
const SettingsPage({super.key});
|
|
|
|
void _showThemePicker(BuildContext context, WidgetRef ref) {
|
|
final current = ref.read(settingsProvider).themeMode;
|
|
showModalBottomSheet(
|
|
context: context,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (ctx) {
|
|
final l10n = AppLocalizations.of(ctx)!;
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
|
),
|
|
child: SafeArea(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(width: 40, height: 4, decoration: BoxDecoration(color: AppColors.divider, borderRadius: BorderRadius.circular(2))),
|
|
const SizedBox(height: 20),
|
|
Text(l10n.themeSettings, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)),
|
|
const SizedBox(height: 20),
|
|
_themeOption(ctx, ref, AppThemeMode.light, l10n.lightMode, Icons.wb_sunny_outlined, current),
|
|
_themeOption(ctx, ref, AppThemeMode.dark, l10n.darkMode, Icons.nights_stay_outlined, current),
|
|
_themeOption(ctx, ref, AppThemeMode.system, l10n.followSystem, Icons.settings_suggest_outlined, current),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _themeOption(BuildContext ctx, WidgetRef ref, AppThemeMode mode, String label, IconData icon, AppThemeMode current) {
|
|
final selected = current == mode;
|
|
return ListTile(
|
|
leading: Icon(icon, color: selected ? AppColors.primary : AppColors.textTertiary),
|
|
title: Text(label, style: TextStyle(fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: AppColors.textPrimary)),
|
|
trailing: selected ? const Icon(Icons.check_circle, color: AppColors.primary) : null,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
onTap: () {
|
|
ref.read(settingsProvider.notifier).setThemeMode(mode);
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
);
|
|
}
|
|
|
|
void _showLanguagePicker(BuildContext context, WidgetRef ref) {
|
|
final current = ref.read(settingsProvider).locale;
|
|
showModalBottomSheet(
|
|
context: context,
|
|
backgroundColor: Colors.transparent,
|
|
builder: (ctx) {
|
|
final l10n = AppLocalizations.of(ctx)!;
|
|
return Container(
|
|
padding: const EdgeInsets.all(20),
|
|
decoration: const BoxDecoration(
|
|
color: Colors.white,
|
|
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
|
|
),
|
|
child: SafeArea(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Container(width: 40, height: 4, decoration: BoxDecoration(color: AppColors.divider, borderRadius: BorderRadius.circular(2))),
|
|
const SizedBox(height: 20),
|
|
Text(l10n.languageSettings, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)),
|
|
const SizedBox(height: 20),
|
|
_languageOption(ctx, ref, const Locale('zh', 'CN'), l10n.simplifiedChinese, current),
|
|
_languageOption(ctx, ref, const Locale('en', 'US'), l10n.english, current),
|
|
_languageOption(ctx, ref, const Locale('th', 'TH'), l10n.thai, current),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _languageOption(BuildContext ctx, WidgetRef ref, Locale locale, String label, Locale current) {
|
|
final selected = current.languageCode == locale.languageCode;
|
|
return ListTile(
|
|
title: Text(label, style: TextStyle(fontWeight: selected ? FontWeight.w600 : FontWeight.w400, color: AppColors.textPrimary)),
|
|
trailing: selected ? const Icon(Icons.check_circle, color: AppColors.primary) : null,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
|
|
onTap: () {
|
|
ref.read(settingsProvider.notifier).setLocale(locale);
|
|
Navigator.of(ctx).pop();
|
|
},
|
|
);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final l10n = AppLocalizations.of(context)!;
|
|
final authState = ref.watch(authProvider);
|
|
final settings = ref.watch(settingsProvider);
|
|
final user = authState.user;
|
|
final isBoss = user?.isBoss ?? false;
|
|
|
|
final themeLabel = switch (settings.themeMode) {
|
|
AppThemeMode.light => l10n.lightMode,
|
|
AppThemeMode.dark => l10n.darkMode,
|
|
AppThemeMode.system => l10n.followSystem,
|
|
};
|
|
|
|
final localeLabel = switch (settings.locale.languageCode) {
|
|
'zh' => l10n.simplifiedChinese,
|
|
'en' => l10n.english,
|
|
'th' => l10n.thai,
|
|
_ => l10n.simplifiedChinese,
|
|
};
|
|
|
|
return Scaffold(
|
|
backgroundColor: AppColors.background,
|
|
appBar: AppBar(title: Text(l10n.settings)),
|
|
body: ListView(
|
|
padding: const EdgeInsets.all(20),
|
|
children: [
|
|
_buildProfileCard(user, l10n),
|
|
const SizedBox(height: 24),
|
|
_buildSectionTitle(l10n.appConfiguration),
|
|
const SizedBox(height: 12),
|
|
_buildSettingsCard([
|
|
_SettingItem(
|
|
icon: Icons.notifications_outlined,
|
|
iconColor: const Color(0xFF8B5CF6),
|
|
title: l10n.pushNotification,
|
|
subtitle: settings.pushNotification ? l10n.enabled : l10n.disabled,
|
|
trailing: Switch(
|
|
value: settings.pushNotification,
|
|
onChanged: (v) => ref.read(settingsProvider.notifier).setPushNotification(v),
|
|
activeTrackColor: AppColors.primary,
|
|
),
|
|
onTap: () => ref.read(settingsProvider.notifier).setPushNotification(!settings.pushNotification),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.volume_up_outlined,
|
|
iconColor: const Color(0xFFEC4899),
|
|
title: l10n.soundNotification,
|
|
subtitle: settings.soundNotification ? l10n.enabled : l10n.disabled,
|
|
trailing: Switch(
|
|
value: settings.soundNotification,
|
|
onChanged: (v) => ref.read(settingsProvider.notifier).setSoundNotification(v),
|
|
activeTrackColor: AppColors.primary,
|
|
),
|
|
onTap: () => ref.read(settingsProvider.notifier).setSoundNotification(!settings.soundNotification),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.palette_outlined,
|
|
iconColor: const Color(0xFFEC4899),
|
|
title: l10n.themeSettings,
|
|
subtitle: themeLabel,
|
|
onTap: () => _showThemePicker(context, ref),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.language_outlined,
|
|
iconColor: const Color(0xFF3B82F6),
|
|
title: l10n.languageSettings,
|
|
subtitle: localeLabel,
|
|
onTap: () => _showLanguagePicker(context, ref),
|
|
),
|
|
]),
|
|
const SizedBox(height: 24),
|
|
_buildSectionTitle(l10n.businessExtension),
|
|
const SizedBox(height: 12),
|
|
_buildSettingsCard([
|
|
_SettingItem(
|
|
icon: Icons.store_outlined,
|
|
iconColor: const Color(0xFFF59E0B),
|
|
title: l10n.storeManagement,
|
|
subtitle: l10n.storeDesc,
|
|
onTap: () => context.push('/settings/store'),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.people_outline,
|
|
iconColor: const Color(0xFF10B981),
|
|
title: l10n.employeeManagement,
|
|
subtitle: l10n.employeeDesc,
|
|
showBadge: isBoss,
|
|
onTap: () => context.push('/settings/employees'),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.analytics_outlined,
|
|
iconColor: const Color(0xFFEF4444),
|
|
title: l10n.dataReport,
|
|
subtitle: l10n.reportDesc,
|
|
showBadge: isBoss,
|
|
onTap: () => context.push('/settings/report'),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.extension_outlined,
|
|
iconColor: const Color(0xFF6366F1),
|
|
title: l10n.appMarket,
|
|
subtitle: l10n.marketDesc,
|
|
onTap: () => context.push('/settings/app-market'),
|
|
),
|
|
]),
|
|
const SizedBox(height: 24),
|
|
_buildSectionTitle(l10n.system),
|
|
const SizedBox(height: 12),
|
|
_buildSettingsCard([
|
|
_SettingItem(
|
|
icon: Icons.help_outline,
|
|
iconColor: const Color(0xFF64748B),
|
|
title: l10n.helpCenter,
|
|
subtitle: l10n.helpDesc,
|
|
onTap: () => context.push('/settings/help'),
|
|
),
|
|
_SettingItem(
|
|
icon: Icons.info_outline,
|
|
iconColor: const Color(0xFF64748B),
|
|
title: l10n.aboutUs,
|
|
subtitle: '${l10n.version} 1.0.0',
|
|
onTap: () => context.push('/settings/about'),
|
|
),
|
|
]),
|
|
const SizedBox(height: 32),
|
|
SizedBox(
|
|
width: double.infinity,
|
|
height: 52,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
ref.read(authProvider.notifier).logout();
|
|
context.go('/login');
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: AppColors.error.withValues(alpha: 0.1),
|
|
foregroundColor: AppColors.error,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(14),
|
|
),
|
|
),
|
|
child: Text(
|
|
l10n.logout,
|
|
style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w600),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 32),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildProfileCard(user, AppLocalizations l10n) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(24),
|
|
decoration: BoxDecoration(
|
|
gradient: AppGradients.primary,
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Container(
|
|
width: 64,
|
|
height: 64,
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.2),
|
|
shape: BoxShape.circle,
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
user?.name.substring(0, 1) ?? l10n.user,
|
|
style: const TextStyle(
|
|
fontSize: 24,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
user?.name ?? l10n.user,
|
|
style: const TextStyle(
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.bold,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
user?.phone ?? '',
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
color: Colors.white.withValues(alpha: 0.8),
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
|
decoration: BoxDecoration(
|
|
color: Colors.white.withValues(alpha: 0.2),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
child: Text(
|
|
user?.isBoss == true ? l10n.boss : l10n.employee,
|
|
style: const TextStyle(
|
|
fontSize: 12,
|
|
fontWeight: FontWeight.w500,
|
|
color: Colors.white,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
Icon(
|
|
Icons.chevron_right,
|
|
color: Colors.white.withValues(alpha: 0.6),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSectionTitle(String title) {
|
|
return Text(
|
|
title,
|
|
style: const TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: AppColors.textSecondary,
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildSettingsCard(List<_SettingItem> items) {
|
|
return Container(
|
|
decoration: BoxDecoration(
|
|
color: AppColors.surface,
|
|
borderRadius: BorderRadius.circular(16),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.03),
|
|
blurRadius: 10,
|
|
offset: const Offset(0, 2),
|
|
),
|
|
],
|
|
),
|
|
child: Column(
|
|
children: items.asMap().entries.map((entry) {
|
|
final item = entry.value;
|
|
final isLast = entry.key == items.length - 1;
|
|
return Column(
|
|
children: [
|
|
ListTile(
|
|
leading: Container(
|
|
width: 40,
|
|
height: 40,
|
|
decoration: BoxDecoration(
|
|
color: item.iconColor.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(10),
|
|
),
|
|
child: Icon(item.icon, color: item.iconColor, size: 20),
|
|
),
|
|
title: Row(
|
|
children: [
|
|
Text(
|
|
item.title,
|
|
style: const TextStyle(
|
|
fontSize: 15,
|
|
fontWeight: FontWeight.w500,
|
|
color: AppColors.textPrimary,
|
|
),
|
|
),
|
|
if (item.showBadge) ...[
|
|
const SizedBox(width: 8),
|
|
Container(
|
|
width: 8,
|
|
height: 8,
|
|
decoration: const BoxDecoration(
|
|
color: AppColors.error,
|
|
shape: BoxShape.circle,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
subtitle: Text(
|
|
item.subtitle,
|
|
style: const TextStyle(
|
|
fontSize: 13,
|
|
color: AppColors.textTertiary,
|
|
),
|
|
),
|
|
trailing: item.trailing ?? const Icon(Icons.chevron_right, color: AppColors.textTertiary, size: 20),
|
|
onTap: item.onTap,
|
|
),
|
|
if (!isLast)
|
|
const Divider(height: 1, indent: 72),
|
|
],
|
|
);
|
|
}).toList(),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SettingItem {
|
|
final IconData icon;
|
|
final Color iconColor;
|
|
final String title;
|
|
final String subtitle;
|
|
final VoidCallback onTap;
|
|
final bool showBadge;
|
|
final Widget? trailing;
|
|
|
|
const _SettingItem({
|
|
required this.icon,
|
|
required this.iconColor,
|
|
required this.title,
|
|
required this.subtitle,
|
|
required this.onTap,
|
|
this.showBadge = false,
|
|
this.trailing,
|
|
});
|
|
}
|