Compare commits
10 Commits
c5f1a4e9ba
...
0.1.3
Author | SHA1 | Date | |
---|---|---|---|
70c26be8e3 | |||
b177b6317e | |||
3c52239efc | |||
de4f8306d7 | |||
f77cf7bd38 | |||
716d40c694 | |||
d9f8536f26 | |||
1966f72c93 | |||
e896611bd1 | |||
2d9c93fec4 |
@ -1 +1,4 @@
|
|||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_lints/flutter.yaml
|
||||||
|
analyzer:
|
||||||
|
errors:
|
||||||
|
unreachable_switch_default: ignore
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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.")
|
||||||
],
|
],
|
||||||
|
@ -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),
|
||||||
|
@ -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.
|
||||||
|
@ -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. :)",
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,204 +63,230 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
|
|||||||
"Add items to get started.",
|
"Add items to get started.",
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: Column(
|
||||||
itemCount: snapshot.data!.length,
|
children: [
|
||||||
itemBuilder: (_, index) {
|
TitleCard(title: widget.assetType.plural),
|
||||||
final TrackedItem curr = snapshot.data![index];
|
/*Text(
|
||||||
|
"${widget.assetType.description}",
|
||||||
final itemKey = Key(curr.id!.toString());
|
style: TextStyle(
|
||||||
final String itemTitle = curr.name;
|
fontSize: 16.0,
|
||||||
|
decoration: TextDecoration.none,
|
||||||
final String itemAmount;
|
fontWeight: FontWeight.bold,
|
||||||
if (curr.frequency != null) {
|
|
||||||
itemAmount =
|
|
||||||
"${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}";
|
|
||||||
} else {
|
|
||||||
itemAmount = curr.amount.toStringAsFixed(2);
|
|
||||||
}
|
|
||||||
final String itemDescription = curr.description;
|
|
||||||
|
|
||||||
final double itemDayAmount, itemMonthAmount, itemYearAmount;
|
|
||||||
final String estimateSymbolDaily,
|
|
||||||
estimateSymbolMonthly,
|
|
||||||
estimateSymbolYearly;
|
|
||||||
|
|
||||||
if (curr.frequency != null) {
|
|
||||||
itemDayAmount = curr.calcComparableAmountDaily();
|
|
||||||
estimateSymbolDaily = curr.frequency!.numDays
|
|
||||||
.toStringAsFixed(2)
|
|
||||||
.endsWith(".00") &&
|
|
||||||
itemDayAmount.toStringAsFixed(3).endsWith("0")
|
|
||||||
? ""
|
|
||||||
: "~";
|
|
||||||
|
|
||||||
itemMonthAmount =
|
|
||||||
(curr.calcComparableAmountYearly() / 12);
|
|
||||||
estimateSymbolMonthly = curr.frequency!.timesPerYear
|
|
||||||
.toStringAsFixed(2)
|
|
||||||
.endsWith(".00") &&
|
|
||||||
itemMonthAmount.toStringAsFixed(3).endsWith("0")
|
|
||||||
? ""
|
|
||||||
: "~";
|
|
||||||
|
|
||||||
itemYearAmount = curr.calcComparableAmountYearly();
|
|
||||||
estimateSymbolYearly = curr.frequency!.timesPerYear
|
|
||||||
.toStringAsFixed(2)
|
|
||||||
.endsWith(".00") &&
|
|
||||||
itemYearAmount.toStringAsFixed(3).endsWith("0")
|
|
||||||
? ""
|
|
||||||
: "~";
|
|
||||||
} else {
|
|
||||||
itemDayAmount = -1;
|
|
||||||
estimateSymbolDaily = "";
|
|
||||||
itemMonthAmount = curr.amount;
|
|
||||||
estimateSymbolMonthly = "";
|
|
||||||
itemYearAmount = -1;
|
|
||||||
estimateSymbolYearly = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
final String monthlyTitle = curr.type == ItemType.asset
|
|
||||||
? ""
|
|
||||||
: " ${Frequency.monthly.title}";
|
|
||||||
|
|
||||||
final String itemTopText = itemDayAmount < 0
|
|
||||||
? ""
|
|
||||||
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
|
|
||||||
final String itemMiddleText = itemMonthAmount < 0
|
|
||||||
? ""
|
|
||||||
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle";
|
|
||||||
final String itemBottomText = itemYearAmount < 0
|
|
||||||
? ""
|
|
||||||
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
|
|
||||||
|
|
||||||
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),
|
|
||||||
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(
|
|
||||||
child: 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),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
),*/
|
||||||
},
|
Expanded(
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: snapshot.data!.length,
|
||||||
|
itemBuilder: (_, index) {
|
||||||
|
final TrackedItem curr = snapshot.data![index];
|
||||||
|
|
||||||
|
final itemKey = Key(curr.id!.toString());
|
||||||
|
final String itemTitle = curr.name;
|
||||||
|
|
||||||
|
final String itemAmount;
|
||||||
|
if (curr.frequency != null) {
|
||||||
|
itemAmount =
|
||||||
|
"${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}";
|
||||||
|
} else {
|
||||||
|
itemAmount = curr.amount.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
final String itemDescription = curr.description;
|
||||||
|
|
||||||
|
final double itemDayAmount,
|
||||||
|
itemMonthAmount,
|
||||||
|
itemYearAmount;
|
||||||
|
final String estimateSymbolDaily,
|
||||||
|
estimateSymbolMonthly,
|
||||||
|
estimateSymbolYearly;
|
||||||
|
|
||||||
|
if (curr.frequency != null) {
|
||||||
|
itemDayAmount = curr.calcComparableAmountDaily();
|
||||||
|
estimateSymbolDaily = curr.frequency!.numDays
|
||||||
|
.toStringAsFixed(2)
|
||||||
|
.endsWith(".00") &&
|
||||||
|
itemDayAmount
|
||||||
|
.toStringAsFixed(3)
|
||||||
|
.endsWith("0")
|
||||||
|
? ""
|
||||||
|
: "~";
|
||||||
|
|
||||||
|
itemMonthAmount =
|
||||||
|
(curr.calcComparableAmountYearly() / 12);
|
||||||
|
estimateSymbolMonthly = curr.frequency!.timesPerYear
|
||||||
|
.toStringAsFixed(2)
|
||||||
|
.endsWith(".00") &&
|
||||||
|
itemMonthAmount
|
||||||
|
.toStringAsFixed(3)
|
||||||
|
.endsWith("0")
|
||||||
|
? ""
|
||||||
|
: "~";
|
||||||
|
|
||||||
|
itemYearAmount = curr.calcComparableAmountYearly();
|
||||||
|
estimateSymbolYearly = curr.frequency!.timesPerYear
|
||||||
|
.toStringAsFixed(2)
|
||||||
|
.endsWith(".00") &&
|
||||||
|
itemYearAmount
|
||||||
|
.toStringAsFixed(3)
|
||||||
|
.endsWith("0")
|
||||||
|
? ""
|
||||||
|
: "~";
|
||||||
|
} else {
|
||||||
|
itemDayAmount = -1;
|
||||||
|
estimateSymbolDaily = "";
|
||||||
|
itemMonthAmount = curr.amount;
|
||||||
|
estimateSymbolMonthly = "";
|
||||||
|
itemYearAmount = -1;
|
||||||
|
estimateSymbolYearly = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
final String monthlyTitle =
|
||||||
|
curr.type == ItemType.asset
|
||||||
|
? ""
|
||||||
|
: " ${Frequency.monthly.title}";
|
||||||
|
|
||||||
|
final String itemTopText = itemDayAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
|
||||||
|
final String itemMiddleText = itemMonthAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle";
|
||||||
|
final String itemBottomText = itemYearAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
|
||||||
|
|
||||||
|
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),
|
||||||
|
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(
|
||||||
|
child: 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -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) {
|
||||||
|
@ -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,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user