Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions jive-flutter/lib/core/router/app_router.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:jive_money/screens/home/home_screen.dart';
import 'package:jive_money/screens/dashboard/dashboard_screen.dart';
import 'package:jive_money/screens/transactions/transactions_screen.dart';
import 'package:jive_money/screens/accounts/accounts_screen.dart';
import 'package:jive_money/screens/accounts/user_assets_screen.dart';
import 'package:jive_money/screens/budgets/budgets_screen.dart';
import 'package:jive_money/screens/settings/settings_screen.dart';
import 'package:jive_money/screens/settings/theme_settings_screen.dart';
Expand All @@ -28,6 +29,7 @@ import 'package:jive_money/providers/ledger_provider.dart';

/// 路由路径常量
class AppRoutes {
static const userAssets = '/accounts/assets';
static const splash = '/';
static const login = '/login';
static const register = '/register';
Expand Down Expand Up @@ -152,6 +154,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
path: AppRoutes.accounts,
builder: (context, state) => const AccountsScreen(),
routes: [
GoRoute(
path: 'assets',
builder: (context, state) => const UserAssetsScreen(),
),
GoRoute(
path: 'add',
builder: (context, state) => const AccountAddScreen(),
Expand Down
13 changes: 13 additions & 0 deletions jive-flutter/lib/screens/accounts/accounts_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ class _AccountsScreenState extends ConsumerState<AccountsScreen> {
appBar: AppBar(
title: const Text('账户管理'),
actions: [
// 资产总览快捷入口(A)
IconButton(
tooltip: '资产总览',
icon: const Icon(Icons.pie_chart_outline),
onPressed: () => context.go(AppRoutes.userAssets),
),
// 视图切换
IconButton(
icon: Icon(_viewMode == 'list' ? Icons.folder : Icons.list),
Expand All @@ -50,6 +56,9 @@ class _AccountsScreenState extends ConsumerState<AccountsScreen> {
case 'archive':
_navigateToArchived();
break;
case 'assets':
context.go(AppRoutes.userAssets);
break;
}
},
itemBuilder: (context) => [
Expand All @@ -65,6 +74,10 @@ class _AccountsScreenState extends ConsumerState<AccountsScreen> {
value: 'archive',
child: Text('已归档账户'),
),
const PopupMenuItem(
value: 'assets',
child: Text('资产总览'),
),
],
),
],
Expand Down
129 changes: 129 additions & 0 deletions jive-flutter/lib/screens/accounts/user_assets_screen.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:jive_money/core/constants/app_constants.dart';
import 'package:jive_money/models/account.dart';
import 'package:jive_money/providers/account_provider.dart';

class UserAssetsScreen extends ConsumerWidget {
const UserAssetsScreen({super.key});

@override
Widget build(BuildContext context, WidgetRef ref) {
final stats = ref.watch(accountStatsProvider);
final accounts = ref.watch(accountsProvider);
final assetAccounts = accounts.where((a) => a.isAsset).toList();
final liabilityAccounts = accounts.where((a) => a.isLiability).toList();

final theme = Theme.of(context);

return Scaffold(
appBar: AppBar(
title: const Text('资产总览'),
),
body: RefreshIndicator(
onRefresh: () async => ref.read(accountProvider.notifier).refresh(),
child: ListView(
padding: const EdgeInsets.all(16),
children: [
_buildNetWorthCard(theme, stats),
const SizedBox(height: 16),
_buildSectionHeader(theme, '资产账户', Icons.account_balance_wallet),
..._buildAccountRows(theme, assetAccounts),
const SizedBox(height: 16),
_buildSectionHeader(theme, '负债账户', Icons.credit_card),
..._buildAccountRows(theme, liabilityAccounts),
const SizedBox(height: 32),
],
),
),
);
}

Widget _buildNetWorthCard(ThemeData theme, AccountStats stats) {
return Card(
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(AppConstants.borderRadius),
),
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('净资产', style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
Text(
stats.netWorth.toStringAsFixed(2),
style: theme.textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
Row(
children: [
Expanded(
child: _buildKpi(theme, '总资产', stats.totalAssets, color: AppConstants.successColor),
),
Container(width: 1, height: 24, color: theme.dividerColor.withValues(alpha: 0.5)),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The withValues method is an internal detail of the Color class and is being removed from the Flutter SDK. Using it can lead to build failures in future Flutter versions. Please use the public withOpacity method instead to set the color's transparency.

Suggested change
Container(width: 1, height: 24, color: theme.dividerColor.withValues(alpha: 0.5)),
Container(width: 1, height: 24, color: theme.dividerColor.withOpacity(0.5)),

Expanded(
child: _buildKpi(theme, '总负债', stats.totalLiabilities, color: AppConstants.errorColor),
),
],
),
],
),
),
);
}

Widget _buildKpi(ThemeData theme, String label, double value, {required Color color}) {
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurface.withValues(alpha: 0.6))),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The withValues method is an internal API and is slated for removal. To ensure future compatibility, please use the recommended withOpacity method to adjust the color's alpha value.

Suggested change
Text(label, style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurface.withValues(alpha: 0.6))),
Text(label, style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.onSurface.withOpacity(0.6))),

