44 Commits

Author SHA1 Message Date
5425b22ba2 Add a Help page. 2025-02-07 10:53:29 -07:00
305012ffd4 Reset income page to just a placeholder for consistency. 2025-02-07 10:53:16 -07:00
8c31d868b9 Add a daily estimate. Move estimates to the right of the list item. 2025-02-07 10:24:20 -07:00
9d478b9cbf Fix bug which sometimes set an edited expense to Monthly. Fix typo in enum monthly. 2025-02-07 05:10:22 -07:00
452eb73773 Code formatting and dialog text enhancements. 2025-02-07 05:00:57 -07:00
631555af59 Add a dark theme. 2025-02-07 04:54:59 -07:00
0f65166123 Properly implement the dismissable backgrounds. 2025-02-07 04:41:53 -07:00
595aaefedc Added editing an expense. 2025-02-07 04:29:10 -07:00
9d8a5e6685 Add more frequencies. 2025-02-06 11:50:23 -07:00
2acabf4d3b Huge improvements to UI, very interactive now! 2025-02-06 11:50:10 -07:00
15fa4aadbd Add method which puts all expenses on equal comparison grounds. 2025-02-06 11:50:02 -07:00
78a407d0ec Always show the page names. 2025-02-06 11:48:51 -07:00
54cd86c34b Shorten the title. 2025-02-05 16:45:55 -07:00
66fd966de8 Redo how the frequencies are generated. Clean up old print statements. 2025-02-05 16:41:58 -07:00
5561f50736 Begin working on the database. 2025-02-05 14:43:06 -07:00
6b25e6e552 Add sqlite dependency with flutter pub add sqflite. 2025-02-05 14:17:29 -07:00
360a36f024 Fix the layout when there are no items. 2025-02-05 14:16:49 -07:00
ecbac615e9 Finished drawer and also now showing expenses as they are added! 2025-02-05 14:04:35 -07:00
87392cc73c Break code into multiple files. 2025-02-05 13:33:07 -07:00
1b95feb5d4 Forms is working, items show if hot reloaded, need to refactor code and get the global variable into the state of the expense page. 2025-02-05 13:20:04 -07:00
99b1ec82e6 Add the Form widget and get the fields working with it. Still need to pull the values into a usable object for saving. 2025-02-05 04:20:10 -07:00
7ead0e5ebb Format dialog a little better. 2025-02-05 03:39:12 -07:00
bcae40e0e2 Buttons now close the dialog properly. 2025-02-05 03:27:11 -07:00
aa3c2f9304 Expense input form is now in a popup! Still needs to look and function properly though. 2025-02-05 03:20:29 -07:00
96811f5bfd Make the name of the function more agnostic. 2025-02-01 16:51:12 -07:00
c44d4553c3 Attempting to move the input form to a popup. 2025-02-01 16:50:37 -07:00
d722172cb3 Add buttons and fake items. 2025-02-01 04:17:56 -07:00
43dd151cb4 Refactor the order of the classes based on the UI. 2025-02-01 03:54:53 -07:00
c2cc71eae0 Add widgets for each of the future pages. 2025-02-01 03:48:26 -07:00
0c9b365f7f Added navigation. Very nice!! ;D 2025-02-01 03:42:14 -07:00
f820265dba Found the correct place to set the title info on Linux. 2025-02-01 03:21:12 -07:00
85b7a0a3e6 Add reminder script for how to run with no Internet connection. 2025-02-01 03:20:44 -07:00
861e8bf904 Change project name and ID. 2025-02-01 03:15:01 -07:00
aaa95bd3a6 Break the form into its own widget, since eventually it'll likely be a pop-up. 2025-02-01 03:14:34 -07:00
45b5e33491 Add license. 2025-01-30 21:06:25 -07:00
8a2dfc2131 More messing around. 2025-01-30 20:56:01 -07:00
3b7313ca10 Made the UI fancier but still unable to get the controller to work. 2025-01-30 20:48:30 -07:00
b07a8f9c0a Add the enum properly. 2025-01-30 20:32:04 -07:00
19e10e6792 Test UI done enough, start working on how to get the input into an Expense. 2025-01-30 20:27:03 -07:00
811313dc2c Actually starting to look pretty good!? 2025-01-30 20:08:34 -07:00
26f688cfe9 Random files, maybe due to flutter run. 2025-01-30 20:01:05 -07:00
a4ad5823aa A lot of messing around until something actually finally sorta worked! 2025-01-30 20:00:46 -07:00
b227ddc849 Take the main file from an empty project. (flutter create -e). 2025-01-30 19:23:12 -07:00
92ebe3c4e0 Remove extra sentence. 2025-01-30 19:22:31 -07:00
33 changed files with 859 additions and 277 deletions

