Compare commits

..

No commits in common. "main" and "0.1.1" have entirely different histories.
main ... 0.1.1

11 changed files with 275 additions and 484 deletions

View File

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

View File

@ -2,26 +2,21 @@ 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: "Income", plural: "Incomes",
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,11 +1,7 @@
// 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)) {
@ -42,34 +38,23 @@ 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 ${ItemType.expense.plural} are defined as ${ItemType.expense.description.toLowerCase()}" "\n\t\t Tracked items can be swiped left to right for ,"
" ${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 Android updates, install Obtanium," "\n\t\t To subscribe to app updates, install the Obtanium"
" then use the URL from the Source Code button below." " app, 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. Linux users" " APKs from the Source Code /releases/ page.",
" currently need to install and update manually."
"",
), ),
//Text("Another paragraph.") //Text("Another paragraph.")
], ],

View File

@ -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,36 +26,14 @@ 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(
assetType: ItemType.expense, assetsToLoad: DatabaseHelper.instance.getExpenses(),
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@ -65,7 +43,7 @@ class _HomePageState extends State<HomePage> {
break; break;
case 1: case 1:
page = TrackedItemPage( page = TrackedItemPage(
assetType: ItemType.income, assetsToLoad: DatabaseHelper.instance.getIncomes(),
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@ -75,7 +53,7 @@ class _HomePageState extends State<HomePage> {
break; break;
case 2: case 2:
page = TrackedItemPage( page = TrackedItemPage(
assetType: ItemType.asset, assetsToLoad: DatabaseHelper.instance.getAssets(),
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@ -119,15 +97,15 @@ class _HomePageState extends State<HomePage> {
destinations: [ destinations: [
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.payment), icon: Icon(Icons.payment),
label: Text(ItemType.expense.plural), label: Text('Expenses'),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.account_balance), icon: Icon(Icons.account_balance),
label: Text(ItemType.income.plural), label: Text('Income'),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.attach_money), icon: Icon(Icons.attach_money),
label: Text(ItemType.asset.plural), label: Text('Liquid Assets'),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.bar_chart), icon: Icon(Icons.bar_chart),
@ -151,8 +129,6 @@ class _HomePageState extends State<HomePage> {
} }
}); });
}, },
leading: Text("Menu"),
trailing: Text("v${_packageInfo.version}"),
); );
Widget main = Container( Widget main = Container(

View File

@ -1,5 +1,4 @@
// 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';
@ -11,12 +10,14 @@ 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 = 0,
_expenseMonthly = -1, _expenseMonthly = 0,
_expenseYearly = -1, _expenseYearly = 0,
_incomeMonthly = -1, _incomeMonthly = 0,
_incomeYearly = -1; _incomeYearly = 0;
class ProjectionPage extends StatefulWidget { class ProjectionPage extends StatefulWidget {
const ProjectionPage({ const ProjectionPage({
@ -28,113 +29,67 @@ 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(),
itemType: ItemType.expense, summaryTypeLabel: ItemType.expense.title,
); );
Widget incomeSummary = SummaryCardForTotals( Widget incomeSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getIncomes(), list: DatabaseHelper.instance.getIncomes(),
itemType: ItemType.income, summaryTypeLabel: ItemType.income.title,
); );
Widget assetSummary = SummaryCardForTotals( Widget assetSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getAssets(), list: DatabaseHelper.instance.getAssets(),
itemType: ItemType.asset, summaryTypeLabel: ItemType.asset.title,
); );
// Calculations for the projections. // Calculations for the projections.
Widget projections; double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
if (_assetTotal < 0 || threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
_incomeMonthly < 0 || sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
_incomeYearly < 0 || oneYear = _assetTotal + (_incomeYearly - _expenseYearly),
_expenseMonthly < 0 || twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)),
_expenseYearly < 0) { fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly));
_showProjections = false;
Future.delayed(Duration(seconds: 1), () { // Widgets to show the projections.
setState(() { Widget proj1 = SummaryCard(
_showProjections = true; name: "One month from now...",
}); leftText: "",
}); middleText: oneMonth.toStringAsFixed(2),
} rightText: "",
);
if (_showProjections) { Widget proj2 = SummaryCard(
double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, name: "Three months from now...",
threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), leftText: "",
sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), middleText: threeMonths.toStringAsFixed(2),
oneYear = _assetTotal + (_incomeYearly - _expenseYearly), rightText: "",
twoYears = _assetTotal + (2 * (_incomeYearly - _expenseYearly)), );
fiveYears = _assetTotal + (5 * (_incomeYearly - _expenseYearly)); Widget proj3 = SummaryCard(
name: "Half a year from now...",
// Widgets to show the projections. leftText: "",
Widget proj1 = SummaryCard( middleText: sixMonths.toStringAsFixed(2),
name: "One month from now...", rightText: "",
leftText: "", );
middleText: oneMonth.toStringAsFixed(2), Widget proj4 = SummaryCard(
rightText: "", name: "One year from now...",
); leftText: "",
Widget proj2 = SummaryCard( middleText: oneYear.toStringAsFixed(2),
name: "Three months from now...", rightText: "",
leftText: "", );
middleText: threeMonths.toStringAsFixed(2), Widget proj5 = SummaryCard(
rightText: "", name: "Two years from now...",
); leftText: "",
Widget proj3 = SummaryCard( middleText: twoYears.toStringAsFixed(2),
name: "Half a year from now...", rightText: "",
leftText: "", );
middleText: sixMonths.toStringAsFixed(2), Widget proj6 = SummaryCard(
rightText: "", name: "Five years from now...",
); leftText: "",
Widget proj4 = SummaryCard( middleText: fiveYears.toStringAsFixed(2),
name: "One year from now...", rightText: "",
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(
@ -144,7 +99,12 @@ class _ProjectionPageState extends State<ProjectionPage> {
incomeSummary, incomeSummary,
assetSummary, assetSummary,
TitleCard(title: "Projections"), TitleCard(title: "Projections"),
projections, proj1,
proj2,
proj3,
proj4,
proj5,
proj6,
], ],
); );
} }
@ -154,16 +114,14 @@ class SummaryCardForTotals extends StatelessWidget {
const SummaryCardForTotals({ const SummaryCardForTotals({
super.key, super.key,
required this.list, required this.list,
required this.itemType, required this.summaryTypeLabel,
}); });
final Future<List<TrackedItem>> list; final Future<List<TrackedItem>> list;
final ItemType itemType; final String summaryTypeLabel;
@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: (
@ -176,9 +134,12 @@ 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 (e.type != itemType) { if (itemType == null) {
throw "List in SummaryCardForTotals has incorrect item types, abort!"; itemType = e.type!;
} else if (itemType != e.type) {
throw "List in SummaryCardForTotals has multiple item types, abort!";
} }
if (e.type == ItemType.asset) { if (e.type == ItemType.asset) {
@ -192,6 +153,9 @@ 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;

View File

@ -1,9 +1,5 @@
// 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?)
@ -18,14 +14,8 @@ class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Center(
child: Column( child: Text(
mainAxisSize: MainAxisSize.max, "No settings yet. :)",
children: [
TitleCard(title: "Settings"),
Text(
"No settings exist yet. :)",
),
],
), ),
); );
} }

View File

@ -9,15 +9,14 @@ 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 ItemType assetType; final Future<List<TrackedItem>> assetsToLoad;
final Function() notifyParent; final Function() notifyParent;
const TrackedItemPage({ const TrackedItemPage({
super.key, super.key,
required this.assetType, required this.assetsToLoad,
required this.notifyParent, required this.notifyParent,
}); });
@ -26,28 +25,12 @@ 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: _assetsToLoad, future: widget.assetsToLoad,
builder: builder:
(BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) { (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
@ -63,230 +46,204 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
"Add items to get started.", "Add items to get started.",
softWrap: true, softWrap: true,
) )
: Column( : ListView.builder(
children: [ itemCount: snapshot.data!.length,
TitleCard(title: widget.assetType.plural), itemBuilder: (_, index) {
/*Text( final TrackedItem curr = snapshot.data![index];
"${widget.assetType.description}",
style: TextStyle(
fontSize: 16.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.bold,
),
),*/
Expanded(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (_, index) {
final TrackedItem curr = snapshot.data![index];
final itemKey = Key(curr.id!.toString()); final itemKey = Key(curr.id!.toString());
final String itemTitle = curr.name; final String itemTitle = curr.name;
final String itemAmount; final String itemAmount;
if (curr.frequency != null) { if (curr.frequency != null) {
itemAmount = itemAmount =
"${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}"; "${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}";
} else { } else {
itemAmount = curr.amount.toStringAsFixed(2); itemAmount = curr.amount.toStringAsFixed(2);
} }
final String itemDescription = curr.description; final String itemDescription = curr.description;
final double itemDayAmount, final double itemDayAmount, itemMonthAmount, itemYearAmount;
itemMonthAmount, final String estimateSymbolDaily,
itemYearAmount; estimateSymbolMonthly,
final String estimateSymbolDaily, estimateSymbolYearly;
estimateSymbolMonthly,
estimateSymbolYearly;
if (curr.frequency != null) { if (curr.frequency != null) {
itemDayAmount = curr.calcComparableAmountDaily(); itemDayAmount = curr.calcComparableAmountDaily();
estimateSymbolDaily = curr.frequency!.numDays estimateSymbolDaily = curr.frequency!.numDays
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemDayAmount itemDayAmount.toStringAsFixed(3).endsWith("0")
.toStringAsFixed(3) ? ""
.endsWith("0") : "~";
? ""
: "~";
itemMonthAmount = itemMonthAmount =
(curr.calcComparableAmountYearly() / 12); (curr.calcComparableAmountYearly() / 12);
estimateSymbolMonthly = curr.frequency!.timesPerYear estimateSymbolMonthly = curr.frequency!.timesPerYear
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemMonthAmount itemMonthAmount.toStringAsFixed(3).endsWith("0")
.toStringAsFixed(3) ? ""
.endsWith("0") : "~";
? ""
: "~";
itemYearAmount = curr.calcComparableAmountYearly(); itemYearAmount = curr.calcComparableAmountYearly();
estimateSymbolYearly = curr.frequency!.timesPerYear estimateSymbolYearly = curr.frequency!.timesPerYear
.toStringAsFixed(2) .toStringAsFixed(2)
.endsWith(".00") && .endsWith(".00") &&
itemYearAmount itemYearAmount.toStringAsFixed(3).endsWith("0")
.toStringAsFixed(3) ? ""
.endsWith("0") : "~";
? "" } else {
: "~"; itemDayAmount = -1;
} else { estimateSymbolDaily = "";
itemDayAmount = -1; itemMonthAmount = curr.amount;
estimateSymbolDaily = ""; estimateSymbolMonthly = "";
itemMonthAmount = curr.amount; itemYearAmount = -1;
estimateSymbolMonthly = ""; estimateSymbolYearly = "";
itemYearAmount = -1; }
estimateSymbolYearly = "";
}
final String monthlyTitle = final String monthlyTitle = curr.type == ItemType.asset
curr.type == ItemType.asset ? ""
? "" : " ${Frequency.monthly.title}";
: " ${Frequency.monthly.title}";
final String itemTopText = itemDayAmount < 0 final String itemTopText = itemDayAmount < 0
? "" ? ""
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}"; : "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
final String itemMiddleText = itemMonthAmount < 0 final String itemMiddleText = itemMonthAmount < 0
? "" ? ""
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle"; : "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle";
final String itemBottomText = itemYearAmount < 0 final String itemBottomText = itemYearAmount < 0
? "" ? ""
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}"; : "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
return Padding( 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), padding: const EdgeInsets.all(4.0),
child: Dismissible( child: Row(
key: itemKey, mainAxisSize: MainAxisSize.max,
background: Container( children: [
color: Colors.red, Column(
child: Row( mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(Icons.delete), Text(
Text("Delete"), itemTitle,
style: TextStyle(fontSize: 20.0),
),
Text(
itemAmount,
style: TextStyle(fontSize: 12.0),
),
], ],
), ),
), Expanded(
secondaryBackground: Container( child: Center(
color: Colors.orange, child: Text(
child: Row( itemDescription,
mainAxisAlignment: MainAxisAlignment.end, style: TextStyle(
children: [ fontSize: 12.0,
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( softWrap: true,
child: Center( textAlign: TextAlign.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),
),
],
),
],
), ),
), ),
), 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),
),
],
),
],
), ),
); ),
}, ),
), ),
), );
], },
); );
}); });
} }
@ -340,21 +297,6 @@ 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,
@ -394,14 +336,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<TrackedItem>>( content: FutureBuilder<List<Expense>>(
future: items, future: DatabaseHelper.instance.getExpenses(),
builder: (BuildContext context, builder: (BuildContext context,
AsyncSnapshot<List<TrackedItem>> snapshot) { AsyncSnapshot<List<Expense>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Center(child: Text('Loading...')); return Center(child: Text('Loading...'));
} }
List<TrackedItem> expenses = snapshot.data!; List<Expense> expenses = snapshot.data!;
return Form( return Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
@ -449,16 +391,7 @@ 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) {
switch (_type) { return "Please use the Income page rather than having negative expenses.";
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.";

View File

@ -1,4 +1,3 @@
// Flutter
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TitleCard extends StatelessWidget { class TitleCard extends StatelessWidget {
@ -16,11 +15,7 @@ class TitleCard extends StatelessWidget {
child: Center( child: Center(
child: Text( child: Text(
title, title,
style: TextStyle( style: TextStyle(fontSize: 20),
fontSize: 20,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
), ),
), ),
); );

View File

@ -5,13 +5,11 @@
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"))

View File

@ -80,22 +80,6 @@ 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:
@ -152,22 +136,6 @@ 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:
@ -453,14 +421,6 @@ 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:

View File

@ -1,8 +1,7 @@
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.3 version: 0.1.1
environment: environment:
sdk: ^3.6.1 sdk: ^3.6.1
@ -10,7 +9,6 @@ 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