Compare commits
4 Commits
064884dc07
...
42548d437c
Author | SHA1 | Date | |
---|---|---|---|
42548d437c | |||
be66f52cbf | |||
992195b9a0 | |||
991eea4d54 |
@ -9,7 +9,7 @@ import 'package:sqflite/sqflite.dart';
|
|||||||
|
|
||||||
// Local
|
// Local
|
||||||
import '/models/expense.dart';
|
import '/models/expense.dart';
|
||||||
import '/models/tracked_type_recurring.dart';
|
import '/models/tracked_item.dart';
|
||||||
|
|
||||||
// Leaned on this example:
|
// Leaned on this example:
|
||||||
// https://learnflutterwithme.com/sqlite
|
// https://learnflutterwithme.com/sqlite
|
||||||
@ -77,7 +77,7 @@ class DatabaseHelper {
|
|||||||
return expenseList;
|
return expenseList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> addExpense(RecurringTrackedType expense) async {
|
Future<int> addExpense(TrackedItem expense) async {
|
||||||
Database db = await instance.db;
|
Database db = await instance.db;
|
||||||
return await db.insert(
|
return await db.insert(
|
||||||
"expense",
|
"expense",
|
||||||
@ -94,7 +94,7 @@ class DatabaseHelper {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<int> updateExpense(RecurringTrackedType expense) async {
|
Future<int> updateExpense(TrackedItem expense) async {
|
||||||
Database db = await instance.db;
|
Database db = await instance.db;
|
||||||
return await db.update(
|
return await db.update(
|
||||||
"expense",
|
"expense",
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import '/models/tracked_type.dart';
|
// Local
|
||||||
|
import '/models/tracked_item.dart';
|
||||||
|
|
||||||
class Asset extends TrackedType {
|
class Asset extends TrackedItem {
|
||||||
static String amountText = "Amount";
|
static String amountText = "Amount";
|
||||||
|
|
||||||
Asset({
|
Asset({
|
||||||
|
@ -29,7 +29,7 @@ class DatabaseBackup {
|
|||||||
'id': e.id,
|
'id': e.id,
|
||||||
'name': e.name,
|
'name': e.name,
|
||||||
'cost': e.amount,
|
'cost': e.amount,
|
||||||
'frequency': e.frequency.title,
|
'frequency': e.frequency!.title,
|
||||||
'description': e.description,
|
'description': e.description,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@ -39,7 +39,7 @@ class DatabaseBackup {
|
|||||||
'id': i.id,
|
'id': i.id,
|
||||||
'name': i.name,
|
'name': i.name,
|
||||||
'revenue': i.amount,
|
'revenue': i.amount,
|
||||||
'frequency': i.frequency.title,
|
'frequency': i.frequency!.title,
|
||||||
'description': i.description,
|
'description': i.description,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
// Local
|
// Local
|
||||||
import '/models/tracked_type_recurring.dart';
|
import 'package:flutter_expense_tracker/models/item_type.dart';
|
||||||
|
|
||||||
|
import '/models/tracked_item.dart';
|
||||||
import '/models/frequency.dart';
|
import '/models/frequency.dart';
|
||||||
|
|
||||||
class Expense extends RecurringTrackedType {
|
class Expense extends TrackedItem {
|
||||||
static String amountText = "Cost";
|
static String amountText = "Cost";
|
||||||
|
|
||||||
Expense({
|
Expense({
|
||||||
super.id,
|
super.id,
|
||||||
|
super.type = ItemType.expense,
|
||||||
required super.name,
|
required super.name,
|
||||||
required super.amount,
|
required super.amount,
|
||||||
required super.frequency,
|
required super.frequency,
|
||||||
@ -29,7 +32,7 @@ class Expense extends RecurringTrackedType {
|
|||||||
'id': id,
|
'id': id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'cost': amount,
|
'cost': amount,
|
||||||
'frequency': frequency.title,
|
'frequency': frequency!.title,
|
||||||
'description': description,
|
'description': description,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
// Local
|
// Local
|
||||||
import '/models/tracked_type_recurring.dart';
|
import '/models/tracked_item.dart';
|
||||||
import '/models/frequency.dart';
|
import '/models/frequency.dart';
|
||||||
|
|
||||||
class Income extends RecurringTrackedType {
|
class Income extends TrackedItem {
|
||||||
static String amountText = "Revenue";
|
static String amountText = "Revenue";
|
||||||
|
|
||||||
Income({
|
Income({
|
||||||
@ -29,7 +29,7 @@ class Income extends RecurringTrackedType {
|
|||||||
'id': id,
|
'id': id,
|
||||||
'name': name,
|
'name': name,
|
||||||
'revenue': amount,
|
'revenue': amount,
|
||||||
'frequency': frequency.title,
|
'frequency': frequency!.title,
|
||||||
'description': description,
|
'description': description,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
22
lib/models/item_type.dart
Normal file
22
lib/models/item_type.dart
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
enum ItemType {
|
||||||
|
expense(
|
||||||
|
title: "Expense",
|
||||||
|
plural: "Expenses",
|
||||||
|
),
|
||||||
|
income(
|
||||||
|
title: "Income",
|
||||||
|
plural: "Incomes",
|
||||||
|
),
|
||||||
|
asset(
|
||||||
|
title: "Asset",
|
||||||
|
plural: "Assets",
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemType({
|
||||||
|
required this.title,
|
||||||
|
required this.plural,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String title;
|
||||||
|
final String plural;
|
||||||
|
}
|
53
lib/models/tracked_item.dart
Normal file
53
lib/models/tracked_item.dart
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
// Local
|
||||||
|
import 'package:flutter_expense_tracker/models/item_type.dart';
|
||||||
|
|
||||||
|
import '/models/frequency.dart';
|
||||||
|
|
||||||
|
abstract class TrackedItem {
|
||||||
|
int? id;
|
||||||
|
ItemType? type;
|
||||||
|
String name;
|
||||||
|
double amount;
|
||||||
|
Frequency? frequency;
|
||||||
|
String description;
|
||||||
|
|
||||||
|
TrackedItem({
|
||||||
|
this.id,
|
||||||
|
this.type,
|
||||||
|
required this.name,
|
||||||
|
required this.amount,
|
||||||
|
this.frequency,
|
||||||
|
required this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
static String amountText = "Amount";
|
||||||
|
String getAmountText() => amountText;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return toMap().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcComparableAmountYearly() {
|
||||||
|
return frequency == null ? 0 : amount * frequency!.timesPerYear;
|
||||||
|
}
|
||||||
|
|
||||||
|
double calcComparableAmountDaily() {
|
||||||
|
return frequency == null ? 0 : amount / frequency!.numDays;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return frequency == null ? {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'amount': amount,
|
||||||
|
'description': description,
|
||||||
|
} : {
|
||||||
|
'id': id,
|
||||||
|
'name': name,
|
||||||
|
'amount': amount,
|
||||||
|
'frequency': frequency!.title,
|
||||||
|
'description': description,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +0,0 @@
|
|||||||
abstract class TrackedType {
|
|
||||||
int? id;
|
|
||||||
String name;
|
|
||||||
double amount;
|
|
||||||
String description;
|
|
||||||
|
|
||||||
TrackedType({
|
|
||||||
this.id,
|
|
||||||
required this.name,
|
|
||||||
required this.amount,
|
|
||||||
required this.description,
|
|
||||||
});
|
|
||||||
|
|
||||||
static String amountText = "Amount";
|
|
||||||
String getAmountText() => amountText;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() {
|
|
||||||
return toMap().toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'name': name,
|
|
||||||
'amount': amount,
|
|
||||||
'description': description,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,34 +0,0 @@
|
|||||||
// Local
|
|
||||||
import '/models/tracked_type.dart';
|
|
||||||
import '/models/frequency.dart';
|
|
||||||
|
|
||||||
abstract class RecurringTrackedType extends TrackedType {
|
|
||||||
Frequency frequency;
|
|
||||||
|
|
||||||
RecurringTrackedType({
|
|
||||||
super.id,
|
|
||||||
required super.name,
|
|
||||||
required super.amount,
|
|
||||||
required this.frequency,
|
|
||||||
required super.description,
|
|
||||||
});
|
|
||||||
|
|
||||||
double calcComparableAmountYearly() {
|
|
||||||
return amount * frequency.timesPerYear;
|
|
||||||
}
|
|
||||||
|
|
||||||
double calcComparableAmountDaily() {
|
|
||||||
return amount / frequency.numDays;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Map<String, dynamic> toMap() {
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'name': name,
|
|
||||||
'amount': amount,
|
|
||||||
'frequency': frequency.title,
|
|
||||||
'description': description,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,12 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class AssetPage extends StatelessWidget {
|
|
||||||
const AssetPage({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Placeholder();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,14 +1,14 @@
|
|||||||
// Flutter
|
// Flutter
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_expense_tracker/models/item_type.dart';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
// Local
|
// Local
|
||||||
import '/pages/expense.dart';
|
import '/pages/tracked_item.dart';
|
||||||
import '/pages/income.dart';
|
|
||||||
import '/pages/asset.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({
|
||||||
@ -32,14 +32,15 @@ class _HomePageState extends State<HomePage> {
|
|||||||
Widget? dialog;
|
Widget? dialog;
|
||||||
switch (pageSelected) {
|
switch (pageSelected) {
|
||||||
case 0:
|
case 0:
|
||||||
page = ExpensePage();
|
page = TrackedItemPage(assetsToLoad: DatabaseHelper.instance.getExpenses());
|
||||||
dialog = RecurringTrackedTypeInputDialog(
|
dialog = TrackedItemInputDialog(
|
||||||
notifyParent: refresh,
|
notifyParent: refresh,
|
||||||
|
type: ItemType.expense,
|
||||||
);
|
);
|
||||||
case 1:
|
case 1:
|
||||||
page = IncomePage();
|
page = Placeholder();
|
||||||
case 2:
|
case 2:
|
||||||
page = AssetPage();
|
page = Placeholder();
|
||||||
case 3:
|
case 3:
|
||||||
page = ProjectionPage();
|
page = ProjectionPage();
|
||||||
case 4:
|
case 4:
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class IncomePage extends StatelessWidget {
|
|
||||||
const IncomePage({
|
|
||||||
super.key,
|
|
||||||
});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Placeholder();
|
|
||||||
}
|
|
||||||
}
|
|
@ -2,8 +2,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
// Local
|
// Local
|
||||||
import 'package:flutter_expense_tracker/db.dart';
|
import '/db.dart';
|
||||||
import 'package:flutter_expense_tracker/models/tracked_type_recurring.dart';
|
import '/models/tracked_item.dart';
|
||||||
|
|
||||||
/// TODO:
|
/// TODO:
|
||||||
/// - Expenses (total number, totals by day / month / year)
|
/// - Expenses (total number, totals by day / month / year)
|
||||||
@ -57,31 +57,39 @@ class SummaryCardForTotals extends StatelessWidget {
|
|||||||
required this.summaryTypeLabel,
|
required this.summaryTypeLabel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Future<List<RecurringTrackedType>> list;
|
final Future<List<TrackedItem>> list;
|
||||||
final String summaryTypeLabel;
|
final String summaryTypeLabel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return FutureBuilder<List<RecurringTrackedType>>(
|
return FutureBuilder<List<TrackedItem>>(
|
||||||
future: list,
|
future: list,
|
||||||
builder: (
|
builder: (
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AsyncSnapshot<List<RecurringTrackedType>> snapshot,
|
AsyncSnapshot<List<TrackedItem>> snapshot,
|
||||||
) {
|
) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Text('Loading $summaryTypeLabel Section...');
|
return Text('Loading $summaryTypeLabel Section...');
|
||||||
}
|
}
|
||||||
double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
|
double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
|
||||||
for (RecurringTrackedType e in snapshot.data!) {
|
for (TrackedItem e in snapshot.data!) {
|
||||||
dailyTotal += e.calcComparableAmountDaily();
|
dailyTotal += e.calcComparableAmountDaily();
|
||||||
monthlyTotal += e.calcComparableAmountYearly() / 12;
|
monthlyTotal += e.calcComparableAmountYearly() / 12;
|
||||||
yearlyTotal += e.calcComparableAmountYearly();
|
yearlyTotal += e.calcComparableAmountYearly();
|
||||||
}
|
}
|
||||||
|
String dailyEstimate =
|
||||||
|
dailyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~",
|
||||||
|
monthlyEstimate =
|
||||||
|
monthlyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~",
|
||||||
|
yearlyEstimate =
|
||||||
|
yearlyTotal.toStringAsFixed(3).endsWith("0") ? "" : "~";
|
||||||
return SummaryCard(
|
return SummaryCard(
|
||||||
name: "$summaryTypeLabel Totals",
|
name: "$summaryTypeLabel Totals",
|
||||||
leftText: "${dailyTotal.toStringAsFixed(2)} Daily",
|
leftText: "$dailyEstimate${dailyTotal.toStringAsFixed(2)} Daily",
|
||||||
middleText: "${monthlyTotal.toStringAsFixed(2)} Monthly",
|
middleText:
|
||||||
rightText: "${yearlyTotal.toStringAsFixed(2)} Yearly",
|
"$monthlyEstimate${monthlyTotal.toStringAsFixed(2)} Monthly",
|
||||||
|
rightText:
|
||||||
|
"$yearlyEstimate${yearlyTotal.toStringAsFixed(2)} Yearly",
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -104,27 +112,41 @@ class SummaryCard extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Card(
|
return Card(
|
||||||
|
color: Theme.of(context).cardColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Column(
|
child: Card(
|
||||||
children: [
|
color: Theme.of(context).highlightColor,
|
||||||
Row(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Text(leftText),
|
Text(
|
||||||
Spacer(),
|
name,
|
||||||
Center(
|
style: TextStyle(
|
||||||
child: Column(
|
decoration: TextDecoration.underline,
|
||||||
children: [
|
fontSize: 16
|
||||||
Text(name),
|
|
||||||
Text(middleText),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
Spacer(),
|
),
|
||||||
Text(rightText),
|
Row(
|
||||||
],
|
children: [
|
||||||
),
|
Spacer(
|
||||||
],
|
flex: 3,
|
||||||
|
),
|
||||||
|
Text(leftText),
|
||||||
|
Spacer(
|
||||||
|
flex: 1,
|
||||||
|
),
|
||||||
|
Text(middleText),
|
||||||
|
Spacer(
|
||||||
|
flex: 1,
|
||||||
|
),
|
||||||
|
Text(rightText),
|
||||||
|
Spacer(
|
||||||
|
flex: 3,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,25 +1,30 @@
|
|||||||
// Flutter
|
// Flutter
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_expense_tracker/models/asset.dart';
|
||||||
|
import 'package:flutter_expense_tracker/models/income.dart';
|
||||||
|
|
||||||
// Local
|
// Local
|
||||||
import '/models/tracked_type.dart';
|
import '/models/tracked_item.dart';
|
||||||
import '/models/tracked_type_recurring.dart';
|
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';
|
||||||
|
|
||||||
// TODO: Make this a generic UI based on a superclass of Expense, Income, and Assets.
|
// TODO: Make this a generic UI based on a superclass of Expense, Income, and Assets.
|
||||||
|
|
||||||
class ExpensePage extends StatefulWidget {
|
class TrackedItemPage extends StatefulWidget {
|
||||||
const ExpensePage({
|
final Future<List<TrackedItem>> assetsToLoad;
|
||||||
|
|
||||||
|
const TrackedItemPage({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.assetsToLoad,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ExpensePage> createState() => _ExpensePageState();
|
State<TrackedItemPage> createState() => _TrackedItemPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ExpensePageState extends State<ExpensePage> {
|
class _TrackedItemPageState extends State<TrackedItemPage> {
|
||||||
refresh() {
|
refresh() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
@ -28,10 +33,10 @@ class _ExpensePageState extends State<ExpensePage> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
|
|
||||||
return FutureBuilder<List<RecurringTrackedType>>(
|
return FutureBuilder<List<TrackedItem>>(
|
||||||
future: DatabaseHelper.instance.getExpenses(),
|
future: widget.assetsToLoad,
|
||||||
builder: (BuildContext context,
|
builder:
|
||||||
AsyncSnapshot<List<RecurringTrackedType>> snapshot) {
|
(BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Text('Loading...');
|
return Text('Loading...');
|
||||||
}
|
}
|
||||||
@ -42,65 +47,73 @@ class _ExpensePageState extends State<ExpensePage> {
|
|||||||
);
|
);
|
||||||
return snapshot.data!.isEmpty
|
return snapshot.data!.isEmpty
|
||||||
? Text(
|
? Text(
|
||||||
"Add expenses to get started.",
|
"Add items to get started.",
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
)
|
)
|
||||||
: ListView.builder(
|
: ListView.builder(
|
||||||
itemCount: snapshot.data!.length,
|
itemCount: snapshot.data!.length,
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
//List<Expense> expenses = snapshot.data!;
|
final TrackedItem curr = snapshot.data![index];
|
||||||
final RecurringTrackedType 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;
|
||||||
// TODO: How can we do RecurringTrackedType vs TrackedType here
|
if (curr.frequency != null) {
|
||||||
// if the Widgets are expecting RecurringTrackedType, but we
|
|
||||||
// need to be using Frequency? Change to only have one abstract
|
|
||||||
// class and make it nully again? Hmmm...
|
|
||||||
//if (curr is RecurringTrackedType && 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;
|
||||||
curr.calcComparableAmountDaily();
|
final String estimateSymbolDaily,
|
||||||
final String estimateSymbolDaily = curr.frequency.numDays
|
estimateSymbolMonthly,
|
||||||
.toStringAsFixed(2)
|
estimateSymbolYearly;
|
||||||
.endsWith(".00") &&
|
|
||||||
itemDayAmount.toStringAsFixed(3).endsWith("0")
|
|
||||||
? ""
|
|
||||||
: "~";
|
|
||||||
|
|
||||||
final double itemMonthAmount =
|
if (curr.frequency != null) {
|
||||||
(curr.calcComparableAmountYearly() / 12);
|
itemDayAmount = curr.calcComparableAmountDaily();
|
||||||
final String estimateSymbolMonthly = curr
|
estimateSymbolDaily = curr.frequency!.numDays
|
||||||
.frequency.timesPerYear
|
.toStringAsFixed(2)
|
||||||
.toStringAsFixed(2)
|
.endsWith(".00") &&
|
||||||
.endsWith(".00") &&
|
itemDayAmount.toStringAsFixed(3).endsWith("0")
|
||||||
itemMonthAmount.toStringAsFixed(3).endsWith("0")
|
? ""
|
||||||
? ""
|
: "~";
|
||||||
: "~";
|
|
||||||
|
|
||||||
final double itemYearAmount =
|
itemMonthAmount =
|
||||||
curr.calcComparableAmountYearly();
|
(curr.calcComparableAmountYearly() / 12);
|
||||||
final String estimateSymbolYearly = curr
|
estimateSymbolMonthly = curr.frequency!.timesPerYear
|
||||||
.frequency.timesPerYear
|
.toStringAsFixed(2)
|
||||||
.toStringAsFixed(2)
|
.endsWith(".00") &&
|
||||||
.endsWith(".00") &&
|
itemMonthAmount.toStringAsFixed(3).endsWith("0")
|
||||||
itemYearAmount.toStringAsFixed(3).endsWith("0")
|
? ""
|
||||||
? ""
|
: "~";
|
||||||
: "~";
|
|
||||||
|
|
||||||
final String itemTopText =
|
itemYearAmount = curr.calcComparableAmountYearly();
|
||||||
"$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
|
estimateSymbolYearly = curr.frequency!.timesPerYear
|
||||||
final String itemMiddleText =
|
.toStringAsFixed(2)
|
||||||
"$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)} ${Frequency.monthly.title}";
|
.endsWith(".00") &&
|
||||||
final String itemBottomText =
|
itemYearAmount.toStringAsFixed(3).endsWith("0")
|
||||||
"$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
|
? ""
|
||||||
|
: "~";
|
||||||
|
} else {
|
||||||
|
itemDayAmount = -1;
|
||||||
|
estimateSymbolDaily = "";
|
||||||
|
itemMonthAmount = curr.amount;
|
||||||
|
estimateSymbolMonthly = "";
|
||||||
|
itemYearAmount = -1;
|
||||||
|
estimateSymbolYearly = "";
|
||||||
|
}
|
||||||
|
final String itemTopText = itemDayAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
|
||||||
|
final String itemMiddleText = itemMonthAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)} ${Frequency.monthly.title}";
|
||||||
|
final String itemBottomText = itemYearAmount < 0
|
||||||
|
? ""
|
||||||
|
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(4.0),
|
padding: const EdgeInsets.all(4.0),
|
||||||
@ -131,17 +144,28 @@ class _ExpensePageState extends State<ExpensePage> {
|
|||||||
snapshot.data!.remove(curr);
|
snapshot.data!.remove(curr);
|
||||||
switch (direction) {
|
switch (direction) {
|
||||||
case DismissDirection.startToEnd:
|
case DismissDirection.startToEnd:
|
||||||
DatabaseHelper.instance.removeExpense(curr.id!);
|
if (curr is Expense) {
|
||||||
|
DatabaseHelper.instance
|
||||||
|
.removeExpense(curr.id!);
|
||||||
|
} else if (curr is Income) {
|
||||||
|
// TODO
|
||||||
|
} else if (curr is Asset) {
|
||||||
|
// TODO
|
||||||
|
} else {
|
||||||
|
throw UnimplementedError(
|
||||||
|
"Cannot remove unimplemented item type.");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case DismissDirection.endToStart:
|
case DismissDirection.endToStart:
|
||||||
// Open an edit dialog, then remove the item from the list.
|
// Open an edit dialog, then remove the item from the list.
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (_) => AlertDialog(
|
builder: (_) => AlertDialog(
|
||||||
content: RecurringTrackedTypeInputDialog(
|
content: TrackedItemInputDialog(
|
||||||
notifyParent: refresh,
|
notifyParent: refresh,
|
||||||
entry: curr,
|
entry: curr,
|
||||||
amountText: curr.getAmountText(),
|
amountText: curr.getAmountText(),
|
||||||
|
type: curr.type!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -218,26 +242,26 @@ class _ExpensePageState extends State<ExpensePage> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RecurringTrackedTypeInputDialog extends StatefulWidget {
|
class TrackedItemInputDialog extends StatefulWidget {
|
||||||
final Function() notifyParent;
|
final Function() notifyParent;
|
||||||
final RecurringTrackedType? entry;
|
final TrackedItem? entry;
|
||||||
final String? amountText;
|
final String? amountText;
|
||||||
|
final ItemType? type;
|
||||||
|
|
||||||
const RecurringTrackedTypeInputDialog({
|
const TrackedItemInputDialog({
|
||||||
super.key,
|
super.key,
|
||||||
required this.notifyParent,
|
required this.notifyParent,
|
||||||
this.entry,
|
this.entry,
|
||||||
this.amountText,
|
this.amountText,
|
||||||
|
this.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<RecurringTrackedTypeInputDialog> createState() =>
|
State<TrackedItemInputDialog> createState() => _TrackedItemInputDialogState();
|
||||||
_RecurringTrackedTypeInputDialogState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _RecurringTrackedTypeInputDialogState
|
class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
|
||||||
extends State<RecurringTrackedTypeInputDialog> {
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _expenseFormKey = GlobalKey<FormState>();
|
|
||||||
|
|
||||||
int? _id;
|
int? _id;
|
||||||
String _name = "";
|
String _name = "";
|
||||||
@ -247,16 +271,23 @@ class _RecurringTrackedTypeInputDialogState
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (widget.type == null &&
|
||||||
|
(widget.entry != null && widget.entry!.type == null)) {
|
||||||
|
throw FlutterError("No ItemType provided for TrackedItemInputDialog.");
|
||||||
|
}
|
||||||
|
ItemType? _type = widget.type;
|
||||||
|
|
||||||
if (widget.entry != null) {
|
if (widget.entry != null) {
|
||||||
_id = widget.entry!.id;
|
_id = widget.entry!.id;
|
||||||
_name = widget.entry!.name;
|
_name = widget.entry!.name;
|
||||||
_amount = widget.entry!.amount;
|
_amount = widget.entry!.amount;
|
||||||
_freq = widget.entry!.frequency;
|
widget.entry!.frequency == null ? null : _freq = widget.entry!.frequency!;
|
||||||
_desc = widget.entry!.description;
|
_desc = widget.entry!.description;
|
||||||
|
_type = widget.entry!.type!;
|
||||||
}
|
}
|
||||||
|
|
||||||
String amountText =
|
String amountText =
|
||||||
widget.amountText != null ? widget.amountText! : TrackedType.amountText;
|
widget.amountText != null ? widget.amountText! : TrackedItem.amountText;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
// prevent AlertDialog from taking full vertical height.
|
// prevent AlertDialog from taking full vertical height.
|
||||||
@ -268,7 +299,20 @@ class _RecurringTrackedTypeInputDialogState
|
|||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (widget.entry != null) {
|
if (widget.entry != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
DatabaseHelper.instance.addExpense(widget.entry!);
|
switch (_type) {
|
||||||
|
case ItemType.expense:
|
||||||
|
DatabaseHelper.instance.addExpense(widget.entry!);
|
||||||
|
break;
|
||||||
|
case ItemType.income:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
case ItemType.asset:
|
||||||
|
// TODO
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw UnimplementedError(
|
||||||
|
"Cannot add unimplemented type.");
|
||||||
|
}
|
||||||
widget.notifyParent();
|
widget.notifyParent();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -281,7 +325,7 @@ class _RecurringTrackedTypeInputDialogState
|
|||||||
insetPadding: EdgeInsets.all(0),
|
insetPadding: EdgeInsets.all(0),
|
||||||
title: Center(
|
title: Center(
|
||||||
child: widget.entry == null
|
child: widget.entry == null
|
||||||
? Text("New Expense")
|
? Text("New ${_type!.title}")
|
||||||
: Text("Edit Expense"),
|
: Text("Edit Expense"),
|
||||||
),
|
),
|
||||||
content: FutureBuilder<List<Expense>>(
|
content: FutureBuilder<List<Expense>>(
|
||||||
@ -293,7 +337,7 @@ class _RecurringTrackedTypeInputDialogState
|
|||||||
}
|
}
|
||||||
List<Expense> expenses = snapshot.data!;
|
List<Expense> expenses = snapshot.data!;
|
||||||
return Form(
|
return Form(
|
||||||
key: _expenseFormKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@ -416,8 +460,8 @@ class _RecurringTrackedTypeInputDialogState
|
|||||||
Center(
|
Center(
|
||||||
child: ElevatedButton.icon(
|
child: ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_expenseFormKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
_expenseFormKey.currentState!.save();
|
_formKey.currentState!.save();
|
||||||
setState(() {
|
setState(() {
|
||||||
Expense expense = Expense(
|
Expense expense = Expense(
|
||||||
id: _id,
|
id: _id,
|
Loading…
x
Reference in New Issue
Block a user