ing connected, makign some features

This commit is contained in:
davmar
2026-09-07 00:20:58 +02:00
parent 87afe271b7
commit 21df7b0085
15 changed files with 1197 additions and 322 deletions
+48 -34
View File
@@ -31,7 +31,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
DateTime _month = monthStart(DateTime.now());
DateTime? _selectedDay;
var _searchInMonthOnly = false;
final Set<TxCategory> _categoryFilters = {};
final Set<String> _labelFilters = {};
@override
void dispose() {
@@ -40,7 +40,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
}
bool get _isSearching =>
_query.trim().isNotEmpty || _categoryFilters.isNotEmpty;
_query.trim().isNotEmpty || _labelFilters.isNotEmpty;
void _goMonth(int delta) {
final next = addMonths(_month, delta);
@@ -67,15 +67,15 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
return isSameMonth(date, _month);
}
Future<void> _pickCategories(List<TxCategory> available) async {
Future<void> _pickLabels(List<TxLabel> available) async {
if (available.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No categories in use yet')),
const SnackBar(content: Text('No labels in use yet')),
);
return;
}
final selected = Set<TxCategory>.of(_categoryFilters);
final selected = Set<String>.of(_labelFilters);
await showModalBottomSheet<void>(
context: context,
showDragHandle: true,
@@ -90,7 +90,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'Filter by category',
'Filter by label',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 12),
@@ -98,21 +98,21 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
spacing: 8,
runSpacing: 8,
children: [
for (final category in available)
for (final label in available)
FilterChip(
avatar: Icon(
category.icon,
label.icon,
size: 16,
color: category.color,
color: label.color,
),
label: Text(category.label),
selected: selected.contains(category),
label: Text(label.name),
selected: selected.contains(label.id),
onSelected: (value) {
setSheetState(() {
if (value) {
selected.add(category);
selected.add(label.id);
} else {
selected.remove(category);
selected.remove(label.id);
}
});
},
@@ -123,7 +123,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
FilledButton(
onPressed: () {
setState(() {
_categoryFilters
_labelFilters
..clear()
..addAll(selected);
});
@@ -241,7 +241,7 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
final filtered = metaController.search(
searchPool,
_query,
categories: _categoryFilters,
labelIds: _labelFilters,
);
var moneyOut = 0.0;
@@ -257,7 +257,10 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
(bank.transactions.isNotEmpty
? bank.transactions.first.currency
: 'EUR');
final usedCategories = metaController.usedCategories(bank.transactions);
final usedLabels = metaController.usedLabels(bank.transactions);
final labelById = {
for (final label in metaController.allLabels) label.id: label,
};
return ListView(
physics: const AlwaysScrollableScrollPhysics(),
@@ -274,13 +277,11 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
decoration: InputDecoration(
hintText: 'Search description, notes…',
prefixIcon: IconButton(
tooltip: 'Categories',
onPressed: () => _pickCategories(usedCategories),
tooltip: 'Labels',
onPressed: () => _pickLabels(usedLabels),
icon: Icon(
Icons.search,
color: _categoryFilters.isEmpty
? null
: colors.primary,
color: _labelFilters.isEmpty ? null : colors.primary,
),
),
suffixIcon: _query.isEmpty
@@ -316,21 +317,26 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
],
),
),
if (_categoryFilters.isNotEmpty)
if (_labelFilters.isNotEmpty)
Padding(
padding: const EdgeInsets.only(bottom: 8),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
for (final category in _categoryFilters)
InputChip(
avatar: Icon(category.icon, size: 16, color: category.color),
label: Text(category.label),
onDeleted: () {
setState(() => _categoryFilters.remove(category));
},
),
for (final id in _labelFilters)
if (labelById[id] != null)
InputChip(
avatar: Icon(
labelById[id]!.icon,
size: 16,
color: labelById[id]!.color,
),
label: Text(labelById[id]!.name),
onDeleted: () {
setState(() => _labelFilters.remove(id));
},
),
],
),
),
@@ -411,6 +417,8 @@ class _TransactionsScreenState extends State<TransactionsScreen> {
_CompactTransactionTile(
tx: filtered[i],
meta: metaController.metaFor(filtered[i]),
labels: metaController
.labelsFor(metaController.metaFor(filtered[i])),
),
],
],
@@ -467,16 +475,21 @@ class _CompactTransactionTile extends StatelessWidget {
const _CompactTransactionTile({
required this.tx,
required this.meta,
this.labels = const [],
});
final BankTransaction tx;
final TxMeta meta;
final List<TxLabel> labels;
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colors = theme.colorScheme;
final amountColor = tx.amount >= 0 ? colors.primary : colors.error;
final primary = labels.isEmpty ? null : labels.first;
final iconColor = primary?.color ?? colors.outline;
final iconData = primary?.icon ?? Icons.receipt_long_outlined;
return InkWell(
onTap: () {
@@ -494,13 +507,13 @@ class _CompactTransactionTile extends StatelessWidget {
width: 36,
height: 36,
decoration: BoxDecoration(
color: meta.category.color.withValues(alpha: 0.14),
color: iconColor.withValues(alpha: 0.14),
borderRadius: BorderRadius.circular(10),
),
child: Icon(
meta.category.icon,
iconData,
size: 18,
color: meta.category.color,
color: iconColor,
),
),
const SizedBox(width: 12),
@@ -521,7 +534,8 @@ class _CompactTransactionTile extends StatelessWidget {
Text(
[
formatIsoDate(tx.date),
if (meta.hasCategory) meta.category.label,
if (labels.isNotEmpty)
labels.map((l) => l.name).join(', '),
if (meta.recurring) 'Recurring',
if (meta.hasNotes) 'Note',
if (tx.pending) 'Pending',