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