9
LICENSE Normal file
View File

@ -0,0 +1,9 @@
MIT License
Copyright (c) 2025 me
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -4,5 +4,3 @@ Recurring expense tracker app for Android.
Add a debit as daily, weekly, monthly, etc, then see how it affects your liquid
assets based on your reported income over different time projections.
Useful for many applications!

View File

@ -1,28 +1 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -6,7 +6,7 @@ plugins {
}
android {
namespace = "com.example.flutter_expense_tracker"
namespace = "com.hyperling.expense_tracker"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
@ -21,7 +21,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.example.flutter_expense_tracker"
applicationId = "com.hyperling.expense_tracker"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion

View File

@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="flutter_expense_tracker"
android:label="Recurring Expense Tracker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity

View File

@ -1,4 +1,4 @@
package com.example.flutter_expense_tracker
package com.example.flutter_empty
import io.flutter.embedding.android.FlutterActivity

View File

@ -368,7 +368,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -384,7 +384,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -401,7 +401,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -416,7 +416,7 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner";
@ -547,7 +547,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -569,7 +569,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View File

@ -5,7 +5,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Flutter Expense Tracker</string>
<string>Flutter Empty</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
@ -13,7 +13,7 @@
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>flutter_expense_tracker</string>
<string>flutter_empty</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>

45
lib/db.dart Normal file
View 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,
);
}

View File

