Begin making the Expense page more arbitrary for future re-use by creating superclasses which will fit all 3 object use cases.

This commit is contained in:
Hyperling 2025-02-11 14:33:38 -07:00
parent 7a3eaf70b5
commit ab9b3e0bf9
4 changed files with 91 additions and 55 deletions

View File

@ -1,51 +1,24 @@
import '/models/recurring_tracked_type.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
class Expense { class Expense extends RecurringTrackedType {
final int? id; static String amountText = "Cost";
final String name;
final double cost;
final Frequency frequency;
final String description;
const Expense({ Expense({
this.id, super.id,
required this.name, required super.name,
required this.cost, required super.amount,
required this.frequency, required super.frequency,
required this.description, required super.description,
}); });
@override
String toString() {
//return "$name, $cost, ${frequency.title}, $description";
return toMap().toString();
}
double calcComparableCost() {
return cost * frequency.timesPerYear;
}
double calcComparableCostDaily() {
return cost / frequency.numDays;
}
factory Expense.fromMap(Map<String, dynamic> json) => Expense( factory Expense.fromMap(Map<String, dynamic> json) => Expense(
id: json['id'], id: json['id'],
name: json['name'], name: json['name'],
cost: json['cost'], amount: json['cost'],
frequency: Frequency.values frequency: Frequency.values
.where((expense) => expense.title == json['frequency']) .where((freq) => freq.title == json['frequency'])
.first, .first,
description: json['description'], description: json['description'],
); );
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'cost': cost,
'frequency': frequency.title,
'description': description,
};
}
} }

View File

@ -0,0 +1,34 @@
// Local
import '/models/tracked_type.dart';
import '/models/frequency.dart';
abstract class RecurringTrackedType extends TrackedType {
Frequency frequency;
RecurringTrackedType({
super.id,
required super.name,
required super.amount,
required this.frequency,
required super.description,
});
double calcComparableAmountYearly() {
return amount * frequency.timesPerYear;
}
double calcComparableAmountDaily() {
return amount / frequency.numDays;
}
@override
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'amount': amount,
'frequency': frequency.title,
'description': description,
};
}
}

View File

@ -0,0 +1,27 @@
abstract class TrackedType {
int? id;
String name;
double amount;
String description;
TrackedType({
this.id,
required this.name,
required this.amount,
required this.description,
});
@override
String toString() {
return toMap().toString();
}
Map<String, dynamic> toMap() {
return {
'id': id,
'name': name,
'amount': amount,
'description': description,
};
}
}

View File

