app working with sandbox
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
/// Transactions tab: recent bank movements from the ingtrak API.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/bank.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 TransactionsScreen extends StatelessWidget {
|
||||
const TransactionsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bank = context.watch<BankController>();
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Transactions'),
|
||||
actions: [
|
||||
IconButton(
|
||||
tooltip: 'Refresh',
|
||||
onPressed: bank.isLoading ? null : bank.refresh,
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: RefreshIndicator(
|
||||
onRefresh: bank.refresh,
|
||||
child: ResponsiveBody(
|
||||
child: _TransactionsBody(bank: bank),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TransactionsBody extends StatelessWidget {
|
||||
const _TransactionsBody({required this.bank});
|
||||
|
||||
final BankController bank;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (bank.isLoading && bank.transactions.isEmpty) {
|
||||
return const LoadingView(message: 'Loading transactions…');
|
||||
}
|
||||
|
||||
if (!bank.isLinked) {
|
||||
return ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
EmptyState(
|
||||
icon: Icons.receipt_long_outlined,
|
||||
title: 'No transactions yet',
|
||||
message: bank.error ??
|
||||
'Link your bank on the Balance tab to load recent activity.',
|
||||
actionLabel: bank.isConnecting ? 'Opening…' : 'Connect bank',
|
||||
onAction: () => runConnectBank(context, bank),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (bank.error != null && bank.transactions.isEmpty) {
|
||||
return ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: [
|
||||
EmptyState(
|
||||
icon: Icons.error_outline,
|
||||
title: 'Could not load transactions',
|
||||
message: bank.error!,
|
||||
actionLabel: 'Retry',
|
||||
onAction: bank.refresh,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
if (bank.transactions.isEmpty) {
|
||||
return ListView(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
children: const [
|
||||
EmptyState(
|
||||
icon: Icons.inbox_outlined,
|
||||
title: 'No recent activity',
|
||||
message: 'The backend returned an empty transaction list.',
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.separated(
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 32),
|
||||
itemCount: bank.transactions.length,
|
||||
separatorBuilder: (_, _) => const SizedBox(height: 8),
|
||||
itemBuilder: (context, index) {
|
||||
return _TransactionTile(tx: bank.transactions[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TransactionTile extends StatelessWidget {
|
||||
const _TransactionTile({required this.tx});
|
||||
|
||||
final BankTransaction tx;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
final isCredit = tx.amount >= 0;
|
||||
final amountColor = isCredit ? colors.primary : colors.error;
|
||||
|
||||
return Card(
|
||||
child: ListTile(
|
||||
title: Text(
|
||||
tx.description,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
[
|
||||
formatIsoDate(tx.date),
|
||||
if (tx.pending) 'Pending',
|
||||
].where((part) => part.isNotEmpty).join(' · '),
|
||||
),
|
||||
trailing: Text(
|
||||
formatMoney(tx.amount, tx.currency),
|
||||
style: theme.textTheme.titleMedium?.copyWith(color: amountColor),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user