Compare commits
	
		
			9 Commits
		
	
	
		
			0.1.1
			...
			c5f1a4e9ba
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| c5f1a4e9ba | |||
| 538a298acd | |||
| cce878ccaa | |||
| 147178e4dd | |||
| c39e09b2b6 | |||
| fa852faadc | |||
| 6f9d0d8afb | |||
| 75cc72678b | |||
| 2970431b91 | 
| @@ -2,6 +2,7 @@ | |||||||
| 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'; | ||||||
| @@ -26,10 +27,32 @@ 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( | ||||||
| @@ -129,6 +152,8 @@ class _HomePageState extends State<HomePage> { | |||||||
|           } |           } | ||||||
|         }); |         }); | ||||||
|       }, |       }, | ||||||
|  |       leading: Text("Menu"), | ||||||
|  |       trailing: Text("v${_packageInfo.version}"), | ||||||
|     ); |     ); | ||||||
|  |  | ||||||
|     Widget main = Container( |     Widget main = Container( | ||||||
|   | |||||||
| @@ -1,4 +1,6 @@ | |||||||
| // 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'; | ||||||
|  |  | ||||||
| @@ -13,11 +15,11 @@ import '/models/tracked_item.dart'; | |||||||
| ///   - Fix bug where editing an item does not reflect immediately when returning to Reports page. | ///   - 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. | ///     - 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 +31,111 @@ 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. |       projections = Center( | ||||||
|     Widget proj1 = SummaryCard( |         child: SizedBox( | ||||||
|       name: "One month from now...", |           child: CircularProgressIndicator(), | ||||||
|       leftText: "", |         ), | ||||||
|       middleText: oneMonth.toStringAsFixed(2), |       ); | ||||||
|       rightText: "", |  | ||||||
|     ); |       Future.delayed(Duration(seconds: 1), () { | ||||||
|     Widget proj2 = SummaryCard( |         setState(() { | ||||||
|       name: "Three months from now...", |           _showProjections = true; | ||||||
|       leftText: "", |         }); | ||||||
|       middleText: threeMonths.toStringAsFixed(2), |       }); | ||||||
|       rightText: "", |     } else { | ||||||
|     ); |       double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, | ||||||
|     Widget proj3 = SummaryCard( |           threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), | ||||||
|       name: "Half a year from now...", |           sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), | ||||||
|       leftText: "", |           oneYear = _assetTotal + (_incomeYearly - _expenseYearly), | ||||||
|       middleText: sixMonths.toStringAsFixed(2), |           twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)), | ||||||
|       rightText: "", |           fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly)); | ||||||
|     ); |  | ||||||
|     Widget proj4 = SummaryCard( |       // Widgets to show the projections. | ||||||
|       name: "One year from now...", |       Widget proj1 = SummaryCard( | ||||||
|       leftText: "", |         name: "One month from now...", | ||||||
|       middleText: oneYear.toStringAsFixed(2), |         leftText: "", | ||||||
|       rightText: "", |         middleText: oneMonth.toStringAsFixed(2), | ||||||
|     ); |         rightText: "", | ||||||
|     Widget proj5 = SummaryCard( |       ); | ||||||
|       name: "Two years from now...", |       Widget proj2 = SummaryCard( | ||||||
|       leftText: "", |         name: "Three months from now...", | ||||||
|       middleText: twoYears.toStringAsFixed(2), |         leftText: "", | ||||||
|       rightText: "", |         middleText: threeMonths.toStringAsFixed(2), | ||||||
|     ); |         rightText: "", | ||||||
|     Widget proj6 = SummaryCard( |       ); | ||||||
|       name: "Five years from now...", |       Widget proj3 = SummaryCard( | ||||||
|       leftText: "", |         name: "Half a year from now...", | ||||||
|       middleText: fiveYears.toStringAsFixed(2), |         leftText: "", | ||||||
|       rightText: "", |         middleText: sixMonths.toStringAsFixed(2), | ||||||
|     ); |         rightText: "", | ||||||
|  |       ); | ||||||
|  |       Widget proj4 = SummaryCard( | ||||||
|  |         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, | ||||||
|  |         ], | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     // Return all of the UI elements. |     // Return all of the UI elements. | ||||||
|     return ListView( |     return ListView( | ||||||
| @@ -99,12 +145,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 +155,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 +177,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 +193,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; | ||||||
|   | |||||||
| @@ -297,6 +297,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 +351,15 @@ 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(), |               // TODO / TBD -- This should no longer only be Expenses. | ||||||
|  |               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 +407,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."; | ||||||
|   | |||||||
| @@ -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