From 42548d437c34dba813f78f6f360e72d7d0132d0e Mon Sep 17 00:00:00 2001 From: Hyperling Date: Fri, 21 Feb 2025 09:30:46 -0700 Subject: [PATCH] Add ItemType for determining which DB methods should be called based on the screen data. Begin implementing it. --- lib/models/expense.dart | 3 + lib/models/item_type.dart | 22 ++++++ lib/models/tracked_item.dart | 4 + lib/pages/asset.dart | 12 --- lib/pages/home.dart | 13 ++-- lib/pages/income.dart | 12 --- lib/pages/{expense.dart => tracked_item.dart} | 76 ++++++++++++++----- 7 files changed, 92 insertions(+), 50 deletions(-) create mode 100644 lib/models/item_type.dart delete mode 100644 lib/pages/asset.dart delete mode 100644 lib/pages/income.dart rename lib/pages/{expense.dart => tracked_item.dart} (87%) diff --git a/lib/models/expense.dart b/lib/models/expense.dart index a6c090d..3d3c63a 100644 --- a/lib/models/expense.dart +++ b/lib/models/expense.dart @@ -1,4 +1,6 @@ // Local +import 'package:flutter_expense_tracker/models/item_type.dart'; + import '/models/tracked_item.dart'; import '/models/frequency.dart'; @@ -7,6 +9,7 @@ class Expense extends TrackedItem { Expense({ super.id, + super.type = ItemType.expense, required super.name, required super.amount, required super.frequency, diff --git a/lib/models/item_type.dart b/lib/models/item_type.dart new file mode 100644 index 0000000..b13fafd --- /dev/null +++ b/lib/models/item_type.dart @@ -0,0 +1,22 @@ +enum ItemType { + expense( + title: "Expense", + plural: "Expenses", + ), + income( + title: "Income", + plural: "Incomes", + ), + asset( + title: "Asset", + plural: "Assets", + ); + + const ItemType({ + required this.title, + required this.plural, + }); + + final String title; + final String plural; +} diff --git a/lib/models/tracked_item.dart b/lib/models/tracked_item.dart index bea8c00..53a4585 100644 --- a/lib/models/tracked_item.dart +++ b/lib/models/tracked_item.dart @@ -1,8 +1,11 @@ // Local +import 'package:flutter_expense_tracker/models/item_type.dart'; + import '/models/frequency.dart'; abstract class TrackedItem { int? id; + ItemType? type; String name; double amount; Frequency? frequency; @@ -10,6 +13,7 @@ abstract class TrackedItem { TrackedItem({ this.id, + this.type, required this.name, required this.amount, this.frequency, diff --git a/lib/pages/asset.dart b/lib/pages/asset.dart deleted file mode 100644 index b54b60b..0000000 --- a/lib/pages/asset.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class AssetPage extends StatelessWidget { - const AssetPage({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Placeholder(); - } -} diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 06cbba8..a243a14 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,14 +1,14 @@ // Flutter import 'package:flutter/material.dart'; +import 'package:flutter_expense_tracker/models/item_type.dart'; import 'dart:io'; // Local -import '/pages/expense.dart'; -import '/pages/income.dart'; -import '/pages/asset.dart'; +import '/pages/tracked_item.dart'; import '/pages/report.dart'; import '/pages/settings.dart'; import '/pages/help.dart'; +import '/db.dart'; class HomePage extends StatefulWidget { const HomePage({ @@ -32,14 +32,15 @@ class _HomePageState extends State { Widget? dialog; switch (pageSelected) { case 0: - page = ExpensePage(); + page = TrackedItemPage(assetsToLoad: DatabaseHelper.instance.getExpenses()); dialog = TrackedItemInputDialog( notifyParent: refresh, + type: ItemType.expense, ); case 1: - page = IncomePage(); + page = Placeholder(); case 2: - page = AssetPage(); + page = Placeholder(); case 3: page = ProjectionPage(); case 4: diff --git a/lib/pages/income.dart b/lib/pages/income.dart deleted file mode 100644 index 18e00bc..0000000 --- a/lib/pages/income.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class IncomePage extends StatelessWidget { - const IncomePage({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Placeholder(); - } -} diff --git a/lib/pages/expense.dart b/lib/pages/tracked_item.dart similarity index 87% rename from lib/pages/expense.dart rename to lib/pages/tracked_item.dart index 32b2816..85f7894 100644 --- a/lib/pages/expense.dart +++ b/lib/pages/tracked_item.dart @@ -1,24 +1,30 @@ // Flutter import 'package:flutter/material.dart'; +import 'package:flutter_expense_tracker/models/asset.dart'; +import 'package:flutter_expense_tracker/models/income.dart'; // Local import '/models/tracked_item.dart'; +import '/models/item_type.dart'; import '/models/expense.dart'; import '/models/frequency.dart'; import '/db.dart'; // TODO: Make this a generic UI based on a superclass of Expense, Income, and Assets. -class ExpensePage extends StatefulWidget { - const ExpensePage({ +class TrackedItemPage extends StatefulWidget { + final Future> assetsToLoad; + + const TrackedItemPage({ super.key, + required this.assetsToLoad, }); @override - State createState() => _ExpensePageState(); + State createState() => _TrackedItemPageState(); } -class _ExpensePageState extends State { +class _TrackedItemPageState extends State { refresh() { setState(() {}); } @@ -28,9 +34,9 @@ class _ExpensePageState extends State { final theme = Theme.of(context); return FutureBuilder>( - future: DatabaseHelper.instance.getExpenses(), - builder: (BuildContext context, - AsyncSnapshot> snapshot) { + future: widget.assetsToLoad, + builder: + (BuildContext context, AsyncSnapshot> snapshot) { if (!snapshot.hasData) { return Text('Loading...'); } @@ -41,13 +47,12 @@ class _ExpensePageState extends State { ); return snapshot.data!.isEmpty ? Text( - "Add expenses to get started.", + "Add items to get started.", softWrap: true, ) : ListView.builder( itemCount: snapshot.data!.length, itemBuilder: (_, index) { - //List expenses = snapshot.data!; final TrackedItem curr = snapshot.data![index]; final itemKey = Key(curr.id!.toString()); @@ -139,7 +144,17 @@ class _ExpensePageState extends State { snapshot.data!.remove(curr); switch (direction) { case DismissDirection.startToEnd: - DatabaseHelper.instance.removeExpense(curr.id!); + if (curr is Expense) { + DatabaseHelper.instance + .removeExpense(curr.id!); + } else if (curr is Income) { + // TODO + } else if (curr is Asset) { + // TODO + } else { + throw UnimplementedError( + "Cannot remove unimplemented item type."); + } break; case DismissDirection.endToStart: // Open an edit dialog, then remove the item from the list. @@ -150,6 +165,7 @@ class _ExpensePageState extends State { notifyParent: refresh, entry: curr, amountText: curr.getAmountText(), + type: curr.type!, ), ), ); @@ -230,22 +246,22 @@ class TrackedItemInputDialog extends StatefulWidget { final Function() notifyParent; final TrackedItem? entry; final String? amountText; + final ItemType? type; const TrackedItemInputDialog({ super.key, required this.notifyParent, this.entry, this.amountText, + this.type, }); @override - State createState() => - _TrackedItemInputDialogState(); + State createState() => _TrackedItemInputDialogState(); } -class _TrackedItemInputDialogState - extends State { - final _expenseFormKey = GlobalKey(); +class _TrackedItemInputDialogState extends State { + final _formKey = GlobalKey(); int? _id; String _name = ""; @@ -255,12 +271,19 @@ class _TrackedItemInputDialogState @override Widget build(BuildContext context) { + if (widget.type == null && + (widget.entry != null && widget.entry!.type == null)) { + throw FlutterError("No ItemType provided for TrackedItemInputDialog."); + } + ItemType? _type = widget.type; + if (widget.entry != null) { _id = widget.entry!.id; _name = widget.entry!.name; _amount = widget.entry!.amount; widget.entry!.frequency == null ? null : _freq = widget.entry!.frequency!; _desc = widget.entry!.description; + _type = widget.entry!.type!; } String amountText = @@ -276,7 +299,20 @@ class _TrackedItemInputDialogState onPressed: () { if (widget.entry != null) { setState(() { - DatabaseHelper.instance.addExpense(widget.entry!); + switch (_type) { + case ItemType.expense: + DatabaseHelper.instance.addExpense(widget.entry!); + break; + case ItemType.income: + // TODO + break; + case ItemType.asset: + // TODO + break; + default: + throw UnimplementedError( + "Cannot add unimplemented type."); + } widget.notifyParent(); }); } @@ -289,7 +325,7 @@ class _TrackedItemInputDialogState insetPadding: EdgeInsets.all(0), title: Center( child: widget.entry == null - ? Text("New Expense") + ? Text("New ${_type!.title}") : Text("Edit Expense"), ), content: FutureBuilder>( @@ -301,7 +337,7 @@ class _TrackedItemInputDialogState } List expenses = snapshot.data!; return Form( - key: _expenseFormKey, + key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -424,8 +460,8 @@ class _TrackedItemInputDialogState Center( child: ElevatedButton.icon( onPressed: () { - if (_expenseFormKey.currentState!.validate()) { - _expenseFormKey.currentState!.save(); + if (_formKey.currentState!.validate()) { + _formKey.currentState!.save(); setState(() { Expense expense = Expense( id: _id,