ok for now
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
/// Transaction detail: category, recurring, and user notes.
|
||||
library;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../models/bank.dart';
|
||||
import '../models/transaction_meta.dart';
|
||||
import '../providers/tx_meta_provider.dart';
|
||||
import '../utils/helpers.dart';
|
||||
import '../widgets/responsive_body.dart';
|
||||
|
||||
class TransactionDetailScreen extends StatefulWidget {
|
||||
const TransactionDetailScreen({super.key, required this.tx});
|
||||
|
||||
final BankTransaction tx;
|
||||
|
||||
@override
|
||||
State<TransactionDetailScreen> createState() =>
|
||||
_TransactionDetailScreenState();
|
||||
}
|
||||
|
||||
class _TransactionDetailScreenState extends State<TransactionDetailScreen> {
|
||||
late final TextEditingController _notesController;
|
||||
var _dirty = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final notes = context.read<TxMetaController>().metaFor(widget.tx).notes;
|
||||
_notesController = TextEditingController(text: notes);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_notesController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _saveNotes() async {
|
||||
await context.read<TxMetaController>().setNotes(
|
||||
widget.tx,
|
||||
_notesController.text,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() => _dirty = false);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Notes saved')),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final tx = widget.tx;
|
||||
final meta = context.watch<TxMetaController>().metaFor(tx);
|
||||
final theme = Theme.of(context);
|
||||
final colors = theme.colorScheme;
|
||||
final amountColor = tx.amount >= 0 ? colors.primary : colors.error;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('Transaction'),
|
||||
actions: [
|
||||
if (_dirty)
|
||||
TextButton(
|
||||
onPressed: _saveNotes,
|
||||
child: const Text('Save'),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ResponsiveBody(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 32),
|
||||
children: [
|
||||
Text(
|
||||
formatMoney(tx.amount, tx.currency),
|
||||
style: theme.textTheme.headlineMedium?.copyWith(color: amountColor),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(tx.description, style: theme.textTheme.titleMedium),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
[
|
||||
formatIsoDate(tx.date),
|
||||
if (tx.pending) 'Pending',
|
||||
].join(' · '),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SwitchListTile(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
secondary: const Icon(Icons.autorenew),
|
||||
title: const Text('Recurring'),
|
||||
subtitle: const Text('Salary, mortgage, subscriptions…'),
|
||||
value: meta.recurring,
|
||||
onChanged: (value) {
|
||||
context.read<TxMetaController>().setRecurring(tx, value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text('Category', style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8,
|
||||
runSpacing: 8,
|
||||
children: [
|
||||
for (final category in TxCategory.values)
|
||||
ChoiceChip(
|
||||
avatar: Icon(
|
||||
category.icon,
|
||||
size: 16,
|
||||
color: category.color,
|
||||
),
|
||||
label: Text(category.label),
|
||||
selected: meta.category == category,
|
||||
onSelected: (_) {
|
||||
context.read<TxMetaController>().setCategory(tx, category);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Text('Notes', style: theme.textTheme.titleSmall),
|
||||
const SizedBox(height: 8),
|
||||
TextField(
|
||||
controller: _notesController,
|
||||
minLines: 3,
|
||||
maxLines: 6,
|
||||
textInputAction: TextInputAction.newline,
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Add a private note…',
|
||||
),
|
||||
onChanged: (_) {
|
||||
if (!_dirty) setState(() => _dirty = true);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton(
|
||||
onPressed: _dirty ? _saveNotes : null,
|
||||
child: const Text('Save notes'),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'Matching descriptions reuse this category automatically. '
|
||||
'Exact same amount + description also copies notes.',
|
||||
style: theme.textTheme.labelMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user