Add ItemType for determining which DB methods should be called based on the screen data. Begin implementing it.

This commit is contained in:
Hyperling 2025-02-21 09:30:46 -07:00
parent be66f52cbf
commit 42548d437c
7 changed files with 92 additions and 50 deletions

View File

@ -1,4 +1,6 @@
// Local // Local
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/tracked_item.dart'; import '/models/tracked_item.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
@ -7,6 +9,7 @@ class Expense extends TrackedItem {
Expense({ Expense({
super.id, super.id,
super.type = ItemType.expense,
required super.name, required super.name,
required super.amount, required super.amount,
required super.frequency, required super.frequency,

22
lib/models/item_type.dart Normal file
View File

@ -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;
}

View File

@ -1,8 +1,11 @@
// Local // Local
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
abstract class TrackedItem { abstract class TrackedItem {
int? id; int? id;
ItemType? type;
String name; String name;
double amount; double amount;
Frequency? frequency; Frequency? frequency;
@ -10,6 +13,7 @@ abstract class TrackedItem {
TrackedItem({ TrackedItem({
this.id, this.id,
this.type,
required this.name, required this.name,
required this.amount, required this.amount,
this.frequency, this.frequency,

View File

@ -1,12 +0,0 @@
import 'package:flutter/material.dart';
class AssetPage extends StatelessWidget {
const AssetPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

View File

@ -1,14 +1,14 @@
// Flutter // Flutter
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_expense_tracker/models/item_type.dart';
import 'dart:io'; import 'dart:io';
// Local // Local
import '/pages/expense.dart'; import '/pages/tracked_item.dart';
import '/pages/income.dart';
import '/pages/asset.dart';
import '/pages/report.dart'; import '/pages/report.dart';
import '/pages/settings.dart'; import '/pages/settings.dart';
import '/pages/help.dart'; import '/pages/help.dart';
import '/db.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({ const HomePage({
@ -32,14 +32,15 @@ class _HomePageState extends State<HomePage> {
Widget? dialog; Widget? dialog;
switch (pageSelected) { switch (pageSelected) {
case 0: case 0:
page = ExpensePage(); page = TrackedItemPage(assetsToLoad: DatabaseHelper.instance.getExpenses());
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
notifyParent: refresh, notifyParent: refresh,
type: ItemType.expense,
); );
case 1: case 1:
page = IncomePage(); page = Placeholder();
case 2: case 2:
page = AssetPage(); page = Placeholder();
case 3: case 3:
page = ProjectionPage(); page = ProjectionPage();
case 4: case 4:

View File

@ -1,12 +0,0 @@
import 'package:flutter/material.dart';
class IncomePage extends StatelessWidget {
const IncomePage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

View File

@ -1,24 +1,30 @@
// Flutter // Flutter
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_expense_tracker/models/asset.dart';
import 'package:flutter_expense_tracker/models/income.dart';
// Local // Local
import '/models/tracked_item.dart'; import '/models/tracked_item.dart';
import '/models/item_type.dart';
import '/models/expense.dart'; import '/models/expense.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
import '/db.dart'; import '/db.dart';
// TODO: Make this a generic UI based on a superclass of Expense, Income, and Assets. // TODO: Make this a generic UI based on a superclass of Expense, Income, and Assets.
class ExpensePage extends StatefulWidget { class TrackedItemPage extends StatefulWidget {
const ExpensePage({ final Future<List<TrackedItem>> assetsToLoad;
const TrackedItemPage({
super.key, super.key,
required this.assetsToLoad,
}); });
@override @override
State<ExpensePage> createState() => _ExpensePageState(); State<TrackedItemPage> createState() => _TrackedItemPageState();
} }
class _ExpensePageState extends State<ExpensePage> { class _TrackedItemPageState extends State<TrackedItemPage> {
refresh() { refresh() {
setState(() {}); setState(() {});
} }
@ -28,9 +34,9 @@ class _ExpensePageState extends State<ExpensePage> {
final theme = Theme.of(context); final theme = Theme.of(context);
return FutureBuilder<List<TrackedItem>>( return FutureBuilder<List<TrackedItem>>(
future: DatabaseHelper.instance.getExpenses(), future: widget.assetsToLoad,
builder: (BuildContext context, builder:
AsyncSnapshot<List<TrackedItem>> snapshot) { (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Text('Loading...'); return Text('Loading...');
} }
@ -41,13 +47,12 @@ class _ExpensePageState extends State<ExpensePage> {
); );
return snapshot.data!.isEmpty return snapshot.data!.isEmpty
? Text( ? Text(
"Add expenses to get started.", "Add items to get started.",
softWrap: true, softWrap: true,
) )
: ListView.builder( : ListView.builder(
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
itemBuilder: (_, index) { itemBuilder: (_, index) {
//List<Expense> expenses = snapshot.data!;
final TrackedItem curr = snapshot.data![index]; final TrackedItem curr = snapshot.data![index];
final itemKey = Key(curr.id!.toString()); final itemKey = Key(curr.id!.toString());
@ -139,7 +144,17 @@ class _ExpensePageState extends State<ExpensePage> {
snapshot.data!.remove(curr); snapshot.data!.remove(curr);
switch (direction) { switch (direction) {
case DismissDirection.startToEnd: 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; break;
case DismissDirection.endToStart: case DismissDirection.endToStart:
// Open an edit dialog, then remove the item from the list. // Open an edit dialog, then remove the item from the list.
@ -150,6 +165,7 @@ class _ExpensePageState extends State<ExpensePage> {
notifyParent: refresh, notifyParent: refresh,
entry: curr, entry: curr,
amountText: curr.getAmountText(), amountText: curr.getAmountText(),
type: curr.type!,
), ),
), ),
); );
@ -230,22 +246,22 @@ class TrackedItemInputDialog extends StatefulWidget {
final Function() notifyParent; final Function() notifyParent;
final TrackedItem? entry; final TrackedItem? entry;
final String? amountText; final String? amountText;
final ItemType? type;
const TrackedItemInputDialog({ const TrackedItemInputDialog({
super.key, super.key,
required this.notifyParent, required this.notifyParent,
this.entry, this.entry,
this.amountText, this.amountText,
this.type,
}); });
@override @override
State<TrackedItemInputDialog> createState() => State<TrackedItemInputDialog> createState() => _TrackedItemInputDialogState();
_TrackedItemInputDialogState();
} }
class _TrackedItemInputDialogState class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
extends State<TrackedItemInputDialog> { final _formKey = GlobalKey<FormState>();
final _expenseFormKey = GlobalKey<FormState>();
int? _id; int? _id;
String _name = ""; String _name = "";
@ -255,12 +271,19 @@ class _TrackedItemInputDialogState
@override @override
Widget build(BuildContext context) { 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) { if (widget.entry != null) {
_id = widget.entry!.id; _id = widget.entry!.id;
_name = widget.entry!.name; _name = widget.entry!.name;
_amount = widget.entry!.amount; _amount = widget.entry!.amount;
widget.entry!.frequency == null ? null : _freq = widget.entry!.frequency!; widget.entry!.frequency == null ? null : _freq = widget.entry!.frequency!;
_desc = widget.entry!.description; _desc = widget.entry!.description;
_type = widget.entry!.type!;
} }
String amountText = String amountText =
@ -276,7 +299,20 @@ class _TrackedItemInputDialogState
onPressed: () { onPressed: () {
if (widget.entry != null) { if (widget.entry != null) {
setState(() { 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(); widget.notifyParent();
}); });
} }
@ -289,7 +325,7 @@ class _TrackedItemInputDialogState
insetPadding: EdgeInsets.all(0), insetPadding: EdgeInsets.all(0),
title: Center( title: Center(
child: widget.entry == null child: widget.entry == null
? Text("New Expense") ? Text("New ${_type!.title}")
: Text("Edit Expense"), : Text("Edit Expense"),
), ),
content: FutureBuilder<List<Expense>>( content: FutureBuilder<List<Expense>>(
@ -301,7 +337,7 @@ class _TrackedItemInputDialogState
} }
List<Expense> expenses = snapshot.data!; List<Expense> expenses = snapshot.data!;
return Form( return Form(
key: _expenseFormKey, key: _formKey,
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -424,8 +460,8 @@ class _TrackedItemInputDialogState
Center( Center(
child: ElevatedButton.icon( child: ElevatedButton.icon(
onPressed: () { onPressed: () {
if (_expenseFormKey.currentState!.validate()) { if (_formKey.currentState!.validate()) {
_expenseFormKey.currentState!.save(); _formKey.currentState!.save();
setState(() { setState(() {
Expense expense = Expense( Expense expense = Expense(
id: _id, id: _id,