@ -6,7 +6,7 @@ import 'package:flutter_expense_tracker/db.dart';
import '/models/expense.dart'; import '/models/expense.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
// TODO: Make this a generic class based on a suerclass of Expense, Income, and Assets? // TODO: Make this a generic class based on a superclass of Expense, Income, and Assets?
class ExpensePage extends StatefulWidget { class ExpensePage extends StatefulWidget {
const ExpensePage({ const ExpensePage({
@ -30,10 +30,12 @@ class _ExpensePageState extends State<ExpensePage> {
future: DatabaseHelper.instance.getExpenses(), future: DatabaseHelper.instance.getExpenses(),
builder: (BuildContext context, AsyncSnapshot<List<Expense>> snapshot) { builder: (BuildContext context, AsyncSnapshot<List<Expense>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Center(child: Text('Loading...')); return Text('Loading...');
} }
snapshot.data!.sort( snapshot.data!.sort(
(a, b) => (b.calcComparableCost() - a.calcComparableCost()).toInt(), (a, b) => (b.calcComparableAmountYearly() -
a.calcComparableAmountYearly())
.toInt(),
); );
return snapshot.data!.isEmpty return snapshot.data!.isEmpty
? Text( ? Text(
@ -50,7 +52,7 @@ class _ExpensePageState extends State<ExpensePage> {
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
curr curr
.calcComparableCost() .calcComparableAmountYearly()
.toStringAsFixed(3) .toStringAsFixed(3)
.endsWith("0") .endsWith("0")
? "" ? ""
@ -59,7 +61,7 @@ class _ExpensePageState extends State<ExpensePage> {
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
curr curr
.calcComparableCostDaily() .calcComparableAmountDaily()
.toStringAsFixed(3) .toStringAsFixed(3)
.endsWith("0") .endsWith("0")
? "" ? ""
@ -133,7 +135,7 @@ class _ExpensePageState extends State<ExpensePage> {
style: TextStyle(fontSize: 20.0), style: TextStyle(fontSize: 20.0),
), ),
Text( Text(
"${curr.cost.toStringAsFixed(2)} ${curr.frequency.title}", "${curr.amount.toStringAsFixed(2)} ${curr.frequency.title}",
style: TextStyle(fontSize: 12.0), style: TextStyle(fontSize: 12.0),
), ),
], ],
@ -155,12 +157,12 @@ class _ExpensePageState extends State<ExpensePage> {
children: [ children: [
//if (curr.frequency != Frequency.daily) //if (curr.frequency != Frequency.daily)
Text( Text(
"$estimateSymbolDaily${curr.calcComparableCostDaily().toStringAsFixed(2)} ${Frequency.daily.title}", "$estimateSymbolDaily${curr.calcComparableAmountDaily().toStringAsFixed(2)} ${Frequency.daily.title}",
style: TextStyle(fontSize: 12.0), style: TextStyle(fontSize: 12.0),
), ),
//if (curr.frequency != Frequency.yearly) //if (curr.frequency != Frequency.yearly)
Text( Text(
"$estimateSymbolYearly${curr.calcComparableCost().toStringAsFixed(2)} ${Frequency.yearly.title}", "$estimateSymbolYearly${curr.calcComparableAmountYearly().toStringAsFixed(2)} ${Frequency.yearly.title}",
style: TextStyle(fontSize: 12.0), style: TextStyle(fontSize: 12.0),
), ),
], ],
@ -196,7 +198,7 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
int? _id; int? _id;
String _name = ""; String _name = "";
double _cost = 0; double _amount = 0;
Frequency _freq = Frequency.monthly; Frequency _freq = Frequency.monthly;
String _desc = ""; String _desc = "";
@ -205,7 +207,7 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
if (widget.expense != null) { if (widget.expense != null) {
_id = widget.expense!.id; _id = widget.expense!.id;
_name = widget.expense!.name; _name = widget.expense!.name;
_cost = widget.expense!.cost; _amount = widget.expense!.amount;
_freq = widget.expense!.frequency; _freq = widget.expense!.frequency;
_desc = widget.expense!.description; _desc = widget.expense!.description;
} }
@ -277,29 +279,29 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
keyboardType: keyboardType:
TextInputType.numberWithOptions(decimal: true), TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration( decoration: InputDecoration(
labelText: "Cost", labelText: "${Expense.amountText}",
hintText: "Example: 10.00", hintText: "Example: 10.00",
hintStyle: TextStyle(fontSize: 10.0), hintStyle: TextStyle(fontSize: 10.0),
errorStyle: TextStyle(fontSize: 10.0), errorStyle: TextStyle(fontSize: 10.0),
), ),
initialValue: _cost != 0 ? _cost.toString() : "", initialValue: _amount != 0 ? _amount.toString() : "",
validator: (value) { validator: (value) {
if (value == null || value.isEmpty) { if (value == null || value.isEmpty) {
return "Cost must be provided."; return "${Expense.amountText} must be provided.";
} }
if (double.parse(value) < 0) { if (double.parse(value) < 0) {
return "Please use the Income page rather than having negative expenses."; return "Please use the Income page rather than having negative expenses.";
} }
if (double.parse(value) < 0.01) { if (double.parse(value) < 0.01) {
return "Cost must be one hundreth (0.01) or higher."; return "${Expense.amountText} must be one hundreth (0.01) or higher.";
} }
if (double.tryParse(value) == null) { if (double.tryParse(value) == null) {
return "Cost must be a valid number."; return "${Expense.amountText} must be a valid number.";
} }
return null; return null;
}, },
onSaved: (value) { onSaved: (value) {
_cost = double.parse(value!); _amount = double.parse(value!);
}, },
), ),
DropdownButtonFormField( DropdownButtonFormField(
@ -374,7 +376,7 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
Expense expense = Expense( Expense expense = Expense(
id: _id, id: _id,
name: _name, name: _name,
cost: _cost, amount: _amount,
frequency: _freq, frequency: _freq,
description: _desc, description: _desc,
); );