@ -1,125 +1,145 @@
// Flutter
import 'package:flutter/material.dart';
// Local
import '/pages/expense.dart';
import '/pages/income.dart';
import '/pages/asset.dart';
import '/pages/report.dart';
import '/pages/settings.dart';
import '/pages/help.dart';
import '/db.dart';
void main() {
runApp(const MyApp());
loadDB();
runApp(const MainApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
class MainApp extends StatelessWidget {
const MainApp({super.key});
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
title: 'Recurring Expense Tracker',
theme: ThemeData(
// This is the theme of your application.
//
// TRY THIS: Try running your application with "flutter run". You'll see
// the application has a purple toolbar. Then, without quitting the app,
// try changing the seedColor in the colorScheme below to Colors.green
// and then invoke "hot reload" (save your changes or press the "hot
// reload" button in a Flutter-supported IDE, or press "r" if you used
// the command line to start the app).
//
// Notice that the counter didn't reset back to zero; the application
// state is not lost during the reload. To reset the state, use hot
// restart instead.
//
// This works for code too, not just values: Most code changes can be
// tested with just a hot reload.
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
useMaterial3: true,
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
darkTheme: ThemeData(
useMaterial3: true,
brightness: Brightness.dark,
colorSchemeSeed: Colors.green,
),
themeMode: ThemeMode.system,
home: HomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
class HomePage extends StatefulWidget {
const HomePage({
super.key,
});
@override
State<MyHomePage> createState() => _MyHomePageState();
State<HomePage> createState() => _HomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
class _HomePageState extends State<HomePage> {
var pageSelected = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
refresh() {
setState(() {});
}
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// TRY THIS: Try changing the color here to a specific color (to
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
// change color while the other colors stay the same.
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
// Column is also a layout widget. It takes a list of children and
// arranges them vertically. By default, it sizes itself to fit its
// children horizontally, and tries to be as tall as its parent.
//
// Column has various properties to control how it sizes itself and
// how it positions its children. Here we use mainAxisAlignment to
// center the children vertically; the main axis here is the vertical
// axis because Columns are vertical (the cross axis would be
// horizontal).
//
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
// action in the IDE, or press "p" in the console), to see the
// wireframe for each widget.
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
Widget page;
Widget? dialog;
switch (pageSelected) {
case 0:
page = ExpensePage();
dialog = ExpenseInputDialog(
notifyParent: refresh,
);
case 1:
page = IncomePage();
case 2:
page = AssetPage();
case 3:
page = ProjectionPage();
case 4:
page = SettingsPage();
case 5:
page = HelpPage();
default:
throw UnimplementedError('No widget for page $pageSelected yet!');
}
Future<void> addNewValue(BuildContext context) {
return showDialog(
context: context,
builder: (_) => AlertDialog(content: dialog),
);
}
Widget? floatingButton;
if (dialog != null) {
floatingButton = IconButton(
onPressed: () {
addNewValue(context);
},
icon: Icon(Icons.add),
color: Theme.of(context).colorScheme.onSurface,
);
}
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
appBar: AppBar(title: Text("Expense Tracker")),
drawer: NavigationRail(
extended: true,
destinations: [
NavigationRailDestination(
icon: Icon(Icons.payment),
label: Text('Expenses'),
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headlineMedium,
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('Reports'),
),
NavigationRailDestination(
icon: Icon(Icons.settings),
label: Text('Settings'),
),
NavigationRailDestination(
icon: Icon(Icons.help),
label: Text('Help'),
),
],
selectedIndex: pageSelected,
onDestinationSelected: (value) {
setState(() {
pageSelected = value;
Navigator.pop(context);
});
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: const Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
body: Container(
color: Theme.of(context).colorScheme.primaryContainer,
child: Center(child: page),
),
floatingActionButton: floatingButton,
);
});
}
}

27
lib/models/expense.dart Normal file
View File

@ -0,0 +1,27 @@
import '/models/frequency.dart';
class Expense {
final String name;
final double cost;
final Frequency frequency;
final String description;
const Expense(
{required this.name,
required this.cost,
required this.frequency,
required this.description});
@override
String toString() {
return "$name, $cost, ${frequency.title}, $description";
}
double calcComparableCost() {
return cost * frequency.timesPerYear;
}
double calcComparableCostDaily() {
return cost / frequency.numDays;
}
}

63
lib/models/frequency.dart Normal file
View File

@ -0,0 +1,63 @@
// https://www.tutorialspoint.com/dart_programming/dart_programming_enumeration.htm
enum Frequency {
daily(
title: "Daily",
hint: "Once Per Day",
timesPerYear: 364.25,
numDays: 1,
),
weekly(
title: "Weekly",
hint: "Once Per Week",
timesPerYear: (364.25 / 7),
numDays: 7,
),
biweekly(
title: "Biweekly",
hint: "Every Other Week",
timesPerYear: (364.25 / 14),
numDays: 14,
),
bimonthly(
title: "Bimonthly",
hint: "Twice Per Month",
timesPerYear: 24,
numDays: (364.25 / 24),
),
monthly(
title: "Monthly",
hint: "Once Per Month",
timesPerYear: 12,
numDays: (364.25 / 12),
),
quarterly(
title: "Quarterly",
hint: "Every Three Months",
timesPerYear: 4,
numDays: (364.25 / 4),
),
biannual(
title: "Biannual",
hint: "Twice Per Year",
timesPerYear: 2,
numDays: (364.25 / 2),
),
yearly(
title: "Yearly",
hint: "Once Per Year",
timesPerYear: 1,
numDays: 364.25,
);
const Frequency({
required this.title,
required this.hint,
required this.timesPerYear,
required this.numDays,
});
final String title;
final String hint;
final double timesPerYear;
final double numDays;
}

12
lib/pages/asset.dart Normal file
View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class AssetPage extends StatelessWidget {
const AssetPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

365
lib/pages/expense.dart Normal file
View File

@ -0,0 +1,365 @@
// Flutter
import 'package:flutter/material.dart';
// Local
import '/models/expense.dart';
import '/models/frequency.dart';
List<Expense> expenses = [];
class ExpensePage extends StatefulWidget {
const ExpensePage({
super.key,
});
@override
State<ExpensePage> createState() => _ExpensePageState();
}
class _ExpensePageState extends State<ExpensePage> {
refresh() {
setState(() {});
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
expenses.sort(
(a, b) => (b.calcComparableCost() - a.calcComparableCost()).toInt(),
);
return expenses.isEmpty
? Text("Add expenses to get started!")
: ListView.builder(
itemCount: expenses.length,
itemBuilder: (_, index) {
final Expense curr = expenses[index];
final String estimateSymbolYearly = curr.frequency.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
curr.calcComparableCost().toStringAsFixed(3).endsWith("0")
? ""
: "~";
final String estimateSymbolDaily =
curr.frequency.numDays.toStringAsFixed(2).endsWith(".00") &&
curr
.calcComparableCostDaily()
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
return Padding(
padding: const EdgeInsets.all(4.0),
child: Dismissible(
key: Key(curr.toString()),
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(() {
expenses.remove(curr);
});
switch (direction) {
case DismissDirection.startToEnd:
// Only remove the item from the list.
break;
case DismissDirection.endToStart:
// Open an edit dialog, then remove the item from the list.
showDialog(
context: context,
builder: (_) => AlertDialog(
content: ExpenseInputDialog(
notifyParent: refresh,
expense: curr,
),
),
);
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(
curr.name,
style: TextStyle(fontSize: 20.0),
),
Text(
"${curr.cost.toStringAsFixed(2)} ${curr.frequency.title}",
style: TextStyle(fontSize: 12.0),
),
],
),
Expanded(
child: Center(
child: Text(
curr.description,
style: TextStyle(
fontSize: 12.0,
),
softWrap: true,
textAlign: TextAlign.center,
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
//if (curr.frequency != Frequency.daily)
Text(
"$estimateSymbolDaily${curr.calcComparableCostDaily().toStringAsFixed(2)} ${Frequency.daily.title}",
style: TextStyle(fontSize: 12.0),
),
//if (curr.frequency != Frequency.yearly)
Text(
"$estimateSymbolYearly${curr.calcComparableCost().toStringAsFixed(2)} ${Frequency.yearly.title}",
style: TextStyle(fontSize: 12.0),
),
],
),
],
),
),
),
),
);
},
);
}
}
class ExpenseInputDialog extends StatefulWidget {
final Function() notifyParent;
final Expense? expense;
const ExpenseInputDialog({
super.key,
required this.notifyParent,
this.expense,
});
@override
State<ExpenseInputDialog> createState() => _ExpenseInputDialogState();
}
class _ExpenseInputDialogState extends State<ExpenseInputDialog> {
final _expenseFormKey = GlobalKey<FormState>();
String _name = "";
double _cost = 0;
Frequency _freq = Frequency.monthly;
String _desc = "";
@override
Widget build(BuildContext context) {
if (widget.expense != null) {
_name = widget.expense!.name;
_cost = widget.expense!.cost;
_freq = widget.expense!.frequency;
_desc = widget.expense!.description;
}
return Column(
// prevent AlertDialog from taking full vertical height.
mainAxisSize: MainAxisSize.min,
children: [
Container(
alignment: FractionalOffset.topRight,
child: IconButton(
onPressed: () {
if (widget.expense != null) {
setState(() {
expenses.add(widget.expense!);
widget.notifyParent();
});
}
Navigator.of(context).pop();
},
icon: Icon(Icons.clear),
),
),
AlertDialog(
insetPadding: EdgeInsets.all(0),
title: Center(
child: widget.expense == null
? Text("New Expense")
: Text("Edit Expense"),
),
content: Form(
key: _expenseFormKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.words,
decoration: InputDecoration(
labelText: "Name",
hintText: "Example: Red Pocket",
hintStyle: TextStyle(fontSize: 10.0),
errorStyle: TextStyle(fontSize: 10.0),
),
initialValue: _name,
validator: (value) {
if (value!.isEmpty) {
return "Name must be provided.";
}
if (!expenses.every((expense) => expense.name != value)) {
return "Name must be unique, already in use.";
}
return null;
},
onSaved: (value) {
_name = value!;
},
),
TextFormField(
keyboardType: TextInputType.numberWithOptions(decimal: true),
decoration: InputDecoration(
labelText: "Cost",
hintText: "Example: 10.00",
hintStyle: TextStyle(fontSize: 10.0),
errorStyle: TextStyle(fontSize: 10.0),
),
initialValue: _cost != 0 ? _cost.toString() : "",
validator: (value) {
if (value == null || value.isEmpty) {
return "Cost must be provided.";
}
if (double.parse(value) < 0) {
return "Please use the Income page rather than having negative expenses.";
}
if (double.parse(value) < 0.01) {
return "Cost must be one hundreth (0.01) or higher.";
}
if (double.tryParse(value) == null) {
return "Cost must be a valid number.";
}
return null;
},
onSaved: (value) {
_cost = double.parse(value!);
},
),
DropdownButtonFormField(
items: Frequency.values
.map(
(freq) => DropdownMenuItem(
value: freq,
child: Row(
children: [
Text(
freq.title,
),
Padding(
padding: EdgeInsets.all(1.0),
child: Text(
" (${freq.hint})",
style: TextStyle(fontSize: 10.0),
),
),
],
),
),
)
.toList(),
value: _freq,
decoration: InputDecoration(
labelText: "Frequency",
errorStyle: TextStyle(fontSize: 10.0),
),
validator: (value) {
if (value == null) {
return "Frequency must be provided.";
}
if (!Frequency.values.contains(value)) {
return "Value not valid.";
}
return null;
},
onChanged: (value) {
_freq = value!;
},
),
TextFormField(
keyboardType: TextInputType.text,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: "Description",
hintText: "Example: 1GB data with unlimited talk & text.",
hintStyle: TextStyle(fontSize: 8.0),
errorStyle: TextStyle(fontSize: 10.0),
),
initialValue: _desc,
validator: (value) {
return null;
},
onSaved: (value) {
_desc = value!;
},
),
],
),
),
actions: [
Center(
child: ElevatedButton.icon(
onPressed: () {
if (_expenseFormKey.currentState!.validate()) {
_expenseFormKey.currentState!.save();
setState(() {
expenses.add(
Expense(
name: _name,
cost: _cost,
frequency: _freq,
description: _desc),
);
});
widget.notifyParent();
Navigator.of(context).pop();
}
},
icon: Icon(Icons.save),
label: Text('Submit'),
),
)
],
),
],
);
}
}

