Files
ingtrakapp/lib/screens/balance_screen.dart
T
2026-09-06 21:13:30 +02:00

173 lines
5.1 KiB
Dart

/// 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<BankController>();
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'),
),
],
);
}
}