app working with sandbox
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
/// 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'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user