Files
monisuo/flutter_monisuo/lib/ui/pages/asset/asset_page.dart
2026-04-06 09:31:14 +08:00

151 lines
5.0 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../../core/theme/app_spacing.dart';
import '../../../core/theme/app_theme.dart';
import '../../../core/event/app_event_bus.dart';
import '../../../providers/asset_provider.dart';
import 'components/action_buttons_row.dart';
import 'components/asset_dialogs.dart';
import 'components/balance_card.dart';
import 'components/holdings_section.dart';
import 'components/records_link_row.dart';
import '../orders/fund_orders_page.dart';
import 'transfer_page.dart';
/// 资产页面 - Matching .pen design spec (CMcqs)
class AssetPage extends StatefulWidget {
const AssetPage({super.key});
@override
State<AssetPage> createState() => _AssetPageState();
}
class _AssetPageState extends State<AssetPage> with AutomaticKeepAliveClientMixin {
StreamSubscription<AppEvent>? _eventSub;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_loadData();
_listenEvents();
});
}
@override
void dispose() {
_eventSub?.cancel();
super.dispose();
}
void _listenEvents() {
final eventBus = context.read<AppEventBus>();
_eventSub = eventBus.on(AppEventType.assetChanged, (_) {
if (mounted) {
context.read<AssetProvider>().refreshAll(force: true);
}
});
}
void _loadData() {
context.read<AssetProvider>().refreshAll(force: true);
}
@override
Widget build(BuildContext context) {
super.build(context);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
backgroundColor: colorScheme.background,
body: Consumer<AssetProvider>(
builder: (context, provider, _) {
return RefreshIndicator(
onRefresh: () => provider.refreshAll(force: true),
color: colorScheme.primary,
backgroundColor: colorScheme.surfaceContainerHighest,
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.fromLTRB(AppSpacing.md, AppSpacing.md + 8, AppSpacing.md, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Page title
Padding(
padding: const EdgeInsets.only(top: 16, bottom: 8),
child: Text(
'资产',
style: AppTextStyles.displaySmall(context),
),
),
const SizedBox(height: AppSpacing.sm),
// 资金账户 + 交易账户 左右并排
Row(
children: [
Expanded(
child: BalanceCard(
provider: provider,
label: '资金账户',
balance: provider.fundAccount?.balance ?? provider.overview?.fundBalance ?? '0.00',
),
),
const SizedBox(width: AppSpacing.sm),
Expanded(
child: BalanceCard(
provider: provider,
label: '交易账户',
balance: _calculateTradeTotal(provider),
),
),
],
),
const SizedBox(height: AppSpacing.md),
// Action buttons row — matching .pen pIpHe (gap 12)
ActionButtonsRow(
onDeposit: () => showDepositDialog(context),
onWithdraw: () => showWithdrawDialog(context, provider.fundAccount?.balance),
onTransfer: () => _navigateToTransfer(context),
),
const SizedBox(height: AppSpacing.md),
// Records link row — matching .pen fLHtq (cornerRadius lg, padding [14,16], stroke)
RecordsLinkRow(
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const FundOrdersPage()),
),
),
const SizedBox(height: AppSpacing.md),
// Holdings section
HoldingsSection(holdings: provider.holdings),
],
),
),
);
},
),
);
}
String _calculateTradeTotal(AssetProvider provider) {
double total = 0;
for (var h in provider.holdings) {
total += double.tryParse(h.currentValue?.toString() ?? '0') ?? 0;
}
return total.toStringAsFixed(2);
}
void _navigateToTransfer(BuildContext context) async {
final result = await Navigator.push<bool>(
context,
MaterialPageRoute(builder: (_) => const TransferPage()),
);
if (result == true && context.mounted) {
context.read<AssetProvider>().refreshAll(force: true);
}
}
}