Compare commits
6 Commits
last_versi
...
54cd86c34b
Author | SHA1 | Date | |
---|---|---|---|
54cd86c34b | |||
66fd966de8 | |||
5561f50736 | |||
6b25e6e552 | |||
360a36f024 | |||
ecbac615e9 |
45
lib/db.dart
Normal file
45
lib/db.dart
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
// https://docs.flutter.dev/cookbook/persistence/sqlite
|
||||||
|
|
||||||
|
// SQLite
|
||||||
|
import 'dart:async';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter_expense_tracker/models/frequency.dart';
|
||||||
|
import 'package:path/path.dart';
|
||||||
|
import 'package:sqflite/sqflite.dart';
|
||||||
|
|
||||||
|
// Local
|
||||||
|
import '/models/expense.dart';
|
||||||
|
|
||||||
|
void loadDB() async {
|
||||||
|
// Avoid errors caused by flutter upgrade.
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
final String frequencies =
|
||||||
|
"'${Frequency.values.map((freq) => freq.title).join("','")}'";
|
||||||
|
print(frequencies);
|
||||||
|
|
||||||
|
// Open the database and store the reference.
|
||||||
|
final database = openDatabase(
|
||||||
|
// Set the path to the database. Note: Using the `join` function from the
|
||||||
|
// `path` package is best practice to ensure the path is correctly
|
||||||
|
// constructed for each platform.
|
||||||
|
join(await getDatabasesPath(), 'expense_tracker.db'),
|
||||||
|
|
||||||
|
onCreate: (db, version) {
|
||||||
|
// Run the CREATE TABLE statement on the database.
|
||||||
|
return db.execute(
|
||||||
|
"""
|
||||||
|
CREATE TABLE expense
|
||||||
|
( id INTEGER PRIMARY KEY
|
||||||
|
, name TEXT
|
||||||
|
, cost DOUBLE
|
||||||
|
, frequency TEXT CHECK(frequency IN ($frequencies) )
|
||||||
|
, description TEXT
|
||||||
|
)""",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
// Set the version. This executes the onCreate function and provides a
|
||||||
|
// path to perform database upgrades and downgrades.
|
||||||
|
version: 1,
|
||||||
|
);
|
||||||
|
}
|
@ -1,15 +1,16 @@
|
|||||||
// Helpful guides:
|
// Flutter
|
||||||
// - https://flutter.dev/docs/cookbook/forms/validation
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Local
|
||||||
import '/pages/expense.dart';
|
import '/pages/expense.dart';
|
||||||
import '/pages/income.dart';
|
import '/pages/income.dart';
|
||||||
import '/pages/asset.dart';
|
import '/pages/asset.dart';
|
||||||
import '/pages/report.dart';
|
import '/pages/report.dart';
|
||||||
import '/pages/settings.dart';
|
import '/pages/settings.dart';
|
||||||
|
import '/db.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
loadDB();
|
||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -41,6 +42,10 @@ class HomePage extends StatefulWidget {
|
|||||||
class _HomePageState extends State<HomePage> {
|
class _HomePageState extends State<HomePage> {
|
||||||
var pageSelected = 0;
|
var pageSelected = 0;
|
||||||
|
|
||||||
|
refresh() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
Widget page;
|
Widget page;
|
||||||
@ -48,7 +53,9 @@ class _HomePageState extends State<HomePage> {
|
|||||||
switch (pageSelected) {
|
switch (pageSelected) {
|
||||||
case 0:
|
case 0:
|
||||||
page = ExpensePage();
|
page = ExpensePage();
|
||||||
dialog = ExpenseInputDialog();
|
dialog = ExpenseInputDialog(
|
||||||
|
notifyParent: refresh,
|
||||||
|
);
|
||||||
case 1:
|
case 1:
|
||||||
page = IncomePage();
|
page = IncomePage();
|
||||||
case 2:
|
case 2:
|
||||||
@ -79,11 +86,8 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
return LayoutBuilder(builder: (context, constraints) {
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
// TODO: Add a drawer instead of nav rail.
|
appBar: AppBar(title: Text("Expense Tracker")),
|
||||||
body: Row(
|
drawer: NavigationRail(
|
||||||
children: [
|
|
||||||
SafeArea(
|
|
||||||
child: NavigationRail(
|
|
||||||
extended: constraints.maxWidth >= 800,
|
extended: constraints.maxWidth >= 800,
|
||||||
destinations: [
|
destinations: [
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
@ -100,7 +104,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
icon: Icon(Icons.bar_chart),
|
icon: Icon(Icons.bar_chart),
|
||||||
label: Text('Projections'),
|
label: Text('Reports'),
|
||||||
),
|
),
|
||||||
NavigationRailDestination(
|
NavigationRailDestination(
|
||||||
icon: Icon(Icons.settings),
|
icon: Icon(Icons.settings),
|
||||||
@ -111,17 +115,13 @@ class _HomePageState extends State<HomePage> {
|
|||||||
onDestinationSelected: (value) {
|
onDestinationSelected: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
pageSelected = value;
|
pageSelected = value;
|
||||||
|
Navigator.pop(context);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
body: Container(
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
color: Theme.of(context).colorScheme.primaryContainer,
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
child: page,
|
child: Center(child: page),
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
floatingActionButton: floatingButton,
|
floatingActionButton: floatingButton,
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
|
// Flutter
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
// Local
|
||||||
import '/models/expense.dart';
|
import '/models/expense.dart';
|
||||||
import '/models/frequency.dart';
|
import '/models/frequency.dart';
|
||||||
|
|
||||||
@ -12,7 +14,9 @@ class ExpensePage extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ListView.builder(
|
return expenses.isEmpty
|
||||||
|
? Text("Add expenses to get started!")
|
||||||
|
: ListView.builder(
|
||||||
itemCount: expenses.length,
|
itemCount: expenses.length,
|
||||||
itemBuilder: (_, index) {
|
itemBuilder: (_, index) {
|
||||||
final Expense curr = expenses[index];
|
final Expense curr = expenses[index];
|
||||||
@ -33,27 +37,16 @@ class ExpensePage extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
/*
|
|
||||||
return ListView(
|
|
||||||
children: [
|
|
||||||
ListTile(
|
|
||||||
title: Text("Fake Item 1"),
|
|
||||||
subtitle: Text("30.00 / month"),
|
|
||||||
),
|
|
||||||
ListTile(
|
|
||||||
title: Text("Fake Item 2"),
|
|
||||||
subtitle: Text("180.00 / year"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExpenseInputDialog extends StatefulWidget {
|
class ExpenseInputDialog extends StatefulWidget {
|
||||||
|
final Function() notifyParent;
|
||||||
const ExpenseInputDialog({
|
const ExpenseInputDialog({
|
||||||
super.key,
|
super.key,
|
||||||
|
required this.notifyParent,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -70,25 +63,18 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
const inputWidth = 300.0;
|
|
||||||
const inputHeight = 50.0;
|
|
||||||
|
|
||||||
List<DropdownMenuItem> freqValues = [];
|
|
||||||
for (var freq in Frequency.values) {
|
|
||||||
freqValues.add(DropdownMenuItem(value: freq, child: Text(freq.title)));
|
|
||||||
}
|
|
||||||
;
|
|
||||||
|
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: Center(child: Text("Add New Expense")),
|
title: Center(
|
||||||
|
child: Text("New Expense"),
|
||||||
|
),
|
||||||
content: Form(
|
content: Form(
|
||||||
key: _expenseFormKey,
|
key: _expenseFormKey,
|
||||||
autovalidateMode: AutovalidateMode.onUserInteraction,
|
//autovalidateMode: AutovalidateMode.onUserInteraction,
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, spacing: 10, children: [
|
child: Column(
|
||||||
SizedBox(
|
mainAxisSize: MainAxisSize.min,
|
||||||
width: inputWidth,
|
//spacing: 10,
|
||||||
height: inputHeight,
|
children: [
|
||||||
child: TextFormField(
|
TextFormField(
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Name",
|
labelText: "Name",
|
||||||
@ -104,11 +90,7 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
_name = newValue!;
|
_name = newValue!;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
TextFormField(
|
||||||
SizedBox(
|
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: TextFormField(
|
|
||||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Cost", hintText: "Example: 10.00"),
|
labelText: "Cost", hintText: "Example: 10.00"),
|
||||||
@ -125,12 +107,10 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
_cost = double.parse(newValue!);
|
_cost = double.parse(newValue!);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
DropdownButtonFormField(
|
||||||
SizedBox(
|
items: (Frequency.values.map((freq) =>
|
||||||
width: inputWidth,
|
DropdownMenuItem(value: freq, child: Text(freq.title))))
|
||||||
height: inputHeight,
|
.toList(),
|
||||||
child: DropdownButtonFormField(
|
|
||||||
items: freqValues,
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Recurrence", hintText: "Example: Monthly"),
|
labelText: "Recurrence", hintText: "Example: Monthly"),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
@ -143,14 +123,10 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
onChanged: (newValue) {
|
onChanged: (newValue) {
|
||||||
_freq = newValue;
|
_freq = newValue!;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
TextFormField(
|
||||||
SizedBox(
|
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: TextFormField(
|
|
||||||
keyboardType: TextInputType.text,
|
keyboardType: TextInputType.text,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
labelText: "Description",
|
labelText: "Description",
|
||||||
@ -162,20 +138,16 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
_desc = newValue!;
|
_desc = newValue!;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
]),
|
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SizedBox(
|
Row(
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print("TODO: Clear fields!");
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
icon: Icon(Icons.cancel),
|
icon: Icon(Icons.cancel),
|
||||||
@ -183,7 +155,6 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
),
|
),
|
||||||
ElevatedButton.icon(
|
ElevatedButton.icon(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
print("TODO: Save expense!");
|
|
||||||
if (_expenseFormKey.currentState!.validate()) {
|
if (_expenseFormKey.currentState!.validate()) {
|
||||||
_expenseFormKey.currentState!.save();
|
_expenseFormKey.currentState!.save();
|
||||||
setState(() {
|
setState(() {
|
||||||
@ -195,10 +166,7 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
description: _desc),
|
description: _desc),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
print(expenses.toString());
|
widget.notifyParent();
|
||||||
for (var expense in expenses) {
|
|
||||||
print(expense.toString());
|
|
||||||
}
|
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -206,7 +174,6 @@ class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
|
|||||||
label: Text('Submit'),
|
label: Text('Submit'),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -5,6 +5,8 @@
|
|||||||
import FlutterMacOS
|
import FlutterMacOS
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
import sqflite_darwin
|
||||||
|
|
||||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
}
|
}
|
||||||
|
66
pubspec.lock
66
pubspec.lock
@ -131,6 +131,22 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.9.0"
|
version: "1.9.0"
|
||||||
|
platform:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: platform
|
||||||
|
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.1.6"
|
||||||
|
plugin_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: plugin_platform_interface
|
||||||
|
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.8"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
@ -144,6 +160,46 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.10.0"
|
version: "1.10.0"
|
||||||
|
sqflite:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: sqflite
|
||||||
|
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1"
|
||||||
|
sqflite_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_android
|
||||||
|
sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
|
sqflite_common:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_common
|
||||||
|
sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.5.4+6"
|
||||||
|
sqflite_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_darwin
|
||||||
|
sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.1+1"
|
||||||
|
sqflite_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqflite_platform_interface
|
||||||
|
sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.4.0"
|
||||||
stack_trace:
|
stack_trace:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -168,6 +224,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.0"
|
version: "1.3.0"
|
||||||
|
synchronized:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: synchronized
|
||||||
|
sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.3.0+3"
|
||||||
term_glyph:
|
term_glyph:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -202,4 +266,4 @@ packages:
|
|||||||
version: "14.3.0"
|
version: "14.3.0"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.6.1 <4.0.0"
|
dart: ">=3.6.1 <4.0.0"
|
||||||
flutter: ">=3.18.0-18.0.pre.54"
|
flutter: ">=3.24.0"
|
||||||
|
@ -9,6 +9,7 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
sqflite: ^2.4.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Reference in New Issue
Block a user