Compare commits
	
		
			19 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 70c26be8e3 | |||
| b177b6317e | |||
| 3c52239efc | |||
| de4f8306d7 | |||
| f77cf7bd38 | |||
| 716d40c694 | |||
| d9f8536f26 | |||
| 1966f72c93 | |||
| e896611bd1 | |||
| 2d9c93fec4 | |||
| c5f1a4e9ba | |||
| 538a298acd | |||
| cce878ccaa | |||
| 147178e4dd | |||
| c39e09b2b6 | |||
| fa852faadc | |||
| 6f9d0d8afb | |||
| 75cc72678b | |||
| 2970431b91 | 
| @@ -1 +1,4 @@ | |||||||
| include: package:flutter_lints/flutter.yaml | include: package:flutter_lints/flutter.yaml | ||||||
|  | analyzer: | ||||||
|  |   errors: | ||||||
|  |     unreachable_switch_default: ignore | ||||||
|   | |||||||
| @@ -2,21 +2,26 @@ enum ItemType { | |||||||
|   expense( |   expense( | ||||||
|     title: "Expense", |     title: "Expense", | ||||||
|     plural: "Expenses", |     plural: "Expenses", | ||||||
|  |     description: "Items which cost revenue, or decrease asset value.", | ||||||
|   ), |   ), | ||||||
|   income( |   income( | ||||||
|     title: "Income", |     title: "Income", | ||||||
|     plural: "Incomes", |     plural: "Income", | ||||||
|  |     description: "Items which bring in revenue, or increase asset value.", | ||||||
|   ), |   ), | ||||||
|   asset( |   asset( | ||||||
|     title: "Asset", |     title: "Asset", | ||||||
|     plural: "Assets", |     plural: "Assets", | ||||||
|  |     description: "Value which has been earned and can be spent.", | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   const ItemType({ |   const ItemType({ | ||||||
|     required this.title, |     required this.title, | ||||||
|     required this.plural, |     required this.plural, | ||||||
|  |     required this.description, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   final String title; |   final String title; | ||||||
|   final String plural; |   final String plural; | ||||||
|  |   final String description; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,7 +1,11 @@ | |||||||
| // Flutter | // Flutter | ||||||
|  | import '/models/item_type.dart'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import 'package:url_launcher/url_launcher_string.dart'; | import 'package:url_launcher/url_launcher_string.dart'; | ||||||
|  |  | ||||||
|  | // Local | ||||||
|  | import '/widgets/cards.dart'; | ||||||
|  |  | ||||||
| _launchSite(String url) async { | _launchSite(String url) async { | ||||||
|   try { |   try { | ||||||
|     if (await canLaunchUrlString(url)) { |     if (await canLaunchUrlString(url)) { | ||||||
| @@ -38,23 +42,34 @@ class HelpPage extends StatelessWidget { | |||||||
|               ), |               ), | ||||||
|               child: Column( |               child: Column( | ||||||
|                 children: [ |                 children: [ | ||||||
|  |                   TitleCard(title: "Help"), | ||||||
|                   Text( |                   Text( | ||||||
|                     "\t\t This app is meant to be a simple budgeting tool," |                     "\t\t This app is meant to be a simple budgeting tool," | ||||||
|                     " allowing you to view your income and expenses at a high" |                     " allowing you to view your income and expenses at a high" | ||||||
|                     " level without micro managing specific budget items or" |                     " level without micro managing specific budget items or" | ||||||
|                     " adding receipts.", |                     " adding receipts." | ||||||
|  |                     "", | ||||||
|                   ), |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     "\n\t\t Tracked items can be swiped left to right for ," |                     "\n\t\t ${ItemType.expense.plural} are defined as ${ItemType.expense.description.toLowerCase()}" | ||||||
|  |                     " ${ItemType.income.title} is defined as ${ItemType.income.description.toLowerCase()}" | ||||||
|  |                     " ${ItemType.asset.plural} are defined as ${ItemType.asset.description.toLowerCase()}" | ||||||
|  |                     "", | ||||||
|  |                   ), | ||||||
|  |                   Text( | ||||||
|  |                     "\n\t\t Tracked items can be swiped left to right for" | ||||||
|                     " Deletion or right to left for Editing. Items are sorted" |                     " Deletion or right to left for Editing. Items are sorted" | ||||||
|                     " from highest to lowest so that the biggest impacts are" |                     " from highest to lowest so that the biggest impacts are" | ||||||
|                     " always in view.", |                     " always in view." | ||||||
|  |                     "", | ||||||
|                   ), |                   ), | ||||||
|                   Text( |                   Text( | ||||||
|                     "\n\t\t To subscribe to app updates, install the Obtanium" |                     "\n\t\t To subscribe to Android updates, install Obtanium," | ||||||
|                     " app, then use the URL from the Source Code button below." |                     " then use the URL from the Source Code button below." | ||||||
|                     " Otherwise the app needs installed manually by downloading" |                     " Otherwise the app needs installed manually by downloading" | ||||||
|                     " APKs from the Source Code /releases/ page.", |                     " APKs from the Source Code /releases/ page. Linux users" | ||||||
|  |                     " currently need to install and update manually." | ||||||
|  |                     "", | ||||||
|                   ), |                   ), | ||||||
|                   //Text("Another paragraph.") |                   //Text("Another paragraph.") | ||||||
|                 ], |                 ], | ||||||
|   | |||||||
| @@ -2,13 +2,13 @@ | |||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import '/models/item_type.dart'; | import '/models/item_type.dart'; | ||||||
| import 'dart:io'; | import 'dart:io'; | ||||||
|  | import 'package:package_info_plus/package_info_plus.dart'; | ||||||
|  |  | ||||||
| // Local | // Local | ||||||
| import '/pages/tracked_item.dart'; | import '/pages/tracked_item.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({ | ||||||
| @@ -26,14 +26,36 @@ class _HomePageState extends State<HomePage> { | |||||||
|     setState(() {}); |     setState(() {}); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   PackageInfo _packageInfo = PackageInfo( | ||||||
|  |     appName: 'Unknown', | ||||||
|  |     packageName: 'Unknown', | ||||||
|  |     version: 'Unknown', | ||||||
|  |     buildNumber: 'Unknown', | ||||||
|  |   ); | ||||||
|  |  | ||||||
|  |   Future _initPackageInfo() async { | ||||||
|  |     final PackageInfo info = await PackageInfo.fromPlatform(); | ||||||
|  |     setState(() { | ||||||
|  |       _packageInfo = info; | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void initState() { | ||||||
|  |     super.initState(); | ||||||
|  |     // Get package details | ||||||
|  |     _initPackageInfo(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     Widget page; |     Widget page; | ||||||
|     Widget? dialog; |     Widget? dialog; | ||||||
|  |  | ||||||
|     switch (pageSelected) { |     switch (pageSelected) { | ||||||
|       case 0: |       case 0: | ||||||
|         page = TrackedItemPage( |         page = TrackedItemPage( | ||||||
|           assetsToLoad: DatabaseHelper.instance.getExpenses(), |           assetType: ItemType.expense, | ||||||
|           notifyParent: refresh, |           notifyParent: refresh, | ||||||
|         ); |         ); | ||||||
|         dialog = TrackedItemInputDialog( |         dialog = TrackedItemInputDialog( | ||||||
| @@ -43,7 +65,7 @@ class _HomePageState extends State<HomePage> { | |||||||
|         break; |         break; | ||||||
|       case 1: |       case 1: | ||||||
|         page = TrackedItemPage( |         page = TrackedItemPage( | ||||||
|           assetsToLoad: DatabaseHelper.instance.getIncomes(), |           assetType: ItemType.income, | ||||||
|           notifyParent: refresh, |           notifyParent: refresh, | ||||||
|         ); |         ); | ||||||
|         dialog = TrackedItemInputDialog( |         dialog = TrackedItemInputDialog( | ||||||
| @@ -53,7 +75,7 @@ class _HomePageState extends State<HomePage> { | |||||||
|         break; |         break; | ||||||
|       case 2: |       case 2: | ||||||
|         page = TrackedItemPage( |         page = TrackedItemPage( | ||||||
|           assetsToLoad: DatabaseHelper.instance.getAssets(), |           assetType: ItemType.asset, | ||||||
|           notifyParent: refresh, |           notifyParent: refresh, | ||||||
|         ); |         ); | ||||||
|         dialog = TrackedItemInputDialog( |         dialog = TrackedItemInputDialog( | ||||||
| @@ -97,15 +119,15 @@ class _HomePageState extends State<HomePage> { | |||||||
|       destinations: [ |       destinations: [ | ||||||
|         NavigationRailDestination( |         NavigationRailDestination( | ||||||
|           icon: Icon(Icons.payment), |           icon: Icon(Icons.payment), | ||||||
|           label: Text('Expenses'), |           label: Text(ItemType.expense.plural), | ||||||
|         ), |         ), | ||||||
|         NavigationRailDestination( |         NavigationRailDestination( | ||||||
|           icon: Icon(Icons.account_balance), |           icon: Icon(Icons.account_balance), | ||||||
|           label: Text('Income'), |           label: Text(ItemType.income.plural), | ||||||
|         ), |         ), | ||||||
|         NavigationRailDestination( |         NavigationRailDestination( | ||||||
|           icon: Icon(Icons.attach_money), |           icon: Icon(Icons.attach_money), | ||||||
|           label: Text('Liquid Assets'), |           label: Text(ItemType.asset.plural), | ||||||
|         ), |         ), | ||||||
|         NavigationRailDestination( |         NavigationRailDestination( | ||||||
|           icon: Icon(Icons.bar_chart), |           icon: Icon(Icons.bar_chart), | ||||||
| @@ -129,6 +151,8 @@ class _HomePageState extends State<HomePage> { | |||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|  |       leading: Text("Menu"), | ||||||
|  |       trailing: Text("v${_packageInfo.version}"), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     Widget main = Container( |     Widget main = Container( | ||||||
|   | |||||||
| @@ -1,4 +1,5 @@ | |||||||
| // Flutter | // Flutter | ||||||
|  | import 'dart:async'; | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
| import '/models/item_type.dart'; | import '/models/item_type.dart'; | ||||||
|  |  | ||||||
| @@ -10,14 +11,12 @@ import '/models/tracked_item.dart'; | |||||||
| /// TODO: | /// TODO: | ||||||
| /// - Projected Assets: | /// - Projected Assets: | ||||||
| ///   - Allow customization? | ///   - Allow customization? | ||||||
| ///   - Fix bug where editing an item does not reflect immediately when returning to Reports page. |  | ||||||
| ///     - Currently reflects after going back to Reports the 2nd time. |  | ||||||
|  |  | ||||||
| double _assetTotal = 0, | double _assetTotal = -1, | ||||||
|     _expenseMonthly = 0, |     _expenseMonthly = -1, | ||||||
|     _expenseYearly = 0, |     _expenseYearly = -1, | ||||||
|     _incomeMonthly = 0, |     _incomeMonthly = -1, | ||||||
|     _incomeYearly = 0; |     _incomeYearly = -1; | ||||||
|  |  | ||||||
| class ProjectionPage extends StatefulWidget { | class ProjectionPage extends StatefulWidget { | ||||||
|   const ProjectionPage({ |   const ProjectionPage({ | ||||||
| @@ -29,67 +28,113 @@ class ProjectionPage extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _ProjectionPageState extends State<ProjectionPage> { | class _ProjectionPageState extends State<ProjectionPage> { | ||||||
|  |   bool _showProjections = true; | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   void dispose() { | ||||||
|  |     _assetTotal = -2; | ||||||
|  |     _expenseMonthly = -2; | ||||||
|  |     _expenseYearly = -2; | ||||||
|  |     _incomeMonthly = -2; | ||||||
|  |     _incomeYearly = -2; | ||||||
|  |     super.dispose(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     // Summaries for display as well as calculation of totals for projections. |     // Summaries for display as well as calculation of totals for projections. | ||||||
|     Widget expenseSummary = SummaryCardForTotals( |     Widget expenseSummary = SummaryCardForTotals( | ||||||
|       list: DatabaseHelper.instance.getExpenses(), |       list: DatabaseHelper.instance.getExpenses(), | ||||||
|       summaryTypeLabel: ItemType.expense.title, |       itemType: ItemType.expense, | ||||||
|     ); |     ); | ||||||
|     Widget incomeSummary = SummaryCardForTotals( |     Widget incomeSummary = SummaryCardForTotals( | ||||||
|       list: DatabaseHelper.instance.getIncomes(), |       list: DatabaseHelper.instance.getIncomes(), | ||||||
|       summaryTypeLabel: ItemType.income.title, |       itemType: ItemType.income, | ||||||
|     ); |     ); | ||||||
|     Widget assetSummary = SummaryCardForTotals( |     Widget assetSummary = SummaryCardForTotals( | ||||||
|       list: DatabaseHelper.instance.getAssets(), |       list: DatabaseHelper.instance.getAssets(), | ||||||
|       summaryTypeLabel: ItemType.asset.title, |       itemType: ItemType.asset, | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     // Calculations for the projections. |     // Calculations for the projections. | ||||||
|     double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, |     Widget projections; | ||||||
|         threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), |     if (_assetTotal < 0 || | ||||||
|         sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), |         _incomeMonthly < 0 || | ||||||
|         oneYear = _assetTotal + (_incomeYearly - _expenseYearly), |         _incomeYearly < 0 || | ||||||
|         twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)), |         _expenseMonthly < 0 || | ||||||
|         fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly)); |         _expenseYearly < 0) { | ||||||
|  |       _showProjections = false; | ||||||
|  |  | ||||||
|     // Widgets to show the projections. |       Future.delayed(Duration(seconds: 1), () { | ||||||
|     Widget proj1 = SummaryCard( |         setState(() { | ||||||
|       name: "One month from now...", |           _showProjections = true; | ||||||
|       leftText: "", |         }); | ||||||
|       middleText: oneMonth.toStringAsFixed(2), |       }); | ||||||
|       rightText: "", |     } | ||||||
|     ); |  | ||||||
|     Widget proj2 = SummaryCard( |     if (_showProjections) { | ||||||
|       name: "Three months from now...", |       double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, | ||||||
|       leftText: "", |           threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), | ||||||
|       middleText: threeMonths.toStringAsFixed(2), |           sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), | ||||||
|       rightText: "", |           oneYear = _assetTotal + (_incomeYearly - _expenseYearly), | ||||||
|     ); |           twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)), | ||||||
|     Widget proj3 = SummaryCard( |           fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly)); | ||||||
|       name: "Half a year from now...", |  | ||||||
|       leftText: "", |       // Widgets to show the projections. | ||||||
|       middleText: sixMonths.toStringAsFixed(2), |       Widget proj1 = SummaryCard( | ||||||
|       rightText: "", |         name: "One month from now...", | ||||||
|     ); |         leftText: "", | ||||||
|     Widget proj4 = SummaryCard( |         middleText: oneMonth.toStringAsFixed(2), | ||||||
|       name: "One year from now...", |         rightText: "", | ||||||
|       leftText: "", |       ); | ||||||
|       middleText: oneYear.toStringAsFixed(2), |       Widget proj2 = SummaryCard( | ||||||
|       rightText: "", |         name: "Three months from now...", | ||||||
|     ); |         leftText: "", | ||||||
|     Widget proj5 = SummaryCard( |         middleText: threeMonths.toStringAsFixed(2), | ||||||
|       name: "Two years from now...", |         rightText: "", | ||||||
|       leftText: "", |       ); | ||||||
|       middleText: twoYears.toStringAsFixed(2), |       Widget proj3 = SummaryCard( | ||||||
|       rightText: "", |         name: "Half a year from now...", | ||||||
|     ); |         leftText: "", | ||||||
|     Widget proj6 = SummaryCard( |         middleText: sixMonths.toStringAsFixed(2), | ||||||
|       name: "Five years from now...", |         rightText: "", | ||||||
|       leftText: "", |       ); | ||||||
|       middleText: fiveYears.toStringAsFixed(2), |       Widget proj4 = SummaryCard( | ||||||
|       rightText: "", |         name: "One year from now...", | ||||||
|     ); |         leftText: "", | ||||||
|  |         middleText: oneYear.toStringAsFixed(2), | ||||||
|  |         rightText: "", | ||||||
|  |       ); | ||||||
|  |       Widget proj5 = SummaryCard( | ||||||
|  |         name: "Two years from now...", | ||||||
|  |         leftText: "", | ||||||
|  |         middleText: twoYears.toStringAsFixed(2), | ||||||
|  |         rightText: "", | ||||||
|  |       ); | ||||||
|  |       Widget proj6 = SummaryCard( | ||||||
|  |         name: "Five years from now...", | ||||||
|  |         leftText: "", | ||||||
|  |         middleText: fiveYears.toStringAsFixed(2), | ||||||
|  |         rightText: "", | ||||||
|  |       ); | ||||||
|  |  | ||||||
|  |       projections = Column( | ||||||
|  |         children: [ | ||||||
|  |           proj1, | ||||||
|  |           proj2, | ||||||
|  |           proj3, | ||||||
|  |           proj4, | ||||||
|  |           proj5, | ||||||
|  |           proj6, | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } else { | ||||||
|  |       projections = Center( | ||||||
|  |         child: SizedBox( | ||||||
|  |           child: CircularProgressIndicator(), | ||||||
|  |         ), | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Return all of the UI elements. |     // Return all of the UI elements. | ||||||
|     return ListView( |     return ListView( | ||||||
| @@ -99,12 +144,7 @@ class _ProjectionPageState extends State<ProjectionPage> { | |||||||
|         incomeSummary, |         incomeSummary, | ||||||
|         assetSummary, |         assetSummary, | ||||||
|         TitleCard(title: "Projections"), |         TitleCard(title: "Projections"), | ||||||
|         proj1, |         projections, | ||||||
|         proj2, |  | ||||||
|         proj3, |  | ||||||
|         proj4, |  | ||||||
|         proj5, |  | ||||||
|         proj6, |  | ||||||
|       ], |       ], | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
| @@ -114,14 +154,16 @@ class SummaryCardForTotals extends StatelessWidget { | |||||||
|   const SummaryCardForTotals({ |   const SummaryCardForTotals({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.list, |     required this.list, | ||||||
|     required this.summaryTypeLabel, |     required this.itemType, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   final Future<List<TrackedItem>> list; |   final Future<List<TrackedItem>> list; | ||||||
|   final String summaryTypeLabel; |   final ItemType itemType; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|  |     String summaryTypeLabel = itemType.title.toString(); | ||||||
|  |  | ||||||
|     return FutureBuilder<List<TrackedItem>>( |     return FutureBuilder<List<TrackedItem>>( | ||||||
|         future: list, |         future: list, | ||||||
|         builder: ( |         builder: ( | ||||||
| @@ -134,12 +176,9 @@ class SummaryCardForTotals extends StatelessWidget { | |||||||
|  |  | ||||||
|           // Calculate the total fields based on item type. |           // Calculate the total fields based on item type. | ||||||
|           double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0; |           double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0; | ||||||
|           ItemType? itemType; |  | ||||||
|           for (TrackedItem e in snapshot.data!) { |           for (TrackedItem e in snapshot.data!) { | ||||||
|             if (itemType == null) { |             if (e.type != itemType) { | ||||||
|               itemType = e.type!; |               throw "List in SummaryCardForTotals has incorrect item types, abort!"; | ||||||
|             } else if (itemType != e.type) { |  | ||||||
|               throw "List in SummaryCardForTotals has multiple item types, abort!"; |  | ||||||
|             } |             } | ||||||
|  |  | ||||||
|             if (e.type == ItemType.asset) { |             if (e.type == ItemType.asset) { | ||||||
| @@ -153,9 +192,6 @@ class SummaryCardForTotals extends StatelessWidget { | |||||||
|  |  | ||||||
|           /* Load page variables based on calculated totals. */ |           /* Load page variables based on calculated totals. */ | ||||||
|           switch (itemType) { |           switch (itemType) { | ||||||
|             case null: |  | ||||||
|               break; |  | ||||||
|  |  | ||||||
|             case ItemType.asset: |             case ItemType.asset: | ||||||
|               _assetTotal = monthlyTotal; |               _assetTotal = monthlyTotal; | ||||||
|               break; |               break; | ||||||
|   | |||||||
| @@ -1,5 +1,9 @@ | |||||||
|  | // Flutter | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
|  | // Local | ||||||
|  | import '/widgets/cards.dart'; | ||||||
|  |  | ||||||
| /// TODO: | /// TODO: | ||||||
| /// - Export DB (JSON?) | /// - Export DB (JSON?) | ||||||
| /// - Import DB (JSON?) | /// - Import DB (JSON?) | ||||||
| @@ -14,8 +18,14 @@ class SettingsPage extends StatelessWidget { | |||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     return Center( |     return Center( | ||||||
|       child: Text( |       child: Column( | ||||||
|         "No settings yet. :)", |         mainAxisSize: MainAxisSize.max, | ||||||
|  |         children: [ | ||||||
|  |           TitleCard(title: "Settings"), | ||||||
|  |           Text( | ||||||
|  |             "No settings exist yet. :)", | ||||||
|  |           ), | ||||||
|  |         ], | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -9,14 +9,15 @@ 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'; | ||||||
|  | import '/widgets/cards.dart'; | ||||||
|  |  | ||||||
| class TrackedItemPage extends StatefulWidget { | class TrackedItemPage extends StatefulWidget { | ||||||
|   final Future<List<TrackedItem>> assetsToLoad; |   final ItemType assetType; | ||||||
|   final Function() notifyParent; |   final Function() notifyParent; | ||||||
|  |  | ||||||
|   const TrackedItemPage({ |   const TrackedItemPage({ | ||||||
|     super.key, |     super.key, | ||||||
|     required this.assetsToLoad, |     required this.assetType, | ||||||
|     required this.notifyParent, |     required this.notifyParent, | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
| @@ -25,12 +26,28 @@ class TrackedItemPage extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _TrackedItemPageState extends State<TrackedItemPage> { | class _TrackedItemPageState extends State<TrackedItemPage> { | ||||||
|  |   late Future<List<TrackedItem>> _assetsToLoad; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
|     final theme = Theme.of(context); |     final theme = Theme.of(context); | ||||||
|  |  | ||||||
|  |     switch (widget.assetType) { | ||||||
|  |       case ItemType.expense: | ||||||
|  |         _assetsToLoad = DatabaseHelper.instance.getExpenses(); | ||||||
|  |         break; | ||||||
|  |       case ItemType.income: | ||||||
|  |         _assetsToLoad = DatabaseHelper.instance.getIncomes(); | ||||||
|  |         break; | ||||||
|  |       case ItemType.asset: | ||||||
|  |         _assetsToLoad = DatabaseHelper.instance.getAssets(); | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         throw UnimplementedError("Unsure whch asset group to load."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return FutureBuilder<List<TrackedItem>>( |     return FutureBuilder<List<TrackedItem>>( | ||||||
|         future: widget.assetsToLoad, |         future: _assetsToLoad, | ||||||
|         builder: |         builder: | ||||||
|             (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) { |             (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) { | ||||||
|           if (!snapshot.hasData) { |           if (!snapshot.hasData) { | ||||||
| @@ -46,204 +63,230 @@ class _TrackedItemPageState extends State<TrackedItemPage> { | |||||||
|                   "Add items to get started.", |                   "Add items to get started.", | ||||||
|                   softWrap: true, |                   softWrap: true, | ||||||
|                 ) |                 ) | ||||||
|               : ListView.builder( |               : Column( | ||||||
|                   itemCount: snapshot.data!.length, |                   children: [ | ||||||
|                   itemBuilder: (_, index) { |                     TitleCard(title: widget.assetType.plural), | ||||||
|                     final TrackedItem curr = snapshot.data![index]; |                     /*Text( | ||||||
|  |                       "${widget.assetType.description}", | ||||||
|                     final itemKey = Key(curr.id!.toString()); |                       style: TextStyle( | ||||||
|                     final String itemTitle = curr.name; |                         fontSize: 16.0, | ||||||
|  |                         decoration: TextDecoration.none, | ||||||
|                     final String itemAmount; |                         fontWeight: FontWeight.bold, | ||||||
|                     if (curr.frequency != null) { |  | ||||||
|                       itemAmount = |  | ||||||
|                           "${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}"; |  | ||||||
|                     } else { |  | ||||||
|                       itemAmount = curr.amount.toStringAsFixed(2); |  | ||||||
|                     } |  | ||||||
|                     final String itemDescription = curr.description; |  | ||||||
|  |  | ||||||
|                     final double itemDayAmount, itemMonthAmount, itemYearAmount; |  | ||||||
|                     final String estimateSymbolDaily, |  | ||||||
|                         estimateSymbolMonthly, |  | ||||||
|                         estimateSymbolYearly; |  | ||||||
|  |  | ||||||
|                     if (curr.frequency != null) { |  | ||||||
|                       itemDayAmount = curr.calcComparableAmountDaily(); |  | ||||||
|                       estimateSymbolDaily = curr.frequency!.numDays |  | ||||||
|                                   .toStringAsFixed(2) |  | ||||||
|                                   .endsWith(".00") && |  | ||||||
|                               itemDayAmount.toStringAsFixed(3).endsWith("0") |  | ||||||
|                           ? "" |  | ||||||
|                           : "~"; |  | ||||||
|  |  | ||||||
|                       itemMonthAmount = |  | ||||||
|                           (curr.calcComparableAmountYearly() / 12); |  | ||||||
|                       estimateSymbolMonthly = curr.frequency!.timesPerYear |  | ||||||
|                                   .toStringAsFixed(2) |  | ||||||
|                                   .endsWith(".00") && |  | ||||||
|                               itemMonthAmount.toStringAsFixed(3).endsWith("0") |  | ||||||
|                           ? "" |  | ||||||
|                           : "~"; |  | ||||||
|  |  | ||||||
|                       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 monthlyTitle = curr.type == ItemType.asset |  | ||||||
|                         ? "" |  | ||||||
|                         : " ${Frequency.monthly.title}"; |  | ||||||
|  |  | ||||||
|                     final String itemTopText = itemDayAmount < 0 |  | ||||||
|                         ? "" |  | ||||||
|                         : "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}"; |  | ||||||
|                     final String itemMiddleText = itemMonthAmount < 0 |  | ||||||
|                         ? "" |  | ||||||
|                         : "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle"; |  | ||||||
|                     final String itemBottomText = itemYearAmount < 0 |  | ||||||
|                         ? "" |  | ||||||
|                         : "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}"; |  | ||||||
|  |  | ||||||
|                     return Padding( |  | ||||||
|                       padding: const EdgeInsets.all(4.0), |  | ||||||
|                       child: Dismissible( |  | ||||||
|                         key: itemKey, |  | ||||||
|                         background: Container( |  | ||||||
|                           color: Colors.red, |  | ||||||
|                           child: Row( |  | ||||||
|                             mainAxisAlignment: MainAxisAlignment.start, |  | ||||||
|                             children: [ |  | ||||||
|                               Icon(Icons.delete), |  | ||||||
|                               Text("Delete"), |  | ||||||
|                             ], |  | ||||||
|                           ), |  | ||||||
|                         ), |  | ||||||
|                         secondaryBackground: Container( |  | ||||||
|                           color: Colors.orange, |  | ||||||
|                           child: Row( |  | ||||||
|                             mainAxisAlignment: MainAxisAlignment.end, |  | ||||||
|                             children: [ |  | ||||||
|                               Text("Edit"), |  | ||||||
|                               Icon(Icons.edit), |  | ||||||
|                             ], |  | ||||||
|                           ), |  | ||||||
|                         ), |  | ||||||
|                         onDismissed: (direction) { |  | ||||||
|                           setState(() { |  | ||||||
|                             snapshot.data!.remove(curr); |  | ||||||
|                             switch (direction) { |  | ||||||
|                               case DismissDirection.startToEnd: |  | ||||||
|                                 // Remove the item from the database. |  | ||||||
|                                 if (curr is Expense) { |  | ||||||
|                                   DatabaseHelper.instance.removeExpense( |  | ||||||
|                                     curr.id!, |  | ||||||
|                                   ); |  | ||||||
|                                 } else if (curr is Income) { |  | ||||||
|                                   DatabaseHelper.instance.removeIncome( |  | ||||||
|                                     curr.id!, |  | ||||||
|                                   ); |  | ||||||
|                                 } else if (curr is Asset) { |  | ||||||
|                                   DatabaseHelper.instance.removeAsset( |  | ||||||
|                                     curr.id!, |  | ||||||
|                                   ); |  | ||||||
|                                 } 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: TrackedItemInputDialog( |  | ||||||
|                                       notifyParent: widget.notifyParent, |  | ||||||
|                                       entry: curr, |  | ||||||
|                                       amountText: curr.getAmountText(), |  | ||||||
|                                       type: curr.type!, |  | ||||||
|                                     ), |  | ||||||
|                                   ), |  | ||||||
|                                 ); |  | ||||||
|                                 break; |  | ||||||
|                               default: |  | ||||||
|                                 UnimplementedError( |  | ||||||
|                                   "Direction ${direction.toString()} not recognized.", |  | ||||||
|                                 ); |  | ||||||
|                             } |  | ||||||
|                           }); |  | ||||||
|                         }, |  | ||||||
|                         child: Container( |  | ||||||
|                           decoration: BoxDecoration( |  | ||||||
|                             borderRadius: BorderRadius.circular(4), |  | ||||||
|                             color: theme.colorScheme.onPrimary, |  | ||||||
|                           ), |  | ||||||
|                           child: Padding( |  | ||||||
|                             padding: const EdgeInsets.all(4.0), |  | ||||||
|                             child: Row( |  | ||||||
|                               mainAxisSize: MainAxisSize.max, |  | ||||||
|                               children: [ |  | ||||||
|                                 Column( |  | ||||||
|                                   mainAxisSize: MainAxisSize.min, |  | ||||||
|                                   crossAxisAlignment: CrossAxisAlignment.start, |  | ||||||
|                                   children: [ |  | ||||||
|                                     Text( |  | ||||||
|                                       itemTitle, |  | ||||||
|                                       style: TextStyle(fontSize: 20.0), |  | ||||||
|                                     ), |  | ||||||
|                                     Text( |  | ||||||
|                                       itemAmount, |  | ||||||
|                                       style: TextStyle(fontSize: 12.0), |  | ||||||
|                                     ), |  | ||||||
|                                   ], |  | ||||||
|                                 ), |  | ||||||
|                                 Expanded( |  | ||||||
|                                   child: Center( |  | ||||||
|                                     child: Text( |  | ||||||
|                                       itemDescription, |  | ||||||
|                                       style: TextStyle( |  | ||||||
|                                         fontSize: 12.0, |  | ||||||
|                                       ), |  | ||||||
|                                       softWrap: true, |  | ||||||
|                                       textAlign: TextAlign.center, |  | ||||||
|                                     ), |  | ||||||
|                                   ), |  | ||||||
|                                 ), |  | ||||||
|                                 Column( |  | ||||||
|                                   crossAxisAlignment: CrossAxisAlignment.end, |  | ||||||
|                                   children: [ |  | ||||||
|                                     Text( |  | ||||||
|                                       itemTopText, |  | ||||||
|                                       style: TextStyle(fontSize: 12.0), |  | ||||||
|                                     ), |  | ||||||
|                                     Text( |  | ||||||
|                                       itemMiddleText, |  | ||||||
|                                       style: TextStyle(fontSize: 12.0), |  | ||||||
|                                     ), |  | ||||||
|                                     Text( |  | ||||||
|                                       itemBottomText, |  | ||||||
|                                       style: TextStyle(fontSize: 12.0), |  | ||||||
|                                     ), |  | ||||||
|                                   ], |  | ||||||
|                                 ), |  | ||||||
|                               ], |  | ||||||
|                             ), |  | ||||||
|                           ), |  | ||||||
|                         ), |  | ||||||
|                       ), |                       ), | ||||||
|                     ); |                     ),*/ | ||||||
|                   }, |                     Expanded( | ||||||
|  |                       child: ListView.builder( | ||||||
|  |                         itemCount: snapshot.data!.length, | ||||||
|  |                         itemBuilder: (_, index) { | ||||||
|  |                           final TrackedItem curr = snapshot.data![index]; | ||||||
|  |  | ||||||
|  |                           final itemKey = Key(curr.id!.toString()); | ||||||
|  |                           final String itemTitle = curr.name; | ||||||
|  |  | ||||||
|  |                           final String itemAmount; | ||||||
|  |                           if (curr.frequency != null) { | ||||||
|  |                             itemAmount = | ||||||
|  |                                 "${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}"; | ||||||
|  |                           } else { | ||||||
|  |                             itemAmount = curr.amount.toStringAsFixed(2); | ||||||
|  |                           } | ||||||
|  |                           final String itemDescription = curr.description; | ||||||
|  |  | ||||||
|  |                           final double itemDayAmount, | ||||||
|  |                               itemMonthAmount, | ||||||
|  |                               itemYearAmount; | ||||||
|  |                           final String estimateSymbolDaily, | ||||||
|  |                               estimateSymbolMonthly, | ||||||
|  |                               estimateSymbolYearly; | ||||||
|  |  | ||||||
|  |                           if (curr.frequency != null) { | ||||||
|  |                             itemDayAmount = curr.calcComparableAmountDaily(); | ||||||
|  |                             estimateSymbolDaily = curr.frequency!.numDays | ||||||
|  |                                         .toStringAsFixed(2) | ||||||
|  |                                         .endsWith(".00") && | ||||||
|  |                                     itemDayAmount | ||||||
|  |                                         .toStringAsFixed(3) | ||||||
|  |                                         .endsWith("0") | ||||||
|  |                                 ? "" | ||||||
|  |                                 : "~"; | ||||||
|  |  | ||||||
|  |                             itemMonthAmount = | ||||||
|  |                                 (curr.calcComparableAmountYearly() / 12); | ||||||
|  |                             estimateSymbolMonthly = curr.frequency!.timesPerYear | ||||||
|  |                                         .toStringAsFixed(2) | ||||||
|  |                                         .endsWith(".00") && | ||||||
|  |                                     itemMonthAmount | ||||||
|  |                                         .toStringAsFixed(3) | ||||||
|  |                                         .endsWith("0") | ||||||
|  |                                 ? "" | ||||||
|  |                                 : "~"; | ||||||
|  |  | ||||||
|  |                             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 monthlyTitle = | ||||||
|  |                               curr.type == ItemType.asset | ||||||
|  |                                   ? "" | ||||||
|  |                                   : " ${Frequency.monthly.title}"; | ||||||
|  |  | ||||||
|  |                           final String itemTopText = itemDayAmount < 0 | ||||||
|  |                               ? "" | ||||||
|  |                               : "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}"; | ||||||
|  |                           final String itemMiddleText = itemMonthAmount < 0 | ||||||
|  |                               ? "" | ||||||
|  |                               : "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle"; | ||||||
|  |                           final String itemBottomText = itemYearAmount < 0 | ||||||
|  |                               ? "" | ||||||
|  |                               : "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}"; | ||||||
|  |  | ||||||
|  |                           return Padding( | ||||||
|  |                             padding: const EdgeInsets.all(4.0), | ||||||
|  |                             child: Dismissible( | ||||||
|  |                               key: itemKey, | ||||||
|  |                               background: Container( | ||||||
|  |                                 color: Colors.red, | ||||||
|  |                                 child: Row( | ||||||
|  |                                   mainAxisAlignment: MainAxisAlignment.start, | ||||||
|  |                                   children: [ | ||||||
|  |                                     Icon(Icons.delete), | ||||||
|  |                                     Text("Delete"), | ||||||
|  |                                   ], | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               secondaryBackground: Container( | ||||||
|  |                                 color: Colors.orange, | ||||||
|  |                                 child: Row( | ||||||
|  |                                   mainAxisAlignment: MainAxisAlignment.end, | ||||||
|  |                                   children: [ | ||||||
|  |                                     Text("Edit"), | ||||||
|  |                                     Icon(Icons.edit), | ||||||
|  |                                   ], | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                               onDismissed: (direction) { | ||||||
|  |                                 setState(() { | ||||||
|  |                                   snapshot.data!.remove(curr); | ||||||
|  |                                   switch (direction) { | ||||||
|  |                                     case DismissDirection.startToEnd: | ||||||
|  |                                       // Remove the item from the database. | ||||||
|  |                                       if (curr is Expense) { | ||||||
|  |                                         DatabaseHelper.instance.removeExpense( | ||||||
|  |                                           curr.id!, | ||||||
|  |                                         ); | ||||||
|  |                                       } else if (curr is Income) { | ||||||
|  |                                         DatabaseHelper.instance.removeIncome( | ||||||
|  |                                           curr.id!, | ||||||
|  |                                         ); | ||||||
|  |                                       } else if (curr is Asset) { | ||||||
|  |                                         DatabaseHelper.instance.removeAsset( | ||||||
|  |                                           curr.id!, | ||||||
|  |                                         ); | ||||||
|  |                                       } 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: TrackedItemInputDialog( | ||||||
|  |                                             notifyParent: widget.notifyParent, | ||||||
|  |                                             entry: curr, | ||||||
|  |                                             amountText: curr.getAmountText(), | ||||||
|  |                                             type: curr.type!, | ||||||
|  |                                           ), | ||||||
|  |                                         ), | ||||||
|  |                                       ); | ||||||
|  |                                       break; | ||||||
|  |                                     default: | ||||||
|  |                                       UnimplementedError( | ||||||
|  |                                         "Direction ${direction.toString()} not recognized.", | ||||||
|  |                                       ); | ||||||
|  |                                   } | ||||||
|  |                                 }); | ||||||
|  |                               }, | ||||||
|  |                               child: Container( | ||||||
|  |                                 decoration: BoxDecoration( | ||||||
|  |                                   borderRadius: BorderRadius.circular(4), | ||||||
|  |                                   color: theme.colorScheme.onPrimary, | ||||||
|  |                                 ), | ||||||
|  |                                 child: Padding( | ||||||
|  |                                   padding: const EdgeInsets.all(4.0), | ||||||
|  |                                   child: Row( | ||||||
|  |                                     mainAxisSize: MainAxisSize.max, | ||||||
|  |                                     children: [ | ||||||
|  |                                       Column( | ||||||
|  |                                         mainAxisSize: MainAxisSize.min, | ||||||
|  |                                         crossAxisAlignment: | ||||||
|  |                                             CrossAxisAlignment.start, | ||||||
|  |                                         children: [ | ||||||
|  |                                           Text( | ||||||
|  |                                             itemTitle, | ||||||
|  |                                             style: TextStyle(fontSize: 20.0), | ||||||
|  |                                           ), | ||||||
|  |                                           Text( | ||||||
|  |                                             itemAmount, | ||||||
|  |                                             style: TextStyle(fontSize: 12.0), | ||||||
|  |                                           ), | ||||||
|  |                                         ], | ||||||
|  |                                       ), | ||||||
|  |                                       Expanded( | ||||||
|  |                                         child: Center( | ||||||
|  |                                           child: Text( | ||||||
|  |                                             itemDescription, | ||||||
|  |                                             style: TextStyle( | ||||||
|  |                                               fontSize: 12.0, | ||||||
|  |                                             ), | ||||||
|  |                                             softWrap: true, | ||||||
|  |                                             textAlign: TextAlign.center, | ||||||
|  |                                           ), | ||||||
|  |                                         ), | ||||||
|  |                                       ), | ||||||
|  |                                       Column( | ||||||
|  |                                         crossAxisAlignment: | ||||||
|  |                                             CrossAxisAlignment.end, | ||||||
|  |                                         children: [ | ||||||
|  |                                           Text( | ||||||
|  |                                             itemTopText, | ||||||
|  |                                             style: TextStyle(fontSize: 12.0), | ||||||
|  |                                           ), | ||||||
|  |                                           Text( | ||||||
|  |                                             itemMiddleText, | ||||||
|  |                                             style: TextStyle(fontSize: 12.0), | ||||||
|  |                                           ), | ||||||
|  |                                           Text( | ||||||
|  |                                             itemBottomText, | ||||||
|  |                                             style: TextStyle(fontSize: 12.0), | ||||||
|  |                                           ), | ||||||
|  |                                         ], | ||||||
|  |                                       ), | ||||||
|  |                                     ], | ||||||
|  |                                   ), | ||||||
|  |                                 ), | ||||||
|  |                               ), | ||||||
|  |                             ), | ||||||
|  |                           ); | ||||||
|  |                         }, | ||||||
|  |                       ), | ||||||
|  |                     ), | ||||||
|  |                   ], | ||||||
|                 ); |                 ); | ||||||
|         }); |         }); | ||||||
|   } |   } | ||||||
| @@ -297,6 +340,21 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> { | |||||||
|     String amountText = |     String amountText = | ||||||
|         widget.amountText != null ? widget.amountText! : TrackedItem.amountText; |         widget.amountText != null ? widget.amountText! : TrackedItem.amountText; | ||||||
|  |  | ||||||
|  |     Future<List<TrackedItem>> items; | ||||||
|  |     switch (_type) { | ||||||
|  |       case ItemType.expense: | ||||||
|  |         items = DatabaseHelper.instance.getExpenses(); | ||||||
|  |         break; | ||||||
|  |       case ItemType.income: | ||||||
|  |         items = DatabaseHelper.instance.getIncomes(); | ||||||
|  |         break; | ||||||
|  |       case ItemType.asset: | ||||||
|  |         items = DatabaseHelper.instance.getAssets(); | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         throw UnimplementedError("Cannot find unimplemented type."); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     return Column( |     return Column( | ||||||
|       // prevent AlertDialog from taking full vertical height. |       // prevent AlertDialog from taking full vertical height. | ||||||
|       mainAxisSize: MainAxisSize.min, |       mainAxisSize: MainAxisSize.min, | ||||||
| @@ -336,14 +394,14 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> { | |||||||
|                 ? Text("New ${_type!.title}") |                 ? Text("New ${_type!.title}") | ||||||
|                 : Text("Edit ${_type!.title}"), |                 : Text("Edit ${_type!.title}"), | ||||||
|           ), |           ), | ||||||
|           content: FutureBuilder<List<Expense>>( |           content: FutureBuilder<List<TrackedItem>>( | ||||||
|               future: DatabaseHelper.instance.getExpenses(), |               future: items, | ||||||
|               builder: (BuildContext context, |               builder: (BuildContext context, | ||||||
|                   AsyncSnapshot<List<Expense>> snapshot) { |                   AsyncSnapshot<List<TrackedItem>> snapshot) { | ||||||
|                 if (!snapshot.hasData) { |                 if (!snapshot.hasData) { | ||||||
|                   return Center(child: Text('Loading...')); |                   return Center(child: Text('Loading...')); | ||||||
|                 } |                 } | ||||||
|                 List<Expense> expenses = snapshot.data!; |                 List<TrackedItem> expenses = snapshot.data!; | ||||||
|                 return Form( |                 return Form( | ||||||
|                   key: _formKey, |                   key: _formKey, | ||||||
|                   child: Column( |                   child: Column( | ||||||
| @@ -391,7 +449,16 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> { | |||||||
|                             return "$amountText must be a valid number."; |                             return "$amountText must be a valid number."; | ||||||
|                           } |                           } | ||||||
|                           if (double.parse(value) < 0) { |                           if (double.parse(value) < 0) { | ||||||
|                             return "Please use the Income page rather than having negative expenses."; |                             switch (_type) { | ||||||
|  |                               case ItemType.expense: | ||||||
|  |                                 return "Please use the Income page."; | ||||||
|  |  | ||||||
|  |                               case ItemType.income: | ||||||
|  |                                 return "Please use the Expense page."; | ||||||
|  |  | ||||||
|  |                               default: | ||||||
|  |                                 break; | ||||||
|  |                             } | ||||||
|                           } |                           } | ||||||
|                           if (double.parse(value) < 0.01) { |                           if (double.parse(value) < 0.01) { | ||||||
|                             return "$amountText must be one hundreth (0.01) or higher."; |                             return "$amountText must be one hundreth (0.01) or higher."; | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | // Flutter | ||||||
| import 'package:flutter/material.dart'; | import 'package:flutter/material.dart'; | ||||||
|  |  | ||||||
| class TitleCard extends StatelessWidget { | class TitleCard extends StatelessWidget { | ||||||
| @@ -15,7 +16,11 @@ class TitleCard extends StatelessWidget { | |||||||
|       child: Center( |       child: Center( | ||||||
|         child: Text( |         child: Text( | ||||||
|           title, |           title, | ||||||
|           style: TextStyle(fontSize: 20), |           style: TextStyle( | ||||||
|  |             fontSize: 20, | ||||||
|  |             fontWeight: FontWeight.bold, | ||||||
|  |             decoration: TextDecoration.underline, | ||||||
|  |           ), | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   | |||||||
| @@ -5,11 +5,13 @@ | |||||||
| import FlutterMacOS | import FlutterMacOS | ||||||
| import Foundation | import Foundation | ||||||
|  |  | ||||||
|  | import package_info_plus | ||||||
| import path_provider_foundation | import path_provider_foundation | ||||||
| import sqflite_darwin | import sqflite_darwin | ||||||
| import url_launcher_macos | import url_launcher_macos | ||||||
|  |  | ||||||
| func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { | ||||||
|  |   FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) | ||||||
|   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) |   PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) | ||||||
|   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) |   SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) | ||||||
|   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) |   UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) | ||||||
|   | |||||||
							
								
								
									
										40
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										40
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -80,6 +80,22 @@ packages: | |||||||
|     description: flutter |     description: flutter | ||||||
|     source: sdk |     source: sdk | ||||||
|     version: "0.0.0" |     version: "0.0.0" | ||||||
|  |   http: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: http | ||||||
|  |       sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "1.3.0" | ||||||
|  |   http_parser: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: http_parser | ||||||
|  |       sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "4.1.2" | ||||||
|   leak_tracker: |   leak_tracker: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -136,6 +152,22 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.16.0" |     version: "1.16.0" | ||||||
|  |   package_info_plus: | ||||||
|  |     dependency: "direct main" | ||||||
|  |     description: | ||||||
|  |       name: package_info_plus | ||||||
|  |       sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "8.3.0" | ||||||
|  |   package_info_plus_platform_interface: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: package_info_plus_platform_interface | ||||||
|  |       sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "3.2.0" | ||||||
|   path: |   path: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -421,6 +453,14 @@ packages: | |||||||
|       url: "https://pub.dev" |       url: "https://pub.dev" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.1.1" |     version: "1.1.1" | ||||||
|  |   win32: | ||||||
|  |     dependency: transitive | ||||||
|  |     description: | ||||||
|  |       name: win32 | ||||||
|  |       sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f | ||||||
|  |       url: "https://pub.dev" | ||||||
|  |     source: hosted | ||||||
|  |     version: "5.12.0" | ||||||
|   xdg_directories: |   xdg_directories: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -1,7 +1,8 @@ | |||||||
| name: expense_tracker | name: expense_tracker | ||||||
|  | homepage: https://git.hyperling.com/me/flutter-expense-tracker | ||||||
| description: Track recurring expenses against income and liquid assets. | description: Track recurring expenses against income and liquid assets. | ||||||
| publish_to: 'none' | publish_to: 'none' | ||||||
| version: 0.1.1 | version: 0.1.3 | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: ^3.6.1 |   sdk: ^3.6.1 | ||||||
| @@ -9,6 +10,7 @@ environment: | |||||||
| dependencies: | dependencies: | ||||||
|   flutter: |   flutter: | ||||||
|     sdk: flutter |     sdk: flutter | ||||||
|  |   package_info_plus: ^8.3.0 | ||||||
|   path: ^1.9.0 |   path: ^1.9.0 | ||||||
|   path_provider: ^2.1.5 |   path_provider: ^2.1.5 | ||||||
|   sqflite: ^2.4.1 |   sqflite: ^2.4.1 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user