const SizedBox(height: 4),
Text(
value.toStringAsFixed(2),
style: theme.textTheme.titleMedium?.copyWith(color: color, fontWeight: FontWeight.w600),
),
],
),
);
}

Widget _buildSectionHeader(ThemeData theme, String title, IconData icon) {
return Row(
children: [
Icon(icon, color: theme.primaryColor),
const SizedBox(width: 8),
Text(title, style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold)),
],
);
}

List<Widget> _buildAccountRows(ThemeData theme, List<Account> accounts) {
if (accounts.isEmpty) {
return [
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Text('暂无', style: theme.textTheme.bodySmall?.copyWith(color: theme.hintColor)),
),
];
}
return accounts
.map((a) => ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 0),
leading: CircleAvatar(
backgroundColor: (a.displayColor).withValues(alpha: 0.15),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To avoid using internal Flutter APIs that may be removed, please replace withValues with the public withOpacity method for setting the color's transparency.

Suggested change
backgroundColor: (a.displayColor).withValues(alpha: 0.15),
backgroundColor: a.displayColor.withOpacity(0.15),

child: Icon(a.icon, color: a.displayColor),
),
title: Text(a.name),
subtitle: Text(a.type.label),
trailing: Text(
a.formattedBalance,
style: theme.textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
),
))
.toList();
}
}
9 changes: 5 additions & 4 deletions jive-flutter/lib/screens/auth/wechat_qr_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class _WeChatQRScreenState extends State<WeChatQRScreen>
try {
final userInfo = await WeChatService.simulateGetUserInfo();
final authResult = await WeChatService.simulateLogin();
if (!context.mounted) return;
if (!mounted) return;

if (userInfo != null && authResult != null) {
if (widget.isLogin) {
Expand All @@ -108,7 +108,8 @@ class _WeChatQRScreenState extends State<WeChatQRScreen>
});
} else {
// 注册流程:跳转到注册表单
final result = await Navigator.of(context).push(
final navigator = Navigator.of(context);
final result = await navigator.push(
MaterialPageRoute(
builder: (context) => WeChatRegisterFormScreen(
weChatUserInfo: userInfo,
Expand All @@ -117,10 +118,10 @@ class _WeChatQRScreenState extends State<WeChatQRScreen>
),
);

if (!context.mounted) return;
if (!mounted) return;

if (result != null) {
Navigator.of(context).pop(result);
navigator.pop(result);
}
}
}
Expand Down
14 changes: 8 additions & 6 deletions jive-flutter/lib/screens/auth/wechat_register_form_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class _WeChatRegisterFormScreenState extends State<WeChatRegisterFormScreen> {
_isLoading = true;
});

