ing connected, makign some features
This commit is contained in:
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user