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:
		@@ -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,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										34
									
								
								lib/models/recurring_tracked_type.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								lib/models/recurring_tracked_type.dart
									
									
									
									
									
										Normal 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,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										27
									
								
								lib/models/tracked_type.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/models/tracked_type.dart
									
									
									
									
									
										Normal 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,
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
                      );
 | 
					                      );
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user