Compare commits
10 Commits
45b5e33491
...
96811f5bfd
Author | SHA1 | Date | |
---|---|---|---|
96811f5bfd | |||
c44d4553c3 | |||
d722172cb3 | |||
43dd151cb4 | |||
c2cc71eae0 | |||
0c9b365f7f | |||
f820265dba | |||
85b7a0a3e6 | |||
861e8bf904 | |||
aaa95bd3a6 |
@ -6,7 +6,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "com.example.flutter_empty"
|
namespace = "com.hyperling.expense_tracker"
|
||||||
compileSdk = flutter.compileSdkVersion
|
compileSdk = flutter.compileSdkVersion
|
||||||
ndkVersion = flutter.ndkVersion
|
ndkVersion = flutter.ndkVersion
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId = "com.example.flutter_empty"
|
applicationId = "com.hyperling.expense_tracker"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||||
minSdk = flutter.minSdkVersion
|
minSdk = flutter.minSdkVersion
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application
|
<application
|
||||||
android:label="flutter_empty"
|
android:label="Recurring Expense Tracker"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
354
lib/main.dart
354
lib/main.dart
@ -7,86 +7,6 @@ void main() {
|
|||||||
runApp(const MainApp());
|
runApp(const MainApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainApp extends StatefulWidget {
|
|
||||||
const MainApp({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<MainApp> createState() => _MainAppState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _MainAppState extends State<MainApp> {
|
|
||||||
final nameFieldController = TextEditingController();
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
// Clean up the controller when the widget is disposed.
|
|
||||||
nameFieldController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
const inputWidth = 400.0;
|
|
||||||
const inputHeight = 50.0;
|
|
||||||
|
|
||||||
const recurrenceValues = <DropdownMenuEntry>[
|
|
||||||
DropdownMenuEntry(value: Recurrence.daily, label: "Daily"),
|
|
||||||
DropdownMenuEntry(value: Recurrence.weekly, label: "Weekly"),
|
|
||||||
DropdownMenuEntry(value: Recurrence.biweekly, label: "Biweekly"),
|
|
||||||
DropdownMenuEntry(value: Recurrence.montly, label: "Monthly"),
|
|
||||||
DropdownMenuEntry(value: Recurrence.yearly, label: "Yearly"),
|
|
||||||
];
|
|
||||||
|
|
||||||
return const MaterialApp(
|
|
||||||
home: Scaffold(
|
|
||||||
body: Center(
|
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, spacing: 10, children: [
|
|
||||||
Text('Input an expense below!'),
|
|
||||||
SizedBox(
|
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: TextField(
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Name",
|
|
||||||
hintText: "Example: Red Pocket Phone Bill",
|
|
||||||
),
|
|
||||||
// https://docs.flutter.dev/cookbook/forms/retrieve-input
|
|
||||||
//controller: nameFieldController,
|
|
||||||
)),
|
|
||||||
SizedBox(
|
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: TextField(
|
|
||||||
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Cost", hintText: "Example: 10.00"),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DropdownMenu(
|
|
||||||
dropdownMenuEntries: recurrenceValues,
|
|
||||||
width: inputWidth,
|
|
||||||
label: Text("Recurrence"),
|
|
||||||
hintText: "Example: Monthly",
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: inputWidth,
|
|
||||||
height: inputHeight,
|
|
||||||
child: TextField(
|
|
||||||
keyboardType: TextInputType.text,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
labelText: "Description",
|
|
||||||
hintText:
|
|
||||||
"Example: 1GB data with unlimited talk & text."
|
|
||||||
),
|
|
||||||
)),
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// https://www.tutorialspoint.com/dart_programming/dart_programming_enumeration.htm
|
// https://www.tutorialspoint.com/dart_programming/dart_programming_enumeration.htm
|
||||||
enum Recurrence { daily, weekly, biweekly, montly, yearly }
|
enum Recurrence { daily, weekly, biweekly, montly, yearly }
|
||||||
|
|
||||||
@ -98,3 +18,277 @@ class Expense {
|
|||||||
|
|
||||||
Expense(this.name, this.cost, this.recurrence, this.description);
|
Expense(this.name, this.cost, this.recurrence, this.description);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MainApp extends StatelessWidget {
|
||||||
|
const MainApp({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return MaterialApp(
|
||||||
|
title: 'Recurring Expense Tracker',
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
|
||||||
|
),
|
||||||
|
home: HomePage(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class HomePage extends StatefulWidget {
|
||||||
|
const HomePage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<HomePage> createState() => _HomePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _HomePageState extends State<HomePage> {
|
||||||
|
var pageSelected = 0;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Widget page;
|
||||||
|
Widget? dialog;
|
||||||
|
switch (pageSelected) {
|
||||||
|
case 0:
|
||||||
|
page = ExpensePage();
|
||||||
|
dialog = ExpenseInputForm();
|
||||||
|
case 1:
|
||||||
|
page = IncomePage();
|
||||||
|
case 2:
|
||||||
|
page = AssetPage();
|
||||||
|
case 3:
|
||||||
|
page = ProjectionPage();
|
||||||
|
case 4:
|
||||||
|
page = SettingsPage();
|
||||||
|
default:
|
||||||
|
throw UnimplementedError('No widget for page $pageSelected yet!');
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget newValue() {
|
||||||
|
return AlertDialog(title: Text("Add New Value"), content: dialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget? floatingButton;
|
||||||
|
if (dialog != null) {
|
||||||
|
floatingButton = IconButton(
|
||||||
|
onPressed: newValue,
|
||||||
|
icon: Icon(Icons.add),
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(builder: (context, constraints) {
|
||||||
|
return Scaffold(
|
||||||
|
body: Row(
|
||||||
|
children: [
|
||||||
|
SafeArea(
|
||||||
|
child: NavigationRail(
|
||||||
|
extended: constraints.maxWidth >= 800,
|
||||||
|
destinations: [
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.payment),
|
||||||
|
label: Text('Expenses'),
|
||||||
|
),
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.account_balance),
|
||||||
|
label: Text('Income'),
|
||||||
|
),
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.attach_money),
|
||||||
|
label: Text('Liquid Assets'),
|
||||||
|
),
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.bar_chart),
|
||||||
|
label: Text('Projections'),
|
||||||
|
),
|
||||||
|
NavigationRailDestination(
|
||||||
|
icon: Icon(Icons.settings),
|
||||||
|
label: Text('Settings'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selectedIndex: pageSelected,
|
||||||
|
onDestinationSelected: (value) {
|
||||||
|
setState(() {
|
||||||
|
pageSelected = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
color: Theme.of(context).colorScheme.primaryContainer,
|
||||||
|
child: page,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: floatingButton,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpensePage extends StatelessWidget {
|
||||||
|
const ExpensePage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ListView(
|
||||||
|
children: [
|
||||||
|
ExpenseInputForm(),
|
||||||
|
ListTile(
|
||||||
|
title: Text("Fake Item 1"),
|
||||||
|
subtitle: Text("30.00 / month"),
|
||||||
|
),
|
||||||
|
ListTile(
|
||||||
|
title: Text("Fake Item 2"),
|
||||||
|
subtitle: Text("180.00 / year"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ExpenseInputForm extends StatefulWidget {
|
||||||
|
const ExpenseInputForm({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ExpenseInputForm> createState() => _ExpenseInputFormState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpenseInputFormState extends State<ExpenseInputForm> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
const inputWidth = 300.0;
|
||||||
|
const inputHeight = 50.0;
|
||||||
|
|
||||||
|
const recurrenceValues = <DropdownMenuEntry>[
|
||||||
|
DropdownMenuEntry(value: Recurrence.daily, label: "Daily"),
|
||||||
|
DropdownMenuEntry(value: Recurrence.weekly, label: "Weekly"),
|
||||||
|
DropdownMenuEntry(value: Recurrence.biweekly, label: "Biweekly"),
|
||||||
|
DropdownMenuEntry(value: Recurrence.montly, label: "Monthly"),
|
||||||
|
DropdownMenuEntry(value: Recurrence.yearly, label: "Yearly"),
|
||||||
|
];
|
||||||
|
|
||||||
|
return Center(
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, spacing: 10, children: [
|
||||||
|
Text('New Expense'),
|
||||||
|
SizedBox(
|
||||||
|
width: inputWidth,
|
||||||
|
height: inputHeight,
|
||||||
|
child: TextField(
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Name",
|
||||||
|
hintText: "Example: Red Pocket Phone Bill",
|
||||||
|
),
|
||||||
|
// https://docs.flutter.dev/cookbook/forms/retrieve-input
|
||||||
|
//controller: nameFieldController,
|
||||||
|
)),
|
||||||
|
SizedBox(
|
||||||
|
width: inputWidth,
|
||||||
|
height: inputHeight,
|
||||||
|
child: TextField(
|
||||||
|
keyboardType: TextInputType.numberWithOptions(decimal: true),
|
||||||
|
decoration:
|
||||||
|
InputDecoration(labelText: "Cost", hintText: "Example: 10.00"),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenu(
|
||||||
|
dropdownMenuEntries: recurrenceValues,
|
||||||
|
width: inputWidth,
|
||||||
|
label: Text("Recurrence"),
|
||||||
|
hintText: "Example: Monthly",
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: inputWidth,
|
||||||
|
height: inputHeight,
|
||||||
|
child: TextField(
|
||||||
|
keyboardType: TextInputType.text,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
labelText: "Description",
|
||||||
|
hintText: "Example: 1GB data with unlimited talk & text."),
|
||||||
|
)),
|
||||||
|
SizedBox(
|
||||||
|
width: inputWidth,
|
||||||
|
height: inputHeight,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
print("TODO: Clear fields!");
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.cancel),
|
||||||
|
label: Text('Cancel'),
|
||||||
|
),
|
||||||
|
ElevatedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
print("TODO: Save expense!");
|
||||||
|
},
|
||||||
|
icon: Icon(Icons.save),
|
||||||
|
label: Text('Submit'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
))
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class IncomePage extends StatelessWidget {
|
||||||
|
const IncomePage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Center(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text("TBD"),
|
||||||
|
Placeholder(),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetPage extends StatelessWidget {
|
||||||
|
const AssetPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ProjectionPage extends StatelessWidget {
|
||||||
|
const ProjectionPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsPage extends StatelessWidget {
|
||||||
|
const SettingsPage({
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Placeholder();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
|
|||||||
|
|
||||||
# The name of the executable created for the application. Change this to change
|
# The name of the executable created for the application. Change this to change
|
||||||
# the on-disk name of your application.
|
# the on-disk name of your application.
|
||||||
set(BINARY_NAME "flutter_empty")
|
set(BINARY_NAME "expense_tracker")
|
||||||
# The unique GTK application identifier for this application. See:
|
# The unique GTK application identifier for this application. See:
|
||||||
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
|
||||||
set(APPLICATION_ID "com.example.flutter_empty")
|
set(APPLICATION_ID "com.hyperling.expense_tracker")
|
||||||
|
|
||||||
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
|
||||||
# versions of CMake.
|
# versions of CMake.
|
||||||
|
@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
|
|||||||
if (use_header_bar) {
|
if (use_header_bar) {
|
||||||
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
|
||||||
gtk_widget_show(GTK_WIDGET(header_bar));
|
gtk_widget_show(GTK_WIDGET(header_bar));
|
||||||
gtk_header_bar_set_title(header_bar, "flutter_empty");
|
gtk_header_bar_set_title(header_bar, "Recurring Expense Tracker");
|
||||||
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
gtk_header_bar_set_show_close_button(header_bar, TRUE);
|
||||||
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
|
||||||
} else {
|
} else {
|
||||||
gtk_window_set_title(window, "flutter_empty");
|
gtk_window_set_title(window, "Recurring Expense Tracker");
|
||||||
}
|
}
|
||||||
|
|
||||||
gtk_window_set_default_size(window, 1280, 720);
|
gtk_window_set_default_size(window, 1280, 720);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
name: flutter_empty
|
name: flutter_expense_tracker
|
||||||
description: "A new Flutter project."
|
description: Track recurring expenses against income and liquid assets.
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 0.1.0
|
version: 0.1.0
|
||||||
|
|
||||||
|
3
run_offline.sh
Executable file
3
run_offline.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
flutter run --no-pub
|
Reference in New Issue
Block a user