Files
zhinian_manage/lib/pages/settings_page.dart

518 lines
19 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/services.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 ConsumerStatefulWidget {
const SettingsPage({super.key});
@override
ConsumerState<SettingsPage> createState() => _SettingsPageState();
}
class _SettingsPageState extends ConsumerState<SettingsPage> {
final ScrollController _scrollController = ScrollController();
bool _scrolled = false;
static const double _scrollThreshold = 120;
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
@override
void dispose() {
_scrollController.removeListener(_onScroll);
_scrollController.dispose();
super.dispose();
}
void _onScroll() {
final shouldBe = _scrollController.offset > _scrollThreshold;
if (shouldBe != _scrolled) {
setState(() => _scrolled = shouldBe);
}
}
void _showThemePicker(BuildContext context) {
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: BoxDecoration(
color: AppColors.surface,
borderRadius: const 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: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)),
const SizedBox(height: 20),
_themeOption(ctx, AppThemeMode.light, l10n.lightMode, Icons.wb_sunny_outlined, current),
_themeOption(ctx, AppThemeMode.dark, l10n.darkMode, Icons.nights_stay_outlined, current),
_themeOption(ctx, AppThemeMode.system, l10n.followSystem, Icons.settings_suggest_outlined, current),
],
),
),
);
},
);
}
Widget _themeOption(BuildContext ctx, 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) {
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: BoxDecoration(
color: AppColors.surface,
borderRadius: const 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: TextStyle(fontSize: 18, fontWeight: FontWeight.bold, color: AppColors.textPrimary)),
const SizedBox(height: 20),
_languageOption(ctx, const Locale('zh', 'CN'), l10n.simplifiedChinese, current),
_languageOption(ctx, const Locale('en', 'US'), l10n.english, current),
_languageOption(ctx, const Locale('th', 'TH'), l10n.thai, current),
],
),
),
);
},
);
}
Widget _languageOption(BuildContext ctx, 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) {
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 AnnotatedRegion<SystemUiOverlayStyle>(
value: _scrolled
? (AppColors.isDark ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark)
: SystemUiOverlayStyle.light,
child: Scaffold(
backgroundColor: AppColors.background,
body: Stack(
children: [
ListView(
controller: _scrollController,
padding: EdgeInsets.zero,
children: [
_buildHeader(context, user, l10n, isBoss),
Padding(
padding: const EdgeInsets.fromLTRB(20, 28, 20, 32),
child: Column(
children: [
Align(
alignment: Alignment.centerLeft,
child: _buildSectionTitle(l10n.appConfiguration),
),
const SizedBox(height: 14),
_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),
),
_SettingItem(
icon: Icons.language_outlined,
iconColor: const Color(0xFF3B82F6),
title: l10n.languageSettings,
subtitle: localeLabel,
onTap: () => _showLanguagePicker(context),
),
]),
const SizedBox(height: 28),
Align(
alignment: Alignment.centerLeft,
child: _buildSectionTitle(l10n.businessExtension),
),
const SizedBox(height: 14),
_buildSettingsCard([
_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: 28),
Align(
alignment: Alignment.centerLeft,
child: _buildSectionTitle(l10n.system),
),
const SizedBox(height: 14),
_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: 36),
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),
),
),
),
],
),
),
],
),
Positioned(
top: 0,
left: 0,
right: 0,
child: SafeArea(
bottom: false,
child: SizedBox(
height: 44,
child: Align(
alignment: Alignment.centerLeft,
child: IconButton(
icon: Icon(
Icons.arrow_back_ios_new,
color: _scrolled ? AppColors.textPrimary : Colors.white,
size: 20,
),
onPressed: () {
if (Navigator.of(context).canPop()) {
Navigator.of(context).pop();
} else {
context.go('/home');
}
},
),
),
),
),
),
],
),
),
);
}
Widget _buildHeader(BuildContext context, user, AppLocalizations l10n, bool isBoss) {
final initial = (user?.name is String && (user!.name as String).isNotEmpty)
? (user.name as String).substring(0, 1)
: l10n.user.substring(0, 1);
return Container(
decoration: BoxDecoration(
gradient: AppGradients.primary,
borderRadius: const BorderRadius.vertical(bottom: Radius.circular(28)),
),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 60, 12, 36),
child: Column(
children: [
Container(
width: 96,
height: 96,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.white.withValues(alpha: 0.16),
border: Border.all(
color: Colors.white.withValues(alpha: 0.32),
width: 1.5,
),
),
child: Center(
child: Text(
initial,
style: const TextStyle(
fontSize: 36,
fontWeight: FontWeight.w700,
color: Colors.white,
),
),
),
),
const SizedBox(height: 18),
Text(
user?.name ?? l10n.user,
style: const TextStyle(
fontSize: 24,
fontWeight: FontWeight.w700,
color: Colors.white,
letterSpacing: 1.5,
),
),
const SizedBox(height: 8),
Text(
user?.phone ?? '',
style: TextStyle(
fontSize: 14,
color: Colors.white.withValues(alpha: 0.82),
letterSpacing: 1,
),
),
const SizedBox(height: 18),
Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
decoration: BoxDecoration(
color: Colors.white.withValues(alpha: 0.22),
borderRadius: BorderRadius.circular(22),
),
child: Text(
isBoss ? l10n.boss : l10n.employee,
style: const TextStyle(
fontSize: 13,
color: Colors.white,
fontWeight: FontWeight.w500,
letterSpacing: 1.5,
),
),
),
],
),
),
),
);
}
Widget _buildSectionTitle(String title) {
return Padding(
padding: const EdgeInsets.only(left: 4),
child: Text(
title,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: AppColors.textSecondary,
letterSpacing: 1.2,
),
),
);
}
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: 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: TextStyle(
fontSize: 13,
color: AppColors.textTertiary,
),
),
trailing: item.trailing ?? 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,
});
}