final messenger = ScaffoldMessenger.of(context);
final navigator = Navigator.of(context);
try {
// 首先尝试注册账户
final result = await _authService.register(
Expand All @@ -87,40 +89,40 @@ class _WeChatRegisterFormScreenState extends State<WeChatRegisterFormScreen> {
if (result.success && result.userData != null) {
// 注册成功后绑定微信
final bindResult = await _authService.bindWechat();
if (!context.mounted) return;
if (!mounted) return;

if (bindResult.success) {
// 绑定成功,返回成功结果
Navigator.of(context).pop({
navigator.pop({
'success': true,
'userData': result.userData,
'message': '微信注册成功!',
});
} else {
// 绑定失败但账户已创建,提示用户
ScaffoldMessenger.of(context).showSnackBar(
messenger.showSnackBar(
SnackBar(
content: Text('账户创建成功,但微信绑定失败: ${bindResult.message}'),
backgroundColor: Colors.orange,
),
);

Navigator.of(context).pop({
navigator.pop({
'success': true,
'userData': result.userData,
'message': '账户创建成功,请手动绑定微信',
});
}
} else {
ScaffoldMessenger.of(context).showSnackBar(
messenger.showSnackBar(
SnackBar(
content: Text(result.message ?? '注册失败'),
backgroundColor: Colors.red,
),
);
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
messenger.showSnackBar(
SnackBar(
content: Text('注册过程中发生错误: $e'),
backgroundColor: Colors.red,
Expand Down
8 changes: 8 additions & 0 deletions jive-flutter/lib/screens/dashboard/dashboard_screen.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:jive_money/core/router/app_router.dart';
import 'package:jive_money/ui/components/dashboard/quick_actions.dart';
import 'package:jive_money/ui/components/dashboard/account_overview.dart';
import 'package:jive_money/ui/components/dashboard/recent_transactions.dart';
Expand Down Expand Up @@ -34,6 +36,12 @@ class DashboardScreen extends ConsumerWidget {
],
),
actions: [
// 资产入口(C)
IconButton(
tooltip: '资产',
icon: const Icon(Icons.pie_chart_outline),
onPressed: () => context.go(AppRoutes.userAssets),
),
// 使用新的家庭切换器组件
const Padding(
padding: EdgeInsets.only(right: 8),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,14 @@ class _FamilyActivityLogScreenState

Future<void> _loadStatistics() async {
try {
<<<<<<< HEAD
final statsMap = await _auditService.getActivityStatistics(familyId: widget.familyId);
setState(() => _statistics = _parseActivityStatistics(statsMap));
=======
final stats =
await _auditService.getActivityStatistics(familyId: widget.familyId);
setState(() => _statistics = stats);
>>>>>>> origin/main
} catch (e) {
// 忽略统计加载失败
}
Expand Down
21 changes: 20 additions & 1 deletion jive-flutter/lib/screens/theme_management_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'package:jive_money/models/theme_models.dart' as models;
import 'package:jive_money/services/theme_service.dart';
import 'package:jive_money/widgets/theme_preview_card.dart';
import 'package:jive_money/widgets/theme_share_dialog.dart';
import 'package:jive_money/widgets/custom_theme_editor.dart';

/// 主题管理页面
class ThemeManagementScreen extends StatefulWidget {
Expand Down Expand Up @@ -529,7 +530,11 @@ class _ThemeManagementScreenState extends State<ThemeManagementScreen>
try {
await _themeService.copyThemeToClipboard(theme.id);
if (!mounted) return;
<<<<<<< HEAD
ScaffoldMessenger.of(context).showSnackBar(
=======
messenger.showSnackBar(
>>>>>>> origin/main
const SnackBar(
content: Text('主题已复制到剪贴板'),
backgroundColor: Colors.green,
Expand All @@ -548,8 +553,11 @@ class _ThemeManagementScreenState extends State<ThemeManagementScreen>
Future<void> _deleteTheme(models.CustomThemeData theme) async {
final navigator = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
<<<<<<< HEAD
=======
final navigator = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
>>>>>>> origin/main
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
Expand All @@ -576,7 +584,11 @@ class _ThemeManagementScreenState extends State<ThemeManagementScreen>
try {
await _themeService.deleteCustomTheme(theme.id);
if (!mounted) return;
<<<<<<< HEAD
ScaffoldMessenger.of(context).showSnackBar(
=======
messenger.showSnackBar(
>>>>>>> origin/main
SnackBar(
content: Text('主题"${theme.name}"已删除'),
backgroundColor: Colors.orange,
Expand Down Expand Up @@ -703,8 +715,11 @@ class _ThemeManagementScreenState extends State<ThemeManagementScreen>
Future<void> _resetToDefault() async {
final navigator = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
<<<<<<< HEAD
=======
final navigator = Navigator.of(context);
final messenger = ScaffoldMessenger.of(context);
>>>>>>> origin/main
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
Expand All @@ -730,12 +745,16 @@ class _ThemeManagementScreenState extends State<ThemeManagementScreen>
if (confirmed == true) {
await _themeService.resetToSystemTheme();
if (!mounted) return;
<<<<<<< HEAD
ScaffoldMessenger.of(context).showSnackBar(
=======
messenger.showSnackBar(
>>>>>>> origin/main
const SnackBar(
content: Text('已重置为系统默认主题'),
backgroundColor: Colors.green,
),
);
}
}
}
}
Loading