/// Balance tab: connect bank CTA, current balance, and refresh. library; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/bank_provider.dart'; import '../utils/connect_bank.dart'; import '../utils/helpers.dart'; import '../widgets/empty_state.dart'; import '../widgets/loading_view.dart'; import '../widgets/responsive_body.dart'; class BalanceScreen extends StatelessWidget { const BalanceScreen({super.key}); @override Widget build(BuildContext context) { final bank = context.watch(); return Scaffold( appBar: AppBar( title: const Text('Balance'), actions: [ IconButton( tooltip: 'Refresh', onPressed: bank.isLoading ? null : bank.refresh, icon: const Icon(Icons.refresh), ), ], ), body: RefreshIndicator( onRefresh: bank.refresh, child: ResponsiveBody( child: _BalanceBody(bank: bank), ), ), ); } } class _BalanceBody extends StatelessWidget { const _BalanceBody({required this.bank}); final BankController bank; @override Widget build(BuildContext context) { if (bank.isLoading && bank.balance == null && !bank.isLinked) { return const LoadingView(message: 'Checking bank link…'); } if (!bank.isLinked) { return ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ EmptyState( icon: Icons.account_balance_outlined, title: 'Bank not linked', message: bank.error ?? 'Connect opens a sandbox bank login in-app. ' 'Stay in that WebView until it returns here. ' 'You should then see a balance and transactions.', actionLabel: bank.isConnecting ? 'Opening…' : 'Connect bank', onAction: () => runConnectBank(context, bank), ), ], ); } if (bank.error != null && bank.balance == null) { return ListView( physics: const AlwaysScrollableScrollPhysics(), children: [ EmptyState( icon: Icons.error_outline, title: 'Could not load balance', message: bank.error!, actionLabel: 'Retry', onAction: bank.refresh, ), ], ); } final balance = bank.balance; final theme = Theme.of(context); final colors = theme.colorScheme; return ListView( physics: const AlwaysScrollableScrollPhysics(), padding: const EdgeInsets.only(top: 8, bottom: 32), children: [ Container( width: double.infinity, padding: const EdgeInsets.all(24), decoration: BoxDecoration( borderRadius: BorderRadius.circular(20), gradient: LinearGradient( colors: [colors.primary, colors.tertiary], ), boxShadow: [ BoxShadow( color: colors.primary.withValues(alpha: 0.28), blurRadius: 20, offset: const Offset(0, 10), ), ], ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Current balance', style: theme.textTheme.labelMedium?.copyWith( color: colors.onPrimary.withValues(alpha: 0.8), ), ), const SizedBox(height: 8), Text( balance == null ? '—' : formatMoney(balance.amount, balance.currency), style: theme.textTheme.headlineMedium?.copyWith( color: colors.onPrimary, ), ), if (balance?.balanceType != null) ...[ const SizedBox(height: 8), Text( balance!.balanceType!, style: theme.textTheme.labelMedium?.copyWith( color: colors.onPrimary.withValues(alpha: 0.75), ), ), ], ], ), ), const SizedBox(height: 20), ListTile( contentPadding: EdgeInsets.zero, leading: Icon( Icons.check_circle_outline, color: colors.primary, ), title: const Text('Linked'), subtitle: Text( bank.status.accountId == null ? bank.status.status : 'Account ${bank.status.accountId}', ), ), if (bank.error != null) ...[ const SizedBox(height: 8), Text( bank.error!, style: theme.textTheme.bodyMedium?.copyWith(color: colors.error), ), ], const SizedBox(height: 12), OutlinedButton.icon( onPressed: bank.isConnecting ? null : () => runConnectBank(context, bank), icon: const Icon(Icons.link), label: Text(bank.isConnecting ? 'Opening…' : 'Reconnect bank'), ), ], ); } }