10 Commits

8 changed files with 306 additions and 228 deletions

View File

@ -1 +1,4 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer:
errors:
unreachable_switch_default: ignore

View File

@ -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;
} }

View File

@ -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.")
], ],

View File

@ -9,7 +9,6 @@ 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({
@ -56,7 +55,7 @@ class _HomePageState extends State<HomePage> {
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(
@ -66,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(
@ -76,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(
@ -120,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),

View File

@ -1,6 +1,5 @@
// Flutter // Flutter
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '/models/item_type.dart'; import '/models/item_type.dart';
@ -12,8 +11,6 @@ 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 = -1, double _assetTotal = -1,
_expenseMonthly = -1, _expenseMonthly = -1,
@ -68,18 +65,14 @@ class _ProjectionPageState extends State<ProjectionPage> {
_expenseYearly < 0) { _expenseYearly < 0) {
_showProjections = false; _showProjections = false;
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
Future.delayed(Duration(seconds: 1), () { Future.delayed(Duration(seconds: 1), () {
setState(() { setState(() {
_showProjections = true; _showProjections = true;
}); });
}); });
} else { }
if (_showProjections) {
double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
@ -135,6 +128,12 @@ class _ProjectionPageState extends State<ProjectionPage> {
proj6, proj6,
], ],
); );
} else {
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
} }
// Return all of the UI elements. // Return all of the UI elements.

View File

@ -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. :)",
),
],
), ),
); );
} }

View File

@ -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,7 +63,19 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
"Add items to get started.", "Add items to get started.",
softWrap: true, softWrap: true,
) )
: ListView.builder( : Column(
children: [
TitleCard(title: widget.assetType.plural),
/*Text(
"${widget.assetType.description}",
style: TextStyle(
fontSize: 16.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.bold,
),
),*/
Expanded(
child: ListView.builder(
itemCount: snapshot.data!.length, itemCount: snapshot.data!.length,
itemBuilder: (_, index) { itemBuilder: (_, index) {
final TrackedItem curr = snapshot.data![index]; final TrackedItem curr = snapshot.data![index];
@ -63,7 +92,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
} }
final String itemDescription = curr.description; final String itemDescription = curr.description;
final double itemDayAmount, itemMonthAmount, itemYearAmount; final double itemDayAmount,
itemMonthAmount,
itemYearAmount;
final String estimateSymbolDaily, final String estimateSymbolDaily,
estimateSymbolMonthly, estimateSymbolMonthly,
estimateSymbolYearly; estimateSymbolYearly;
@ -73,7 +104,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolDaily = curr.frequency!.numDays estimateSymbolDaily = curr.frequency!.numDays
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemDayAmount.toStringAsFixed(3).endsWith("0") itemDayAmount
.toStringAsFixed(3)
.endsWith("0")
? "" ? ""
: "~"; : "~";
@ -82,7 +115,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolMonthly = curr.frequency!.timesPerYear estimateSymbolMonthly = curr.frequency!.timesPerYear
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemMonthAmount.toStringAsFixed(3).endsWith("0") itemMonthAmount
.toStringAsFixed(3)
.endsWith("0")
? "" ? ""
: "~"; : "~";
@ -90,7 +125,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolYearly = curr.frequency!.timesPerYear estimateSymbolYearly = curr.frequency!.timesPerYear
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemYearAmount.toStringAsFixed(3).endsWith("0") itemYearAmount
.toStringAsFixed(3)
.endsWith("0")
? "" ? ""
: "~"; : "~";
} else { } else {
@ -102,7 +139,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolYearly = ""; estimateSymbolYearly = "";
} }
final String monthlyTitle = curr.type == ItemType.asset final String monthlyTitle =
curr.type == ItemType.asset
? "" ? ""
: " ${Frequency.monthly.title}"; : " ${Frequency.monthly.title}";
@ -196,7 +234,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
children: [ children: [
Column( Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment:
CrossAxisAlignment.start,
children: [ children: [
Text( Text(
itemTitle, itemTitle,
@ -221,7 +260,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
), ),
), ),
Column( Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment:
CrossAxisAlignment.end,
children: [ children: [
Text( Text(
itemTopText, itemTopText,
@ -244,6 +284,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
), ),
); );
}, },
),
),
],
); );
}); });
} }
@ -352,7 +395,6 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
: Text("Edit ${_type!.title}"), : Text("Edit ${_type!.title}"),
), ),
content: FutureBuilder<List<TrackedItem>>( content: FutureBuilder<List<TrackedItem>>(
// TODO / TBD -- This should no longer only be Expenses.
future: items, future: items,
builder: (BuildContext context, builder: (BuildContext context,
AsyncSnapshot<List<TrackedItem>> snapshot) { AsyncSnapshot<List<TrackedItem>> snapshot) {

View File

@ -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,
),
), ),
), ),
); );