create project
This commit is contained in:
435
frontend/lib/screens/profile/profile_screen.dart
Normal file
435
frontend/lib/screens/profile/profile_screen.dart
Normal file
@@ -0,0 +1,435 @@
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user