Files
note-book/frontend/lib/screens/profile/profile_screen.dart
2026-05-19 11:46:42 +08:00

435 lines
15 KiB
Dart
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../providers/auth_provider.dart';
import '../../providers/family_provider.dart';
import '../../theme/colors.dart';
import '../../widgets/glass_card.dart';
class ProfileScreen extends ConsumerWidget {
const ProfileScreen({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
final authState = ref.watch(authProvider);
final isLoggedIn = authState.isLoggedIn;
final user = authState.user;
return Scaffold(
backgroundColor: AppColors.pageBg,
body: isLoggedIn && user != null
? _LoggedInContent(user: user)
: const _LoggedOutContent(),
);
}
}
class _LoggedOutContent extends StatelessWidget {
const _LoggedOutContent();
@override
Widget build(BuildContext context) {
return SafeArea(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
const Spacer(),
// Glass card with login prompt
GlassCard(
padding: const EdgeInsets.all(24),
child: Column(
children: [
Container(
width: 80,
height: 80,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.brand, AppColors.brandLight],
),
shape: BoxShape.circle,
),
child: const Icon(
Icons.person_outline,
size: 40,
color: Colors.white,
),
),
const SizedBox(height: 20),
const Text(
'登录后可同步数据',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 8),
Text(
'管理家庭账本',
style: TextStyle(
fontSize: 14,
color: AppColors.textSecondary.withOpacity(0.8),
),
),
const SizedBox(height: 24),
// Login button
GestureDetector(
onTap: () {
Navigator.pushNamed(context, '/login');
},
child: Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.brand, AppColors.brandLight],
),
borderRadius: BorderRadius.circular(25),
),
child: const Center(
child: Text(
'立即登录',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
),
),
),
),
),
],
),
),
const Spacer(flex: 2),
],
),
),
);
}
}
class _LoggedInContent extends ConsumerWidget {
final dynamic user;
const _LoggedInContent({required this.user});
@override
Widget build(BuildContext context, WidgetRef ref) {
final familiesAsync = ref.watch(familyProvider);
final nickname = user.nickname ?? '用户';
final firstLetter = nickname.isNotEmpty ? nickname[0].toUpperCase() : 'U';
return SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(20),
child: Column(
children: [
// User card with orange gradient avatar
GlassCard(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Row(
children: [
// Orange gradient circular avatar with first letter
Container(
width: 64,
height: 64,
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [AppColors.brand, AppColors.brandLight],
),
shape: BoxShape.circle,
),
child: Center(
child: Text(
firstLetter,
style: const TextStyle(
fontSize: 28,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
),
),
const SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
nickname,
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: AppColors.textPrimary,
),
),
const SizedBox(height: 4),
Text(
'ID: ${user.id ?? ""}',
style: TextStyle(
fontSize: 13,
color: AppColors.textSecondary.withOpacity(0.8),
),
),
],
),
),
],
),
const SizedBox(height: 16),
// Email status and family info
Row(
children: [
Icon(
user.email != null && user.email!.isNotEmpty
? Icons.check_circle
: Icons.warning_amber_outlined,
size: 16,
color: user.email != null && user.email!.isNotEmpty
? AppColors.income
: AppColors.expense,
),
const SizedBox(width: 6),
Text(
user.email != null && user.email!.isNotEmpty
? '邮箱已绑定'
: '未绑定邮箱',
style: TextStyle(
fontSize: 13,
color: AppColors.textSecondary.withOpacity(0.8),
),
),
const Spacer(),
familiesAsync.when(
loading: () => const SizedBox.shrink(),
error: (_, __) => const SizedBox.shrink(),
data: (families) {
if (families.isEmpty) {
return const SizedBox.shrink();
}
return Row(
children: [
const Icon(
Icons.home_outlined,
size: 16,
color: AppColors.brand,
),
const SizedBox(width: 6),
Text(
families.first.name,
style: const TextStyle(
fontSize: 13,
color: AppColors.brand,
),
),
],
);
},
),
],
),
],
),
),
const SizedBox(height: 24),
// Settings groups with glassmorphism
_SettingsGroup(
title: '账本管理',
items: const [
_SettingsItem(icon: '🏠', label: '我的家庭', route: '/family'),
_SettingsItem(icon: '📒', label: '账本管理', route: '/books'),
_SettingsItem(icon: '💰', label: '预算设置', route: '/budget'),
_SettingsItem(icon: '🏷️', label: '分类管理', route: '/categories'),
],
),
const SizedBox(height: 16),
_SettingsGroup(
title: '系统设置',
items: const [
_SettingsItem(icon: '🤖', label: 'AI设置', route: '/ai-settings'),
_SettingsItem(icon: '📤', label: '数据导出', route: '/export'),
_SettingsItem(icon: '🔔', label: '提醒设置', route: '/notifications'),
],
),
const SizedBox(height: 16),
_SettingsGroup(
title: '关于',
items: const [
_SettingsItem(icon: '', label: '关于', route: '/about'),
_SettingsItem(icon: '💬', label: '帮助与反馈', route: '/feedback'),
],
),
const SizedBox(height: 24),
// Logout button with red border
GestureDetector(
onTap: () async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认退出'),
content: const Text('确定要退出登录吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text(
'退出',
style: TextStyle(color: AppColors.expense),
),
),
],
),
);
if (confirmed == true) {
await ref.read(authProvider.notifier).logout();
}
},
child: Container(
width: double.infinity,
height: 50,
decoration: BoxDecoration(
color: Colors.transparent,
borderRadius: BorderRadius.circular(25),
border: Border.all(color: AppColors.expense, width: 1.5),
),
child: const Center(
child: Text(
'退出登录',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: AppColors.expense,
),
),
),
),
),
const SizedBox(height: 20),
],
),
),
);
}
}
class _SettingsGroup extends StatelessWidget {
final String title;
final List<_SettingsItem> items;
const _SettingsGroup({
required this.title,
required this.items,
});
@override
Widget build(BuildContext context) {
return GlassCard(
padding: EdgeInsets.zero,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Text(
title,
style: const TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: AppColors.brand,
),
),
),
...items.asMap().entries.map((entry) {
final index = entry.key;
final item = entry.value;
final isLast = index == items.length - 1;
return Column(
children: [
_SettingsTile(item: item),
if (!isLast)
Divider(
height: 1,
indent: 56,
endIndent: 16,
color: AppColors.textSecondary.withOpacity(0.1),
),
],
);
}),
const SizedBox(height: 8),
],
),
);
}
}
class _SettingsItem {
final String icon;
final String label;
final String route;
const _SettingsItem({
required this.icon,
required this.label,
required this.route,
});
}
class _SettingsTile extends StatelessWidget {
final _SettingsItem item;
const _SettingsTile({required this.item});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
Navigator.pushNamed(context, item.route);
},
behavior: HitTestBehavior.opaque,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
Text(
item.icon,
style: const TextStyle(fontSize: 20),
),
const SizedBox(width: 12),
Expanded(
child: Text(
item.label,
style: const TextStyle(
fontSize: 15,
color: AppColors.textPrimary,
),
),
),
Icon(
Icons.chevron_right,
size: 20,
color: AppColors.textSecondary.withOpacity(0.5),
),
],
),
),
);
}
}