Compare commits
	
		
			4 Commits
		
	
	
		
			064884dc07
			...
			42548d437c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 42548d437c | |||
| be66f52cbf | |||
| 992195b9a0 | |||
| 991eea4d54 | 
| @@ -9,7 +9,7 @@ import 'package:sqflite/sqflite.dart'; | ||||
|  | ||||
| // Local | ||||
| import '/models/expense.dart'; | ||||
| import '/models/tracked_type_recurring.dart'; | ||||
| import '/models/tracked_item.dart'; | ||||
|  | ||||
| // Leaned on this example: | ||||
| //   https://learnflutterwithme.com/sqlite | ||||
| @@ -77,7 +77,7 @@ class DatabaseHelper { | ||||
|     return expenseList; | ||||
|   } | ||||
|  | ||||
|   Future<int> addExpense(RecurringTrackedType expense) async { | ||||
|   Future<int> addExpense(TrackedItem expense) async { | ||||
|     Database db = await instance.db; | ||||
|     return await db.insert( | ||||
|       "expense", | ||||
| @@ -94,7 +94,7 @@ class DatabaseHelper { | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<int> updateExpense(RecurringTrackedType expense) async { | ||||
|   Future<int> updateExpense(TrackedItem expense) async { | ||||
|     Database db = await instance.db; | ||||
|     return await db.update( | ||||
|       "expense", | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import '/models/tracked_type.dart'; | ||||
| // Local | ||||
| import '/models/tracked_item.dart'; | ||||
|  | ||||
| class Asset extends TrackedType { | ||||
| class Asset extends TrackedItem { | ||||
|   static String amountText = "Amount"; | ||||
|  | ||||
|   Asset({ | ||||
|   | ||||
| @@ -29,7 +29,7 @@ class DatabaseBackup { | ||||
|             'id': e.id, | ||||
|             'name': e.name, | ||||
|             'cost': e.amount, | ||||
|             'frequency': e.frequency.title, | ||||
|             'frequency': e.frequency!.title, | ||||
|             'description': e.description, | ||||
|           }, | ||||
|       ], | ||||
| @@ -39,7 +39,7 @@ class DatabaseBackup { | ||||
|             'id': i.id, | ||||
|             'name': i.name, | ||||
|             'revenue': i.amount, | ||||
|             'frequency': i.frequency.title, | ||||
|             'frequency': i.frequency!.title, | ||||
|             'description': i.description, | ||||
|           }, | ||||
|       ], | ||||
|   | ||||
| @@ -1,12 +1,15 @@ | ||||
| // Local | ||||
| import '/models/tracked_type_recurring.dart'; | ||||
| import 'package:flutter_expense_tracker/models/item_type.dart'; | ||||
|  | ||||
| import '/models/tracked_item.dart'; | ||||
| import '/models/frequency.dart'; | ||||
|  | ||||
| class Expense extends RecurringTrackedType { | ||||
| class Expense extends TrackedItem { | ||||
|   static String amountText = "Cost"; | ||||
|  | ||||
|   Expense({ | ||||
|     super.id, | ||||
|     super.type = ItemType.expense, | ||||
|     required super.name, | ||||
|     required super.amount, | ||||
|     required super.frequency, | ||||
| @@ -29,7 +32,7 @@ class Expense extends RecurringTrackedType { | ||||
|       'id': id, | ||||
|       'name': name, | ||||
|       'cost': amount, | ||||
|       'frequency': frequency.title, | ||||
|       'frequency': frequency!.title, | ||||
|       'description': description, | ||||
|     }; | ||||
|   } | ||||
|   | ||||
| @@ -1,8 +1,8 @@ | ||||
| // Local | ||||
| import '/models/tracked_type_recurring.dart'; | ||||
| import '/models/tracked_item.dart'; | ||||
| import '/models/frequency.dart'; | ||||
|  | ||||
| class Income extends RecurringTrackedType { | ||||
| class Income extends TrackedItem { | ||||
|   static String amountText = "Revenue"; | ||||
|  | ||||
|   Income({ | ||||
| @@ -29,7 +29,7 @@ class Income extends RecurringTrackedType { | ||||
|       'id': id, | ||||
|       'name': name, | ||||
|       'revenue': amount, | ||||
|       'frequency': frequency.title, | ||||
|       'frequency': frequency!.title, | ||||
|       'description': description, | ||||
|     }; | ||||
|   } | ||||
|   | ||||
							
								
								
									
										22
									
								
								lib/models/item_type.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								lib/models/item_type.dart
									
									
									
									
									
										Normal 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; | ||||
| } | ||||
							
								
								
									
										53
									
								
								lib/models/tracked_item.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								lib/models/tracked_item.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,53 @@ | ||||
| // 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; | ||||
|   String description; | ||||
|  | ||||
|   TrackedItem({ | ||||
|     this.id, | ||||
|     this.type, | ||||
|     required this.name, | ||||
|     required this.amount, | ||||
|     this.frequency, | ||||
|     required this.description, | ||||
|   }); | ||||
|  | ||||
|   static String amountText = "Amount"; | ||||
|   String getAmountText() => amountText; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return toMap().toString(); | ||||
|   } | ||||
|  | ||||
|   double calcComparableAmountYearly() { | ||||
|     return frequency == null ? 0 : amount * frequency!.timesPerYear; | ||||
|   } | ||||
|  | ||||
|   double calcComparableAmountDaily() { | ||||
|     return frequency == null ? 0 : amount / frequency!.numDays; | ||||
|   } | ||||
|  | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return frequency == null ? { | ||||
|       'id': id, | ||||
|       'name': name, | ||||
|       'amount': amount, | ||||
|       'description': description, | ||||
|     } : { | ||||
|       'id': id, | ||||
|       'name': name, | ||||
|       'amount': amount, | ||||
|       'frequency': frequency!.title, | ||||
|       'description': description, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @@ -1,30 +0,0 @@ | ||||
| abstract class TrackedType { | ||||
|   int? id; | ||||
|   String name; | ||||
|   double amount; | ||||
|   String description; | ||||
|  | ||||
|   TrackedType({ | ||||
|     this.id, | ||||
|     required this.name, | ||||
|     required this.amount, | ||||
|     required this.description, | ||||
|   }); | ||||
|  | ||||
|   static String amountText = "Amount"; | ||||
|   String getAmountText() => amountText; | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
|     return toMap().toString(); | ||||
|   } | ||||
|  | ||||
|   Map<String, dynamic> toMap() { | ||||
|     return { | ||||
|       'id': id, | ||||
|       'name': name, | ||||
|       'amount': amount, | ||||
|       'description': description, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @@ -1,34 +0,0 @@ | ||||
| // 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, | ||||
|     }; | ||||
|   } | ||||
| } | ||||
| @@ -1,12 +0,0 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class AssetPage extends StatelessWidget { | ||||
|   const AssetPage({ | ||||
|     super.key, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Placeholder(); | ||||
|   } | ||||
| } | ||||
| @@ -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<HomePage> { | ||||
|     Widget? dialog; | ||||
|     switch (pageSelected) { | ||||
|       case 0: | ||||
|         page = ExpensePage(); | ||||
|         dialog = RecurringTrackedTypeInputDialog( | ||||
|         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: | ||||
|   | ||||
| @@ -1,12 +0,0 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class IncomePage extends StatelessWidget { | ||||
|   const IncomePage({ | ||||
|     super.key, | ||||
|   }); | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Placeholder(); | ||||
|   } | ||||
| } | ||||
| @@ -2,8 +2,8 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| // Local | ||||
| import 'package:flutter_expense_tracker/db.dart'; | ||||
| import 'package:flutter_expense_tracker/models/tracked_type_recurring.dart'; | ||||
| import '/db.dart'; | ||||
| import '/models/tracked_item.dart'; | ||||
|  | ||||
| /// TODO: | ||||
| /// - Expenses (total number, totals by day / month / year) | ||||
| @@ -57,31 +57,39 @@ class SummaryCardForTotals extends StatelessWidget { | ||||
|     required this.summaryTypeLabel, | ||||
|   }); | ||||
|  | ||||
|   final Future<List<RecurringTrackedType>> list; | ||||
|   final Future<List<TrackedItem>> list; | ||||
|   final String summaryTypeLabel; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return FutureBuilder<List<RecurringTrackedType>>( | ||||
|     return FutureBuilder<List<TrackedItem>>( | ||||
|         future: list, | ||||
|         builder: ( | ||||
|           BuildContext context, | ||||
|           AsyncSnapshot<List<RecurringTrackedType>> snapshot, | ||||
|           AsyncSnapshot<List<TrackedItem>> snapshot, | ||||
|         ) { | ||||
|           if (!snapshot.hasData) { | ||||
|             return Text('Loading $summaryTypeLabel Section...'); | ||||
|           } | ||||
|           double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0; | ||||
|           for (RecurringTrackedType e in snapshot.data!) { | ||||
|           for (TrackedItem e in snapshot.data!) { | ||||
|             dailyTotal += e.calcComparableAmountDaily(); | ||||
|             monthlyTotal += e.calcComparableAmountYearly() / 12; | ||||
|             yearlyTotal += e.calcComparableAmountYearly(); | ||||
|           } | ||||
|           String dailyEstimate = | ||||
|                   dailyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~", | ||||
|               monthlyEstimate = | ||||
|                   monthlyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~", | ||||
|               yearlyEstimate = | ||||
|                   yearlyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~"; | ||||
|           return SummaryCard( | ||||
|             name: "$summaryTypeLabel Totals", | ||||
|             leftText: "${dailyTotal.toStringAsFixed(2)} Daily", | ||||
|             middleText: "${monthlyTotal.toStringAsFixed(2)} Monthly", | ||||
|             rightText: "${yearlyTotal.toStringAsFixed(2)} Yearly", | ||||
|             leftText: "$dailyEstimate${dailyTotal.toStringAsFixed(2)} Daily", | ||||
|             middleText: | ||||
|                 "$monthlyEstimate${monthlyTotal.toStringAsFixed(2)} Monthly", | ||||
|             rightText: | ||||
|                 "$yearlyEstimate${yearlyTotal.toStringAsFixed(2)} Yearly", | ||||
|           ); | ||||
|         }); | ||||
|   } | ||||
| @@ -104,27 +112,41 @@ class SummaryCard extends StatelessWidget { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return Card( | ||||
|       color: Theme.of(context).cardColor, | ||||
|       child: Padding( | ||||
|         padding: const EdgeInsets.all(8.0), | ||||
|         child: Column( | ||||
|           children: [ | ||||
|             Row( | ||||
|               children: [ | ||||
|                 Text(leftText), | ||||
|                 Spacer(), | ||||
|                 Center( | ||||
|                   child: Column( | ||||
|                     children: [ | ||||
|                       Text(name), | ||||
|                       Text(middleText), | ||||
|                     ], | ||||
|                   ), | ||||
|         child: Card( | ||||
|           color: Theme.of(context).highlightColor, | ||||
|           child: Column( | ||||
|             children: [ | ||||
|               Text( | ||||
|                 name, | ||||
|                 style: TextStyle( | ||||
|                   decoration: TextDecoration.underline, | ||||
|                   fontSize: 16 | ||||
|                 ), | ||||
|                 Spacer(), | ||||
|                 Text(rightText), | ||||
|               ], | ||||
|             ), | ||||
|           ], | ||||
|               ), | ||||
|               Row( | ||||
|                 children: [ | ||||
|                   Spacer( | ||||
|                     flex: 3, | ||||
|                   ), | ||||
|                   Text(leftText), | ||||
|                   Spacer( | ||||
|                     flex: 1, | ||||
|                   ), | ||||
|                   Text(middleText), | ||||
|                   Spacer( | ||||
|                     flex: 1, | ||||
|                   ), | ||||
|                   Text(rightText), | ||||
|                   Spacer( | ||||
|                     flex: 3, | ||||
|                   ), | ||||
|                 ], | ||||
|               ), | ||||
|             ], | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   | ||||
| @@ -1,25 +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_type.dart'; | ||||
| import '/models/tracked_type_recurring.dart'; | ||||
| 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<List<TrackedItem>> assetsToLoad; | ||||
| 
 | ||||
|   const TrackedItemPage({ | ||||
|     super.key, | ||||
|     required this.assetsToLoad, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<ExpensePage> createState() => _ExpensePageState(); | ||||
|   State<TrackedItemPage> createState() => _TrackedItemPageState(); | ||||
| } | ||||
| 
 | ||||
| class _ExpensePageState extends State<ExpensePage> { | ||||
| class _TrackedItemPageState extends State<TrackedItemPage> { | ||||
|   refresh() { | ||||
|     setState(() {}); | ||||
|   } | ||||
| @@ -28,10 +33,10 @@ class _ExpensePageState extends State<ExpensePage> { | ||||
|   Widget build(BuildContext context) { | ||||
|     final theme = Theme.of(context); | ||||
| 
 | ||||
|     return FutureBuilder<List<RecurringTrackedType>>( | ||||
|         future: DatabaseHelper.instance.getExpenses(), | ||||
|         builder: (BuildContext context, | ||||
|             AsyncSnapshot<List<RecurringTrackedType>> snapshot) { | ||||
|     return FutureBuilder<List<TrackedItem>>( | ||||
|         future: widget.assetsToLoad, | ||||
|         builder: | ||||
|             (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) { | ||||
|           if (!snapshot.hasData) { | ||||
|             return Text('Loading...'); | ||||
|           } | ||||
| @@ -42,65 +47,73 @@ class _ExpensePageState extends State<ExpensePage> { | ||||
|           ); | ||||
|           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<Expense> expenses = snapshot.data!; | ||||
|                     final RecurringTrackedType curr = snapshot.data![index]; | ||||
|                     final TrackedItem curr = snapshot.data![index]; | ||||
| 
 | ||||
|                     final itemKey = Key(curr.id!.toString()); | ||||
|                     final String itemTitle = curr.name; | ||||
| 
 | ||||
|                     final String itemAmount; | ||||
|                     // TODO: How can we do RecurringTrackedType vs TrackedType here | ||||
|                     // if the Widgets are expecting RecurringTrackedType, but we | ||||
|                     // need to be using Frequency? Change to only have one abstract | ||||
|                     // class and make it nully again? Hmmm... | ||||
|                     //if (curr is RecurringTrackedType && curr.frequency != null) { | ||||
|                     if (curr.frequency != null) { | ||||
|                       itemAmount = | ||||
|                           "${curr.amount.toStringAsFixed(2)} ${curr.frequency.title}"; | ||||
|                     /*} else { | ||||
|                           "${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}"; | ||||
|                     } else { | ||||
|                       itemAmount = curr.amount.toStringAsFixed(2); | ||||
|                     }*/ | ||||
|                     } | ||||
|                     final String itemDescription = curr.description; | ||||
| 
 | ||||
|                     final double itemDayAmount = | ||||
|                         curr.calcComparableAmountDaily(); | ||||
|                     final String estimateSymbolDaily = curr.frequency.numDays | ||||
|                                 .toStringAsFixed(2) | ||||
|                                 .endsWith(".00") && | ||||
|                             itemDayAmount.toStringAsFixed(3).endsWith("0") | ||||
|                         ? "" | ||||
|                         : "~"; | ||||
|                     final double itemDayAmount, itemMonthAmount, itemYearAmount; | ||||
|                     final String estimateSymbolDaily, | ||||
|                         estimateSymbolMonthly, | ||||
|                         estimateSymbolYearly; | ||||
| 
 | ||||
|                     final double itemMonthAmount = | ||||
|                         (curr.calcComparableAmountYearly() / 12); | ||||
|                     final String estimateSymbolMonthly = curr | ||||
|                                 .frequency.timesPerYear | ||||
|                                 .toStringAsFixed(2) | ||||
|                                 .endsWith(".00") && | ||||
|                             itemMonthAmount.toStringAsFixed(3).endsWith("0") | ||||
|                         ? "" | ||||
|                         : "~"; | ||||
|                     if (curr.frequency != null) { | ||||
|                       itemDayAmount = curr.calcComparableAmountDaily(); | ||||
|                       estimateSymbolDaily = curr.frequency!.numDays | ||||
|                                   .toStringAsFixed(2) | ||||
|                                   .endsWith(".00") && | ||||
|                               itemDayAmount.toStringAsFixed(3).endsWith("0") | ||||
|                           ? "" | ||||
|                           : "~"; | ||||
| 
 | ||||
|                     final double itemYearAmount = | ||||
|                         curr.calcComparableAmountYearly(); | ||||
|                     final String estimateSymbolYearly = curr | ||||
|                                 .frequency.timesPerYear | ||||
|                                 .toStringAsFixed(2) | ||||
|                                 .endsWith(".00") && | ||||
|                             itemYearAmount.toStringAsFixed(3).endsWith("0") | ||||
|                         ? "" | ||||
|                         : "~"; | ||||
|                       itemMonthAmount = | ||||
|                           (curr.calcComparableAmountYearly() / 12); | ||||
|                       estimateSymbolMonthly = curr.frequency!.timesPerYear | ||||
|                                   .toStringAsFixed(2) | ||||
|                                   .endsWith(".00") && | ||||
|                               itemMonthAmount.toStringAsFixed(3).endsWith("0") | ||||
|                           ? "" | ||||
|                           : "~"; | ||||
| 
 | ||||
|                     final String itemTopText = | ||||
|                         "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}"; | ||||
|                     final String itemMiddleText = | ||||
|                         "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)} ${Frequency.monthly.title}"; | ||||
|                     final String itemBottomText = | ||||
|                         "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}"; | ||||
|                       itemYearAmount = curr.calcComparableAmountYearly(); | ||||
|                       estimateSymbolYearly = curr.frequency!.timesPerYear | ||||
|                                   .toStringAsFixed(2) | ||||
|                                   .endsWith(".00") && | ||||
|                               itemYearAmount.toStringAsFixed(3).endsWith("0") | ||||
|                           ? "" | ||||
|                           : "~"; | ||||
|                     } else { | ||||
|                       itemDayAmount = -1; | ||||
|                       estimateSymbolDaily = ""; | ||||
|                       itemMonthAmount = curr.amount; | ||||
|                       estimateSymbolMonthly = ""; | ||||
|                       itemYearAmount = -1; | ||||
|                       estimateSymbolYearly = ""; | ||||
|                     } | ||||
|                     final String itemTopText = itemDayAmount < 0 | ||||
|                         ? "" | ||||
|                         : "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}"; | ||||
|                     final String itemMiddleText = itemMonthAmount < 0 | ||||
|                         ? "" | ||||
|                         : "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)} ${Frequency.monthly.title}"; | ||||
|                     final String itemBottomText = itemYearAmount < 0 | ||||
|                         ? "" | ||||
|                         : "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}"; | ||||
| 
 | ||||
|                     return Padding( | ||||
|                       padding: const EdgeInsets.all(4.0), | ||||
| @@ -131,17 +144,28 @@ class _ExpensePageState extends State<ExpensePage> { | ||||
|                             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. | ||||
|                                 showDialog( | ||||
|                                   context: context, | ||||
|                                   builder: (_) => AlertDialog( | ||||
|                                     content: RecurringTrackedTypeInputDialog( | ||||
|                                     content: TrackedItemInputDialog( | ||||
|                                       notifyParent: refresh, | ||||
|                                       entry: curr, | ||||
|                                       amountText: curr.getAmountText(), | ||||
|                                       type: curr.type!, | ||||
|                                     ), | ||||
|                                   ), | ||||
|                                 ); | ||||
| @@ -218,26 +242,26 @@ class _ExpensePageState extends State<ExpensePage> { | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class RecurringTrackedTypeInputDialog extends StatefulWidget { | ||||
| class TrackedItemInputDialog extends StatefulWidget { | ||||
|   final Function() notifyParent; | ||||
|   final RecurringTrackedType? entry; | ||||
|   final TrackedItem? entry; | ||||
|   final String? amountText; | ||||
|   final ItemType? type; | ||||
| 
 | ||||
|   const RecurringTrackedTypeInputDialog({ | ||||
|   const TrackedItemInputDialog({ | ||||
|     super.key, | ||||
|     required this.notifyParent, | ||||
|     this.entry, | ||||
|     this.amountText, | ||||
|     this.type, | ||||
|   }); | ||||
| 
 | ||||
|   @override | ||||
|   State<RecurringTrackedTypeInputDialog> createState() => | ||||
|       _RecurringTrackedTypeInputDialogState(); | ||||
|   State<TrackedItemInputDialog> createState() => _TrackedItemInputDialogState(); | ||||
| } | ||||
| 
 | ||||
| class _RecurringTrackedTypeInputDialogState | ||||
|     extends State<RecurringTrackedTypeInputDialog> { | ||||
|   final _expenseFormKey = GlobalKey<FormState>(); | ||||
| class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> { | ||||
|   final _formKey = GlobalKey<FormState>(); | ||||
| 
 | ||||
|   int? _id; | ||||
|   String _name = ""; | ||||
| @@ -247,16 +271,23 @@ class _RecurringTrackedTypeInputDialogState | ||||
| 
 | ||||
|   @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; | ||||
|       _freq = widget.entry!.frequency; | ||||
|       widget.entry!.frequency == null ? null : _freq = widget.entry!.frequency!; | ||||
|       _desc = widget.entry!.description; | ||||
|       _type = widget.entry!.type!; | ||||
|     } | ||||
| 
 | ||||
|     String amountText = | ||||
|         widget.amountText != null ? widget.amountText! : TrackedType.amountText; | ||||
|         widget.amountText != null ? widget.amountText! : TrackedItem.amountText; | ||||
| 
 | ||||
|     return Column( | ||||
|       // prevent AlertDialog from taking full vertical height. | ||||
| @@ -268,7 +299,20 @@ class _RecurringTrackedTypeInputDialogState | ||||
|             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(); | ||||
|                 }); | ||||
|               } | ||||
| @@ -281,7 +325,7 @@ class _RecurringTrackedTypeInputDialogState | ||||
|           insetPadding: EdgeInsets.all(0), | ||||
|           title: Center( | ||||
|             child: widget.entry == null | ||||
|                 ? Text("New Expense") | ||||
|                 ? Text("New ${_type!.title}") | ||||
|                 : Text("Edit Expense"), | ||||
|           ), | ||||
|           content: FutureBuilder<List<Expense>>( | ||||
| @@ -293,7 +337,7 @@ class _RecurringTrackedTypeInputDialogState | ||||
|                 } | ||||
|                 List<Expense> expenses = snapshot.data!; | ||||
|                 return Form( | ||||
|                   key: _expenseFormKey, | ||||
|                   key: _formKey, | ||||
|                   child: Column( | ||||
|                     mainAxisSize: MainAxisSize.min, | ||||
|                     children: [ | ||||
| @@ -416,8 +460,8 @@ class _RecurringTrackedTypeInputDialogState | ||||
|             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, | ||||
		Reference in New Issue
	
	Block a user