72
lib/pages/help.dart Normal file
View File

@ -0,0 +1,72 @@
import 'package:flutter/material.dart';
class HelpPage extends StatelessWidget {
const HelpPage({
super.key,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.onPrimary,
),
child: Column(
children: [
Text("This app is meant to be a simple budgeting tool,"
" allowing you to view your income and expenses at a high level"
" without micro managing specific budget items or adding receipts."),
//Text("Another paragraph.")
],
),
),
),
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.onPrimary,
),
child: TextButton.icon(
onPressed: () {},
icon: Icon(Icons.code),
label: Text("Code Repository"),
),
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.onPrimary,
),
child: TextButton.icon(
onPressed: () {},
icon: Icon(Icons.web_asset),
label: Text("Personal Website"),
),
),
),
),
],
)
],
);
}
}

12
lib/pages/income.dart Normal file
View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class IncomePage extends StatelessWidget {
const IncomePage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

12
lib/pages/report.dart Normal file
View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class ProjectionPage extends StatelessWidget {
const ProjectionPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

12
lib/pages/settings.dart Normal file
View File

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
class SettingsPage extends StatelessWidget {
const SettingsPage({
super.key,
});
@override
Widget build(BuildContext context) {
return Placeholder();
}
}

View File

@ -4,10 +4,10 @@ project(runner LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "flutter_expense_tracker")
set(BINARY_NAME "expense_tracker")
# The unique GTK application identifier for this application. See:
# https://wiki.gnome.org/HowDoI/ChooseApplicationID
set(APPLICATION_ID "com.example.flutter_expense_tracker")
set(APPLICATION_ID "com.hyperling.expense_tracker")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) {
if (use_header_bar) {
GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new());
gtk_widget_show(GTK_WIDGET(header_bar));
gtk_header_bar_set_title(header_bar, "flutter_expense_tracker");
gtk_header_bar_set_title(header_bar, "Recurring Expense Tracker");
gtk_header_bar_set_show_close_button(header_bar, TRUE);
gtk_window_set_titlebar(window, GTK_WIDGET(header_bar));
} else {
gtk_window_set_title(window, "flutter_expense_tracker");
gtk_window_set_title(window, "Recurring Expense Tracker");
}
gtk_window_set_default_size(window, 1280, 720);

View File

@ -5,6 +5,8 @@
import FlutterMacOS
import Foundation
import sqflite_darwin
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
}

