Files
kmobile/lib/features/accounts/screens/account_statement_screen.dart

356 lines
12 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:material_symbols_icons/material_symbols_icons.dart';
import 'package:shimmer/shimmer.dart';
import 'package:kmobile/data/models/transaction.dart';
import 'package:kmobile/data/repositories/transaction_repository.dart';
import 'package:kmobile/di/injection.dart';
import '../../../l10n/app_localizations.dart';
import 'transaction_details_screen.dart';
class AccountStatementScreen extends StatefulWidget {
final String accountNo;
final String balance;
const AccountStatementScreen({
super.key,
required this.accountNo,
required this.balance,
});
@override
State<AccountStatementScreen> createState() => _AccountStatementScreen();
}
class _AccountStatementScreen extends State<AccountStatementScreen> {
DateTime? fromDate;
DateTime? toDate;
bool _txLoading = true;
List<Transaction> _transactions = [];
final _minAmountController = TextEditingController();
final _maxAmountController = TextEditingController();
@override
void initState() {
super.initState();
_loadTransactions();
}
Future<void> _loadTransactions() async {
setState(() {
_txLoading = true;
_transactions = [];
});
try {
final repo = getIt<TransactionRepository>();
final txs = await repo.fetchTransactions(
widget.accountNo,
fromDate: fromDate,
toDate: toDate,
);
setState(() => _transactions = txs);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'${AppLocalizations.of(context).failedToLoadTransactions} $e',
),
),
);
} finally {
setState(() => _txLoading = false);
}
}
Future<void> _selectFromDate(BuildContext context) async {
final now = DateTime.now();
final picked = await showDatePicker(
context: context,
initialDate: fromDate ?? now,
firstDate: DateTime(2020),
lastDate: now,
);
if (picked != null) {
setState(() {
fromDate = picked;
toDate = null;
});
}
}
Future<void> _selectToDate(BuildContext context) async {
if (fromDate == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context).pleaseSelectDateFirst),
),
);
return;
}
final now = DateTime.now();
final maxToDate = fromDate!.add(const Duration(days: 31)).isBefore(now)
? fromDate!.add(const Duration(days: 31))
: now;
final picked = await showDatePicker(
context: context,
initialDate: toDate ?? fromDate!,
firstDate: fromDate!,
lastDate: maxToDate,
);
if (picked != null) {
setState(() => toDate = picked);
}
}
String _formatDate(DateTime date) {
return "${date.day.toString().padLeft(2, '0')}-"
"${date.month.toString().padLeft(2, '0')}-"
"${date.year}";
}
@override
void dispose() {
_minAmountController.dispose();
_maxAmountController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: const Icon(Symbols.arrow_back_ios_new),
onPressed: () => Navigator.pop(context),
),
title: Text(
AppLocalizations.of(context).accountStatement,
style:
const TextStyle(color: Colors.black, fontWeight: FontWeight.w500),
),
centerTitle: false,
actions: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: CircleAvatar(
backgroundColor: Colors.grey[200],
radius: 20,
child: SvgPicture.asset(
'assets/images/avatar_male.svg',
width: 40,
height: 40,
fit: BoxFit.cover,
),
),
),
],
),
body: Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
"${AppLocalizations.of(context).accountNumber}: ",
style: const TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
),
),
Text(widget.accountNo, style: const TextStyle(fontSize: 17)),
],
),
const SizedBox(height: 15),
Row(
children: [
Text(
"${AppLocalizations.of(context).availableBalance}: ",
style: const TextStyle(
fontSize: 17,
),
),
Text('${widget.balance}',
style: const TextStyle(fontSize: 17)),
],
),
const SizedBox(height: 15),
Text(
AppLocalizations.of(context).filters,
style: const TextStyle(fontSize: 17),
),
const SizedBox(height: 15),
Row(
children: [
Expanded(
child: GestureDetector(
onTap: () => _selectFromDate(context),
child: buildDateBox(
AppLocalizations.of(context).fromDate,
fromDate,
),
),
),
const SizedBox(width: 10),
Expanded(
child: GestureDetector(
onTap: () => _selectToDate(context),
child: buildDateBox(
AppLocalizations.of(context).toDate,
toDate,
),
),
),
],
),
const SizedBox(height: 20),
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: _loadTransactions,
style: ElevatedButton.styleFrom(
backgroundColor: Theme.of(context).primaryColor,
padding: const EdgeInsets.symmetric(vertical: 16),
),
child: Text(
AppLocalizations.of(context).search,
style: TextStyle(
color: Theme.of(context).scaffoldBackgroundColor,
fontSize: 16,
),
),
),
),
const SizedBox(height: 35),
if (!_txLoading &&
_transactions.isNotEmpty &&
fromDate == null &&
toDate == null)
Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Text(
AppLocalizations.of(context).lastTenTransactions,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.grey[700],
),
),
),
Expanded(
child: _txLoading
? ListView.builder(
itemCount: 3,
itemBuilder: (_, __) => ListTile(
leading: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: CircleAvatar(
radius: 12,
backgroundColor:
Theme.of(context).scaffoldBackgroundColor,
),
),
title: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 10,
width: 100,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
subtitle: Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 8,
width: 60,
color: Theme.of(context).scaffoldBackgroundColor,
),
),
),
)
: _transactions.isEmpty
? Center(
child: Text(
AppLocalizations.of(context).noTransactions,
style: TextStyle(
fontSize: 16, color: Colors.grey[600]),
),
)
: ListView.separated(
itemCount: _transactions.length,
itemBuilder: (context, index) {
final tx = _transactions[index];
return ListTile(
leading: Icon(
tx.type == 'CR'
? Symbols.call_received
: Symbols.call_made,
color:
tx.type == 'CR' ? Colors.green : Colors.red,
),
title: Text(
tx.date ?? '',
style: const TextStyle(fontSize: 15),
),
subtitle: Text(
tx.name != null
? (tx.name!.length > 18
? tx.name!.substring(0, 22)
: tx.name!)
: '',
style: const TextStyle(fontSize: 12),
),
trailing: Text(
"${tx.amount}",
style: const TextStyle(fontSize: 17),
),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => TransactionDetailsScreen(
transaction: tx),
),
);
},
);
},
separatorBuilder: (context, index) {
return const Divider();
},
),
),
],
),
),
);
}
Widget buildDateBox(String label, DateTime? date) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 16),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
date != null ? _formatDate(date) : label,
style: TextStyle(
fontSize: 16,
color: date != null ? Colors.black : Colors.grey,
),
),
const Icon(Icons.arrow_drop_down),
],
),
);
}
}