9 Commits

6 changed files with 204 additions and 73 deletions

View File

@ -2,6 +2,7 @@
import 'package:flutter/material.dart';
import '/models/item_type.dart';
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
// Local
import '/pages/tracked_item.dart';
@ -26,10 +27,32 @@ class _HomePageState extends State<HomePage> {
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
Widget build(BuildContext context) {
Widget page;
Widget? dialog;
switch (pageSelected) {
case 0:
page = TrackedItemPage(
@ -129,6 +152,8 @@ class _HomePageState extends State<HomePage> {
}
});
},
leading: Text("Menu"),
trailing: Text("v${_packageInfo.version}"),
);
Widget main = Container(

View File

@ -1,4 +1,6 @@
// Flutter
import 'dart:async';
import 'package:flutter/material.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.
/// - Currently reflects after going back to Reports the 2nd time.
double _assetTotal = 0,
_expenseMonthly = 0,
_expenseYearly = 0,
_incomeMonthly = 0,
_incomeYearly = 0;
double _assetTotal = -1,
_expenseMonthly = -1,
_expenseYearly = -1,
_incomeMonthly = -1,
_incomeYearly = -1;
class ProjectionPage extends StatefulWidget {
const ProjectionPage({
@ -29,23 +31,55 @@ class ProjectionPage extends StatefulWidget {
}
class _ProjectionPageState extends State<ProjectionPage> {
bool _showProjections = true;
@override
void dispose() {
_assetTotal = -2;
_expenseMonthly = -2;
_expenseYearly = -2;
_incomeMonthly = -2;
_incomeYearly = -2;
super.dispose();
}
@override
Widget build(BuildContext context) {
// Summaries for display as well as calculation of totals for projections.
Widget expenseSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getExpenses(),
summaryTypeLabel: ItemType.expense.title,
itemType: ItemType.expense,
);
Widget incomeSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getIncomes(),
summaryTypeLabel: ItemType.income.title,
itemType: ItemType.income,
);
Widget assetSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getAssets(),
summaryTypeLabel: ItemType.asset.title,
itemType: ItemType.asset,
);
// Calculations for the projections.
Widget projections;
if (_assetTotal < 0 ||
_incomeMonthly < 0 ||
_incomeYearly < 0 ||
_expenseMonthly < 0 ||
_expenseYearly < 0) {
_showProjections = false;
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
Future.delayed(Duration(seconds: 1), () {
setState(() {
_showProjections = true;
});
});
} else {
double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
@ -91,14 +125,8 @@ class _ProjectionPageState extends State<ProjectionPage> {
rightText: "",
);
// Return all of the UI elements.
return ListView(
projections = Column(
children: [
TitleCard(title: "Summaries"),
expenseSummary,
incomeSummary,
assetSummary,
TitleCard(title: "Projections"),
proj1,
proj2,
proj3,
@ -108,20 +136,35 @@ class _ProjectionPageState extends State<ProjectionPage> {
],
);
}
// Return all of the UI elements.
return ListView(
children: [
TitleCard(title: "Summaries"),
expenseSummary,
incomeSummary,
assetSummary,
TitleCard(title: "Projections"),
projections,
],
);
}
}
class SummaryCardForTotals extends StatelessWidget {
const SummaryCardForTotals({
super.key,
required this.list,
required this.summaryTypeLabel,
required this.itemType,
});
final Future<List<TrackedItem>> list;
final String summaryTypeLabel;
final ItemType itemType;
@override
Widget build(BuildContext context) {
String summaryTypeLabel = itemType.title.toString();
return FutureBuilder<List<TrackedItem>>(
future: list,
builder: (
@ -134,12 +177,9 @@ class SummaryCardForTotals extends StatelessWidget {
// Calculate the total fields based on item type.
double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
ItemType? itemType;
for (TrackedItem e in snapshot.data!) {
if (itemType == null) {
itemType = e.type!;
} else if (itemType != e.type) {
throw "List in SummaryCardForTotals has multiple item types, abort!";
if (e.type != itemType) {
throw "List in SummaryCardForTotals has incorrect item types, abort!";
}
if (e.type == ItemType.asset) {
@ -153,9 +193,6 @@ class SummaryCardForTotals extends StatelessWidget {
/* Load page variables based on calculated totals. */
switch (itemType) {
case null:
break;
case ItemType.asset:
_assetTotal = monthlyTotal;
break;

View File

@ -297,6 +297,21 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
String 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(
// prevent AlertDialog from taking full vertical height.
mainAxisSize: MainAxisSize.min,
@ -336,14 +351,15 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
? Text("New ${_type!.title}")
: Text("Edit ${_type!.title}"),
),
content: FutureBuilder<List<Expense>>(
future: DatabaseHelper.instance.getExpenses(),
content: FutureBuilder<List<TrackedItem>>(
// TODO / TBD -- This should no longer only be Expenses.
future: items,
builder: (BuildContext context,
AsyncSnapshot<List<Expense>> snapshot) {
AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) {
return Center(child: Text('Loading...'));
}
List<Expense> expenses = snapshot.data!;
List<TrackedItem> expenses = snapshot.data!;
return Form(
key: _formKey,
child: Column(
@ -391,7 +407,16 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
return "$amountText must be a valid number.";
}
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) {
return "$amountText must be one hundreth (0.01) or higher.";

View File

@ -5,11 +5,13 @@
import FlutterMacOS
import Foundation
import package_info_plus
import path_provider_foundation
import sqflite_darwin
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@ -80,6 +80,22 @@ packages:
description: flutter
source: sdk
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:
dependency: transitive
description:
@ -136,6 +152,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: "direct main"
description:
@ -421,6 +453,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev"
source: hosted
version: "5.12.0"
xdg_directories:
dependency: transitive
description:

View File

@ -1,7 +1,8 @@
name: expense_tracker
homepage: https://git.hyperling.com/me/flutter-expense-tracker
description: Track recurring expenses against income and liquid assets.
publish_to: 'none'
version: 0.1.1
version: 0.1.3
environment:
sdk: ^3.6.1
@ -9,6 +10,7 @@ environment:
dependencies:
flutter:
sdk: flutter
package_info_plus: ^8.3.0
path: ^1.9.0
path_provider: ^2.1.5
sqflite: ^2.4.1