View File

@ -64,7 +64,7 @@
331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* flutter_expense_tracker.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "flutter_expense_tracker.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10ED2044A3C60003C045 /* flutter_empty.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "flutter_empty.app"; sourceTree = BUILT_PRODUCTS_DIR; };
33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = "<group>"; };
33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = "<group>"; };
@ -131,7 +131,7 @@
33CC10EE2044A3C60003C045 /* Products */ = {
isa = PBXGroup;
children = (
33CC10ED2044A3C60003C045 /* flutter_expense_tracker.app */,
33CC10ED2044A3C60003C045 /* flutter_empty.app */,
331C80D5294CF71000263BE5 /* RunnerTests.xctest */,
);
name = Products;
@ -217,7 +217,7 @@
);
name = Runner;
productName = Runner;
productReference = 33CC10ED2044A3C60003C045 /* flutter_expense_tracker.app */;
productReference = 33CC10ED2044A3C60003C045 /* flutter_empty.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
@ -385,10 +385,10 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_expense_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_expense_tracker";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_empty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_empty";
};
name = Debug;
};
@ -399,10 +399,10 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_expense_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_expense_tracker";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_empty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_empty";
};
name = Release;
};
@ -413,10 +413,10 @@
CURRENT_PROJECT_VERSION = 1;
GENERATE_INFOPLIST_FILE = YES;
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker.RunnerTests;
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty.RunnerTests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 5.0;
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_expense_tracker.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_expense_tracker";
TEST_HOST = "$(BUILT_PRODUCTS_DIR)/flutter_empty.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/flutter_empty";
};
name = Profile;
};

View File

@ -15,7 +15,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "flutter_expense_tracker.app"
BuildableName = "flutter_empty.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -31,7 +31,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "flutter_expense_tracker.app"
BuildableName = "flutter_empty.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -65,7 +65,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "flutter_expense_tracker.app"
BuildableName = "flutter_empty.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
@ -82,7 +82,7 @@
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "flutter_expense_tracker.app"
BuildableName = "flutter_empty.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>

View File

@ -5,10 +5,10 @@
// 'flutter create' template.
// The application's name. By default this is also the title of the Flutter window.
PRODUCT_NAME = flutter_expense_tracker
PRODUCT_NAME = flutter_empty
// The application's bundle identifier
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterExpenseTracker
PRODUCT_BUNDLE_IDENTIFIER = com.example.flutterEmpty
// The copyright displayed in application information
PRODUCT_COPYRIGHT = Copyright © 2025 com.example. All rights reserved.

View File

@ -41,14 +41,6 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.19.0"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
url: "https://pub.dev"
source: hosted
version: "1.0.8"
fake_async:
dependency: transitive
description:
@ -139,6 +131,22 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description: flutter
@ -152,6 +160,46 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -176,6 +224,14 @@ packages:
url: "https://pub.dev"
source: hosted
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:
dependency: transitive
description:
@ -210,4 +266,4 @@ packages:
version: "14.3.0"
sdks:
dart: ">=3.6.1 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.24.0"

View File

@ -1,89 +1,20 @@
name: flutter_expense_tracker
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
description: Track recurring expenses against income and liquid assets.
publish_to: 'none'
version: 0.1.0
environment:
sdk: ^3.6.1
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8
sqflite: ^2.4.1
dev_dependencies:
flutter_test:
sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^5.0.0
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/to/resolution-aware-images
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/to/asset-from-package
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/to/font-from-package

3
run_offline.sh Executable file
View File

@ -0,0 +1,3 @@
#!/usr/bin/env bash
flutter run --no-pub

View File

@ -1,30 +0,0 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_expense_tracker/main.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const MyApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

View File

@ -23,13 +23,13 @@
<!-- iOS meta tags & icons -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="flutter_expense_tracker">
<meta name="apple-mobile-web-app-title" content="flutter_empty">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>flutter_expense_tracker</title>
<title>flutter_empty</title>
<link rel="manifest" href="manifest.json">
</head>
<body>

View File

@ -1,6 +1,6 @@
{
"name": "flutter_expense_tracker",
"short_name": "flutter_expense_tracker",
"name": "flutter_empty",
"short_name": "flutter_empty",
"start_url": ".",
"display": "standalone",
"background_color": "#0175C2",

View File

@ -1,10 +1,10 @@
# Project-level configuration.
cmake_minimum_required(VERSION 3.14)
project(flutter_expense_tracker LANGUAGES CXX)
project(flutter_empty LANGUAGES CXX)
# The name of the executable created for the application. Change this to change
# the on-disk name of your application.
set(BINARY_NAME "flutter_expense_tracker")
set(BINARY_NAME "flutter_empty")
# Explicitly opt in to modern CMake behaviors to avoid warnings with recent
# versions of CMake.

View File

@ -90,12 +90,12 @@ BEGIN
BLOCK "040904e4"
BEGIN
VALUE "CompanyName", "com.example" "\0"
VALUE "FileDescription", "flutter_expense_tracker" "\0"
VALUE "FileDescription", "flutter_empty" "\0"
VALUE "FileVersion", VERSION_AS_STRING "\0"
VALUE "InternalName", "flutter_expense_tracker" "\0"
VALUE "InternalName", "flutter_empty" "\0"
VALUE "LegalCopyright", "Copyright (C) 2025 com.example. All rights reserved." "\0"
VALUE "OriginalFilename", "flutter_expense_tracker.exe" "\0"
VALUE "ProductName", "flutter_expense_tracker" "\0"
VALUE "OriginalFilename", "flutter_empty.exe" "\0"
VALUE "ProductName", "flutter_empty" "\0"
VALUE "ProductVersion", VERSION_AS_STRING "\0"
END
END

View File

@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev,
FlutterWindow window(project);
Win32Window::Point origin(10, 10);
Win32Window::Size size(1280, 720);
if (!window.Create(L"flutter_expense_tracker", origin, size)) {
if (!window.Create(L"flutter_empty", origin, size)) {
return EXIT_FAILURE;
}
window.SetQuitOnClose(true);