37 Commits
Author SHA1 Message Date
me 3b71cc0267 Merge pull request '0.1.4' (#3) from dev into main
Reviewed-on: #3
2026-11-01 06:32:34 -07:00
me c03062291a pubspec, update the version in case it hasn't already been done. 2026-10-30 19:58:15 -07:00
me c90a2cc437 manifest, use the proper icon. 2026-10-30 19:51:19 -07:00
me b9eb13a42d icon, use Android Studio to install it properly. 2026-10-30 19:48:03 -07:00
me 1d9fdcb6fe Allow the app to compile again. 2026-10-30 19:45:04 -07:00
me d523886659 gitignore, add the new location of build files. 2026-10-30 19:44:41 -07:00
me 8b51114165 More changes by Flutter, seems like the opposite of what was just done? 2026-10-24 20:42:25 -07:00
me 6e354eb908 Flutter keeps making these changes, go ahead and commit them. 2026-10-24 17:34:47 -07:00
me f48ba337a7 gradle, remove supposedly now unnecessary kotlin-android call. 2026-10-24 17:34:19 -07:00
me 430127d040 Remove old Gradle files (non-Kotlin). 2026-10-22 17:54:02 -07:00
me 6b33431613 Update Kotlin. 2026-10-22 17:53:46 -07:00
me d5985b4a11 Update AGP, add Gradle Daemon. 2026-10-22 17:44:03 -07:00
me 23974ea4ae gitignore, ignore Android build files. 2026-10-22 17:33:44 -07:00
me 7e25ce1c06 Update Gradle. 2026-10-22 17:32:38 -07:00
me 3711a888f9 Update Java. 2026-10-22 17:32:33 -07:00
me 48a8ac93ca Changes via Flutter plugin in Codium, Code-OSS version was very outdated, 2026-09-29 11:51:56 -07:00
me 1900a1378a fdroid, add specifier to email and put the field in the app-specific section. 2026-09-29 07:16:51 -07:00
me bdebda9c83 Add more fdroid details while refactoring file. 2026-07-04 14:57:28 -07:00
me 672d34fa43 Fix space at beginning of fdroid manifest description. 2026-07-04 10:58:13 -07:00
me 0aff0208d4 Update issue tracker to be proper HTTP link, use contact page. Change website page to go directly to the app. 2026-07-04 10:47:43 -07:00
me 097a9ea4ed Merge pull request 'Add manifest file for self-hosted F-Droid repo.' (#2) from dev into main
Reviewed-on: #2
2026-07-04 10:17:23 -07:00
me f86d78a969 Add manifest file for self-hosted F-Droid repo. 2026-07-04 10:16:23 -07:00
me 70c26be8e3 Stop showing errors if the default exists and all options have already been covered. 2025-03-27 15:17:14 -07:00
me b177b6317e Adjust settings page formatting and description text. 2025-03-27 15:15:33 -07:00
me 3c52239efc Add header to Settings page. 2025-03-27 15:14:10 -07:00
me de4f8306d7 Remove TODO note. 2025-03-27 15:12:31 -07:00
me f77cf7bd38 Refactor to use value of variable. 2025-03-27 15:12:23 -07:00
me 716d40c694 Remove unneeded dependency. 2025-03-27 15:12:07 -07:00
me d9f8536f26 Use a consistent header on each page. 2025-03-27 14:48:07 -07:00
me 1966f72c93 Add item descriptions to Help information and clarify the updating section. 2025-03-27 14:39:52 -07:00
me e896611bd1 Refactor to add header to the Tracked Item pages. 2025-03-27 14:31:34 -07:00
me 2d9c93fec4 Fix typo. 2025-03-27 14:26:51 -07:00
me c5f1a4e9ba Add app version to menu. 2025-03-27 14:02:18 -07:00
me 538a298acd Made the input dialog fully item type aware. Fixes #1, now all item types can have an item of the same name (such as "Test") rather than only checking the Expense table for the name. 2025-03-27 12:22:57 -07:00
me cce878ccaa Made the input dialog fully item type aware. Fixes issue #1, now all item types can have an item of the same name (such as "Test") rather than only checking the Expense table for the name. 2025-03-27 12:15:55 -07:00
me 147178e4dd Fix the projections not loading if any item type was left unused. 2025-03-27 11:58:26 -07:00
me c39e09b2b6 Increment the version. 2025-03-27 11:57:22 -07:00
40 changed files with 505 additions and 371 deletions

No files matched your search

+4
View File
@@ -47,3 +47,7 @@ app.*.map.json
# Ignore the releases folder. # Ignore the releases folder.
releases/* releases/*
# Ignore build files from Android.
android/build/*
android/app/build/*
+11
View File
@@ -1 +1,12 @@
include: package:flutter_lints/flutter.yaml include: package:flutter_lints/flutter.yaml
analyzer:
errors:
unreachable_switch_default: ignore
exclude:
- build/**
- android/**
- ios/**
- web/**
- windows/**
- macos/**
- linux/**
-44
View File
@@ -1,44 +0,0 @@
plugins {
id "com.android.application"
id "kotlin-android"
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id "dev.flutter.flutter-gradle-plugin"
}
android {
namespace = "com.hyperling.expense_tracker"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_1_8
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
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
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
versionName = flutter.versionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.debug
}
}
}
flutter {
source = "../.."
}
+10 -11
View File
@@ -1,7 +1,8 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins { plugins {
id("com.android.application") id("com.android.application")
id("kotlin-android") // AGP 9 bundles Kotlin natively - no explicit plugin needed here
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
@@ -11,19 +12,19 @@ android {
ndkVersion = flutter.ndkVersion ndkVersion = flutter.ndkVersion
compileOptions { compileOptions {
sourceCompatibility = JavaVersion.VERSION_11 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { // AGP 9's bundled Kotlin - use the native DSL
jvmTarget = JavaVersion.VERSION_11.toString() kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.hyperling.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 minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
@@ -32,8 +33,6 @@ android {
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig = signingConfigs.getByName("debug") signingConfig = signingConfigs.getByName("debug")
} }
} }
+1 -2
View File
@@ -2,8 +2,7 @@
<application <application
android:label="Expenses" android:label="Expenses"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/icon_v003" android:icon="@mipmap/ic_launcher">
android:roundIcon="@mipmap/icon_v003_round">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#FFFFFF</color>
</resources>
+2 -18
View File
@@ -1,18 +1,2 @@
allprojects { // Expose Kotlin version for Flutter's plugin validation (AGP 9 bundles Kotlin natively)
repositories { ext.kotlin_version = "2.4.20"
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
+6
View File
@@ -1,3 +1,9 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true android.enableJetifier=true
# This builtInKotlin flag was added automatically by Flutter migrator
android.builtInKotlin=true
# This newDsl flag was added automatically by Flutter migrator
android.newDsl=false
# Force the Kotlin version resolver in AGP 9 to use our target version
kotlin.version=2.4.20
+1 -1
View File
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.8.0-bin.zip
-25
View File
@@ -1,25 +0,0 @@
pluginManagement {
def flutterSdkPath = {
def properties = new Properties()
file("local.properties").withInputStream { properties.load(it) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
return flutterSdkPath
}()
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version "8.1.0" apply false
id "org.jetbrains.kotlin.android" version "1.8.22" apply false
}
include ":app"
+6 -2
View File
@@ -18,8 +18,12 @@ pluginManagement {
plugins { plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0" id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false id("com.android.application") version "9.4.1" apply false
// This must exist to satisfy Flutter's Gradle plugin validation for AGP 9 bundled Kotlin:
// "Error: Your project's Kotlin version (2.2.10) is lower than Flutter's minimum supported version of 2.2.20."
id("org.jetbrains.kotlin.android") version "2.4.20" apply false
} }
include(":app") include(":app")
+16
View File
@@ -0,0 +1,16 @@
AuthorName: 'Chad Michael "Hyperling" Greenwood'
AuthorWebSite: 'https://hyperling.com/about/'
Donate: 'https://hyperling.com/donate/'
Liberapay: 'HyperVegan'
Bitcoin: 'bc1qsfe8dkvry3d34kztz449gkq67wq8fu2nkgfkh0'
Litecoin: 'ltc1qavmpu5d6ljntxsd6jj548m4yys83zwscl0dzkx'
IssueTracker: 'https://hyperling.com/contact/'
Categories:
- fdroid
- hyperling
- utilities
Name: Expense Tracker
AuthorEmail: 'me+expense@hyperling.com'
Summary: 'Keep track of repeating expenses and view projections of their effect on your finances. First app written with Flutter.'
SourceCode: 'https://git.hyperling.com/me/flutter-expense-tracker/'
WebSite: 'https://hyperling.com/apps/#expense'
+6 -1
View File
@@ -2,21 +2,26 @@ enum ItemType {
expense( expense(
title: "Expense", title: "Expense",
plural: "Expenses", plural: "Expenses",
description: "Items which cost revenue, or decrease asset value.",
), ),
income( income(
title: "Income", title: "Income",
plural: "Incomes", plural: "Income",
description: "Items which bring in revenue, or increase asset value.",
), ),
asset( asset(
title: "Asset", title: "Asset",
plural: "Assets", plural: "Assets",
description: "Value which has been earned and can be spent.",
); );
const ItemType({ const ItemType({
required this.title, required this.title,
required this.plural, required this.plural,
required this.description,
}); });
final String title; final String title;
final String plural; final String plural;
final String description;
} }
+21 -6
View File
@@ -1,7 +1,11 @@
// Flutter // Flutter
import '/models/item_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
// Local
import '/widgets/cards.dart';
_launchSite(String url) async { _launchSite(String url) async {
try { try {
if (await canLaunchUrlString(url)) { if (await canLaunchUrlString(url)) {
@@ -38,23 +42,34 @@ class HelpPage extends StatelessWidget {
), ),
child: Column( child: Column(
children: [ children: [
TitleCard(title: "Help"),
Text( Text(
"\t\t This app is meant to be a simple budgeting tool," "\t\t This app is meant to be a simple budgeting tool,"
" allowing you to view your income and expenses at a high" " allowing you to view your income and expenses at a high"
" level without micro managing specific budget items or" " level without micro managing specific budget items or"
" adding receipts.", " adding receipts."
"",
), ),
Text( Text(
"\n\t\t Tracked items can be swiped left to right for ," "\n\t\t ${ItemType.expense.plural} are defined as ${ItemType.expense.description.toLowerCase()}"
" ${ItemType.income.title} is defined as ${ItemType.income.description.toLowerCase()}"
" ${ItemType.asset.plural} are defined as ${ItemType.asset.description.toLowerCase()}"
"",
),
Text(
"\n\t\t Tracked items can be swiped left to right for"
" Deletion or right to left for Editing. Items are sorted" " Deletion or right to left for Editing. Items are sorted"
" from highest to lowest so that the biggest impacts are" " from highest to lowest so that the biggest impacts are"
" always in view.", " always in view."
"",
), ),
Text( Text(
"\n\t\t To subscribe to app updates, install the Obtanium" "\n\t\t To subscribe to Android updates, install Obtanium,"
" app, then use the URL from the Source Code button below." " then use the URL from the Source Code button below."
" Otherwise the app needs installed manually by downloading" " Otherwise the app needs installed manually by downloading"
" APKs from the Source Code /releases/ page.", " APKs from the Source Code /releases/ page. Linux users"
" currently need to install and update manually."
"",
), ),
//Text("Another paragraph.") //Text("Another paragraph.")
], ],
+31 -7
View File
@@ -2,13 +2,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '/models/item_type.dart'; import '/models/item_type.dart';
import 'dart:io'; import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
// Local // Local
import '/pages/tracked_item.dart'; import '/pages/tracked_item.dart';
import '/pages/report.dart'; import '/pages/report.dart';
import '/pages/settings.dart'; import '/pages/settings.dart';
import '/pages/help.dart'; import '/pages/help.dart';
import '/db.dart';
class HomePage extends StatefulWidget { class HomePage extends StatefulWidget {
const HomePage({ const HomePage({
@@ -26,14 +26,36 @@ class _HomePageState extends State<HomePage> {
setState(() {}); setState(() {});
} }
PackageInfo _packageInfo = PackageInfo(
appName: 'Unknown',
packageName: 'Unknown',
version: 'Unknown',
buildNumber: 'Unknown',
);
Future _initPackageInfo() async {
final PackageInfo info = await PackageInfo.fromPlatform();
setState(() {
_packageInfo = info;
});
}
@override
void initState() {
super.initState();
// Get package details
_initPackageInfo();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget page; Widget page;
Widget? dialog; Widget? dialog;
switch (pageSelected) { switch (pageSelected) {
case 0: case 0:
page = TrackedItemPage( page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getExpenses(), assetType: ItemType.expense,
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@@ -43,7 +65,7 @@ class _HomePageState extends State<HomePage> {
break; break;
case 1: case 1:
page = TrackedItemPage( page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getIncomes(), assetType: ItemType.income,
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@@ -53,7 +75,7 @@ class _HomePageState extends State<HomePage> {
break; break;
case 2: case 2:
page = TrackedItemPage( page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getAssets(), assetType: ItemType.asset,
notifyParent: refresh, notifyParent: refresh,
); );
dialog = TrackedItemInputDialog( dialog = TrackedItemInputDialog(
@@ -97,15 +119,15 @@ class _HomePageState extends State<HomePage> {
destinations: [ destinations: [
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.payment), icon: Icon(Icons.payment),
label: Text('Expenses'), label: Text(ItemType.expense.plural),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.account_balance), icon: Icon(Icons.account_balance),
label: Text('Income'), label: Text(ItemType.income.plural),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.attach_money), icon: Icon(Icons.attach_money),
label: Text('Liquid Assets'), label: Text(ItemType.asset.plural),
), ),
NavigationRailDestination( NavigationRailDestination(
icon: Icon(Icons.bar_chart), icon: Icon(Icons.bar_chart),
@@ -129,6 +151,8 @@ class _HomePageState extends State<HomePage> {
} }
}); });
}, },
leading: Text("Menu"),
trailing: Text("v${_packageInfo.version}"),
); );
Widget main = Container( Widget main = Container(
+18 -23
View File
@@ -1,6 +1,5 @@
// Flutter // Flutter
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '/models/item_type.dart'; import '/models/item_type.dart';
@@ -12,8 +11,6 @@ import '/models/tracked_item.dart';
/// TODO: /// TODO:
/// - Projected Assets: /// - Projected Assets:
/// - Allow customization? /// - Allow customization?
/// - Fix bug where editing an item does not reflect immediately when returning to Reports page.
/// - Currently reflects after going back to Reports the 2nd time.
double _assetTotal = -1, double _assetTotal = -1,
_expenseMonthly = -1, _expenseMonthly = -1,
@@ -48,15 +45,15 @@ class _ProjectionPageState extends State<ProjectionPage> {
// Summaries for display as well as calculation of totals for projections. // Summaries for display as well as calculation of totals for projections.
Widget expenseSummary = SummaryCardForTotals( Widget expenseSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getExpenses(), list: DatabaseHelper.instance.getExpenses(),
summaryTypeLabel: ItemType.expense.title, itemType: ItemType.expense,
); );
Widget incomeSummary = SummaryCardForTotals( Widget incomeSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getIncomes(), list: DatabaseHelper.instance.getIncomes(),
summaryTypeLabel: ItemType.income.title, itemType: ItemType.income,
); );
Widget assetSummary = SummaryCardForTotals( Widget assetSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getAssets(), list: DatabaseHelper.instance.getAssets(),
summaryTypeLabel: ItemType.asset.title, itemType: ItemType.asset,
); );
// Calculations for the projections. // Calculations for the projections.
@@ -68,18 +65,14 @@ class _ProjectionPageState extends State<ProjectionPage> {
_expenseYearly < 0) { _expenseYearly < 0) {
_showProjections = false; _showProjections = false;
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
Future.delayed(Duration(seconds: 1), () { Future.delayed(Duration(seconds: 1), () {
setState(() { setState(() {
_showProjections = true; _showProjections = true;
}); });
}); });
} else { }
if (_showProjections) {
double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly, double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)), threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)), sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
@@ -135,6 +128,12 @@ class _ProjectionPageState extends State<ProjectionPage> {
proj6, proj6,
], ],
); );
} else {
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
} }
// Return all of the UI elements. // Return all of the UI elements.
@@ -155,14 +154,16 @@ class SummaryCardForTotals extends StatelessWidget {
const SummaryCardForTotals({ const SummaryCardForTotals({
super.key, super.key,
required this.list, required this.list,
required this.summaryTypeLabel, required this.itemType,
}); });
final Future<List<TrackedItem>> list; final Future<List<TrackedItem>> list;
final String summaryTypeLabel; final ItemType itemType;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
String summaryTypeLabel = itemType.title.toString();
return FutureBuilder<List<TrackedItem>>( return FutureBuilder<List<TrackedItem>>(
future: list, future: list,
builder: ( builder: (
@@ -175,12 +176,9 @@ class SummaryCardForTotals extends StatelessWidget {
// Calculate the total fields based on item type. // Calculate the total fields based on item type.
double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0; double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
ItemType? itemType;
for (TrackedItem e in snapshot.data!) { for (TrackedItem e in snapshot.data!) {
if (itemType == null) { if (e.type != itemType) {
itemType = e.type!; throw "List in SummaryCardForTotals has incorrect item types, abort!";
} else if (itemType != e.type) {
throw "List in SummaryCardForTotals has multiple item types, abort!";
} }
if (e.type == ItemType.asset) { if (e.type == ItemType.asset) {
@@ -194,9 +192,6 @@ class SummaryCardForTotals extends StatelessWidget {
/* Load page variables based on calculated totals. */ /* Load page variables based on calculated totals. */
switch (itemType) { switch (itemType) {
case null:
break;
case ItemType.asset: case ItemType.asset:
_assetTotal = monthlyTotal; _assetTotal = monthlyTotal;
break; break;
+12 -2
View File
@@ -1,5 +1,9 @@
// Flutter
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// Local
import '/widgets/cards.dart';
/// TODO: /// TODO:
/// - Export DB (JSON?) /// - Export DB (JSON?)
/// - Import DB (JSON?) /// - Import DB (JSON?)
@@ -14,8 +18,14 @@ class SettingsPage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Center( return Center(
child: Text( child: Column(
"No settings yet. :)", mainAxisSize: MainAxisSize.max,
children: [
TitleCard(title: "Settings"),
Text(
"No settings exist yet. :)",
),
],
), ),
); );
} }
+272 -206
View File
@@ -9,14 +9,15 @@ import '/models/item_type.dart';
import '/models/expense.dart'; import '/models/expense.dart';
import '/models/frequency.dart'; import '/models/frequency.dart';
import '/db.dart'; import '/db.dart';
import '/widgets/cards.dart';
class TrackedItemPage extends StatefulWidget { class TrackedItemPage extends StatefulWidget {
final Future<List<TrackedItem>> assetsToLoad; final ItemType assetType;
final Function() notifyParent; final Function() notifyParent;
const TrackedItemPage({ const TrackedItemPage({
super.key, super.key,
required this.assetsToLoad, required this.assetType,
required this.notifyParent, required this.notifyParent,
}); });
@@ -25,12 +26,28 @@ class TrackedItemPage extends StatefulWidget {
} }
class _TrackedItemPageState extends State<TrackedItemPage> { class _TrackedItemPageState extends State<TrackedItemPage> {
late Future<List<TrackedItem>> _assetsToLoad;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
switch (widget.assetType) {
case ItemType.expense:
_assetsToLoad = DatabaseHelper.instance.getExpenses();
break;
case ItemType.income:
_assetsToLoad = DatabaseHelper.instance.getIncomes();
break;
case ItemType.asset:
_assetsToLoad = DatabaseHelper.instance.getAssets();
break;
default:
throw UnimplementedError("Unsure whch asset group to load.");
}
return FutureBuilder<List<TrackedItem>>( return FutureBuilder<List<TrackedItem>>(
future: widget.assetsToLoad, future: _assetsToLoad,
builder: builder:
(BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) { (BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
@@ -46,204 +63,230 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
"Add items to get started.", "Add items to get started.",
softWrap: true, softWrap: true,
) )
: ListView.builder( : Column(
itemCount: snapshot.data!.length, children: [
itemBuilder: (_, index) { TitleCard(title: widget.assetType.plural),
final TrackedItem curr = snapshot.data![index]; /*Text(
"${widget.assetType.description}",
final itemKey = Key(curr.id!.toString()); style: TextStyle(
final String itemTitle = curr.name; fontSize: 16.0,
decoration: TextDecoration.none,
final String itemAmount; fontWeight: FontWeight.bold,
if (curr.frequency != null) {
itemAmount =
"${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}";
} else {
itemAmount = curr.amount.toStringAsFixed(2);
}
final String itemDescription = curr.description;
final double itemDayAmount, itemMonthAmount, itemYearAmount;
final String estimateSymbolDaily,
estimateSymbolMonthly,
estimateSymbolYearly;
if (curr.frequency != null) {
itemDayAmount = curr.calcComparableAmountDaily();
estimateSymbolDaily = curr.frequency!.numDays
.toStringAsFixed(2)
.endsWith(".00") &&
itemDayAmount.toStringAsFixed(3).endsWith("0")
? ""
: "~";
itemMonthAmount =
(curr.calcComparableAmountYearly() / 12);
estimateSymbolMonthly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemMonthAmount.toStringAsFixed(3).endsWith("0")
? ""
: "~";
itemYearAmount = curr.calcComparableAmountYearly();
estimateSymbolYearly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemYearAmount.toStringAsFixed(3).endsWith("0")
? ""
: "~";
} else {
itemDayAmount = -1;
estimateSymbolDaily = "";
itemMonthAmount = curr.amount;
estimateSymbolMonthly = "";
itemYearAmount = -1;
estimateSymbolYearly = "";
}
final String monthlyTitle = curr.type == ItemType.asset
? ""
: " ${Frequency.monthly.title}";
final String itemTopText = itemDayAmount < 0
? ""
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
final String itemMiddleText = itemMonthAmount < 0
? ""
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle";
final String itemBottomText = itemYearAmount < 0
? ""
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
return Padding(
padding: const EdgeInsets.all(4.0),
child: Dismissible(
key: itemKey,
background: Container(
color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(Icons.delete),
Text("Delete"),
],
),
),
secondaryBackground: Container(
color: Colors.orange,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("Edit"),
Icon(Icons.edit),
],
),
),
onDismissed: (direction) {
setState(() {
snapshot.data!.remove(curr);
switch (direction) {
case DismissDirection.startToEnd:
// Remove the item from the database.
if (curr is Expense) {
DatabaseHelper.instance.removeExpense(
curr.id!,
);
} else if (curr is Income) {
DatabaseHelper.instance.removeIncome(
curr.id!,
);
} else if (curr is Asset) {
DatabaseHelper.instance.removeAsset(
curr.id!,
);
} else {
throw UnimplementedError(
"Cannot remove unimplemented item type.");
}
break;
case DismissDirection.endToStart:
// Open an edit dialog, then remove the item from the list.
showDialog(
context: context,
builder: (_) => AlertDialog(
content: TrackedItemInputDialog(
notifyParent: widget.notifyParent,
entry: curr,
amountText: curr.getAmountText(),
type: curr.type!,
),
),
);
break;
default:
UnimplementedError(
"Direction ${direction.toString()} not recognized.",
);
}
});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.onPrimary,
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
itemTitle,
style: TextStyle(fontSize: 20.0),
),
Text(
itemAmount,
style: TextStyle(fontSize: 12.0),
),
],
),
Expanded(
child: Center(
child: Text(
itemDescription,
style: TextStyle(
fontSize: 12.0,
),
softWrap: true,
textAlign: TextAlign.center,
),
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
itemTopText,
style: TextStyle(fontSize: 12.0),
),
Text(
itemMiddleText,
style: TextStyle(fontSize: 12.0),
),
Text(
itemBottomText,
style: TextStyle(fontSize: 12.0),
),
],
),
],
),
),
),
), ),
); ),*/
}, Expanded(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (_, index) {
final TrackedItem curr = snapshot.data![index];
final itemKey = Key(curr.id!.toString());
final String itemTitle = curr.name;
final String itemAmount;
if (curr.frequency != null) {
itemAmount =
"${curr.amount.toStringAsFixed(2)} ${curr.frequency!.title}";
} else {
itemAmount = curr.amount.toStringAsFixed(2);
}
final String itemDescription = curr.description;
final double itemDayAmount,
itemMonthAmount,
itemYearAmount;
final String estimateSymbolDaily,
estimateSymbolMonthly,
estimateSymbolYearly;
if (curr.frequency != null) {
itemDayAmount = curr.calcComparableAmountDaily();
estimateSymbolDaily = curr.frequency!.numDays
.toStringAsFixed(2)
.endsWith(".00") &&
itemDayAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
itemMonthAmount =
(curr.calcComparableAmountYearly() / 12);
estimateSymbolMonthly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemMonthAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
itemYearAmount = curr.calcComparableAmountYearly();
estimateSymbolYearly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemYearAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
} else {
itemDayAmount = -1;
estimateSymbolDaily = "";
itemMonthAmount = curr.amount;
estimateSymbolMonthly = "";
itemYearAmount = -1;
estimateSymbolYearly = "";
}
final String monthlyTitle =
curr.type == ItemType.asset
? ""
: " ${Frequency.monthly.title}";
final String itemTopText = itemDayAmount < 0
? ""
: "$estimateSymbolDaily${itemDayAmount.toStringAsFixed(2)} ${Frequency.daily.title}";
final String itemMiddleText = itemMonthAmount < 0
? ""
: "$estimateSymbolMonthly${itemMonthAmount.toStringAsFixed(2)}$monthlyTitle";
final String itemBottomText = itemYearAmount < 0
? ""
: "$estimateSymbolYearly${itemYearAmount.toStringAsFixed(2)} ${Frequency.yearly.title}";
return Padding(
padding: const EdgeInsets.all(4.0),
child: Dismissible(
key: itemKey,
background: Container(
color: Colors.red,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(Icons.delete),
Text("Delete"),
],
),
),
secondaryBackground: Container(
color: Colors.orange,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text("Edit"),
Icon(Icons.edit),
],
),
),
onDismissed: (direction) {
setState(() {
snapshot.data!.remove(curr);
switch (direction) {
case DismissDirection.startToEnd:
// Remove the item from the database.
if (curr is Expense) {
DatabaseHelper.instance.removeExpense(
curr.id!,
);
} else if (curr is Income) {
DatabaseHelper.instance.removeIncome(
curr.id!,
);
} else if (curr is Asset) {
DatabaseHelper.instance.removeAsset(
curr.id!,
);
} else {
throw UnimplementedError(
"Cannot remove unimplemented item type.");
}
break;
case DismissDirection.endToStart:
// Open an edit dialog, then remove the item from the list.
showDialog(
context: context,
builder: (_) => AlertDialog(
content: TrackedItemInputDialog(
notifyParent: widget.notifyParent,
entry: curr,
amountText: curr.getAmountText(),
type: curr.type!,
),
),
);
break;
default:
UnimplementedError(
"Direction ${direction.toString()} not recognized.",
);
}
});
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(4),
color: theme.colorScheme.onPrimary,
),
child: Padding(
padding: const EdgeInsets.all(4.0),
child: Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
itemTitle,
style: TextStyle(fontSize: 20.0),
),
Text(
itemAmount,
style: TextStyle(fontSize: 12.0),
),
],
),
Expanded(
child: Center(
child: Text(
itemDescription,
style: TextStyle(
fontSize: 12.0,
),
softWrap: true,
textAlign: TextAlign.center,
),
),
),
Column(
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Text(
itemTopText,
style: TextStyle(fontSize: 12.0),
),
Text(
itemMiddleText,
style: TextStyle(fontSize: 12.0),
),
Text(
itemBottomText,
style: TextStyle(fontSize: 12.0),
),
],
),
],
),
),
),
),
);
},
),
),
],
); );
}); });
} }
@@ -297,6 +340,21 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
String amountText = String amountText =
widget.amountText != null ? widget.amountText! : TrackedItem.amountText; widget.amountText != null ? widget.amountText! : TrackedItem.amountText;
Future<List<TrackedItem>> items;
switch (_type) {
case ItemType.expense:
items = DatabaseHelper.instance.getExpenses();
break;
case ItemType.income:
items = DatabaseHelper.instance.getIncomes();
break;
case ItemType.asset:
items = DatabaseHelper.instance.getAssets();
break;
default:
throw UnimplementedError("Cannot find unimplemented type.");
}
return Column( return Column(
// prevent AlertDialog from taking full vertical height. // prevent AlertDialog from taking full vertical height.
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
@@ -336,15 +394,14 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
? Text("New ${_type!.title}") ? Text("New ${_type!.title}")
: Text("Edit ${_type!.title}"), : Text("Edit ${_type!.title}"),
), ),
content: FutureBuilder<List<Expense>>( content: FutureBuilder<List<TrackedItem>>(
// TODO / TBD -- This should no longer only be Expenses. future: items,
future: DatabaseHelper.instance.getExpenses(),
builder: (BuildContext context, builder: (BuildContext context,
AsyncSnapshot<List<Expense>> snapshot) { AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Center(child: Text('Loading...')); return Center(child: Text('Loading...'));
} }
List<Expense> expenses = snapshot.data!; List<TrackedItem> expenses = snapshot.data!;
return Form( return Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
@@ -392,7 +449,16 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
return "$amountText must be a valid number."; return "$amountText must be a valid number.";
} }
if (double.parse(value) < 0) { if (double.parse(value) < 0) {
return "Please use the Income page rather than having negative expenses."; switch (_type) {
case ItemType.expense:
return "Please use the Income page.";
case ItemType.income:
return "Please use the Expense page.";
default:
break;
}
} }
if (double.parse(value) < 0.01) { if (double.parse(value) < 0.01) {
return "$amountText must be one hundreth (0.01) or higher."; return "$amountText must be one hundreth (0.01) or higher.";
+6 -1
View File
@@ -1,3 +1,4 @@
// Flutter
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class TitleCard extends StatelessWidget { class TitleCard extends StatelessWidget {
@@ -15,7 +16,11 @@ class TitleCard extends StatelessWidget {
child: Center( child: Center(
child: Text( child: Text(
title, title,
style: TextStyle(fontSize: 20), style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
), ),
), ),
); );
@@ -5,11 +5,13 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import package_info_plus
import path_provider_foundation import path_provider_foundation
import sqflite_darwin import sqflite_darwin
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
+61 -21
View File
@@ -21,10 +21,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.1"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@@ -45,10 +45,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: fake_async name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.2" version: "1.3.3"
ffi: ffi:
dependency: transitive dependency: transitive
description: description:
@@ -80,30 +80,46 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
http:
dependency: transitive
description:
name: http
sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f
url: "https://pub.dev"
source: hosted
version: "1.3.0"
http_parser:
dependency: transitive
description:
name: http_parser
sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571"
url: "https://pub.dev"
source: hosted
version: "4.1.2"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.0.8" version: "11.0.2"
leak_tracker_flutter_testing: leak_tracker_flutter_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_flutter_testing name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.9" version: "3.0.10"
leak_tracker_testing: leak_tracker_testing:
dependency: transitive dependency: transitive
description: description:
name: leak_tracker_testing name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
lints: lints:
dependency: transitive dependency: transitive
description: description:
@@ -116,26 +132,42 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.17" version: "0.12.20"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.11.1" version: "0.13.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.16.0" version: "1.19.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://pub.dev"
source: hosted
version: "8.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
path: path:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -321,10 +353,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.7.4" version: "0.7.12"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@@ -401,10 +433,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: vector_math name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.4.3"
vm_service: vm_service:
dependency: transitive dependency: transitive
description: description:
@@ -421,6 +453,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev"
source: hosted
version: "5.12.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@@ -430,5 +470,5 @@ packages:
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
sdks: sdks:
dart: ">=3.7.0 <4.0.0" dart: ">=3.11.0-0 <4.0.0"
flutter: ">=3.27.0" flutter: ">=3.27.0"
+3 -1
View File
@@ -1,7 +1,8 @@
name: expense_tracker name: expense_tracker
homepage: https://git.hyperling.com/me/flutter-expense-tracker
description: Track recurring expenses against income and liquid assets. description: Track recurring expenses against income and liquid assets.
publish_to: 'none' publish_to: 'none'
version: 0.1.2 version: 0.1.4
environment: environment:
sdk: ^3.6.1 sdk: ^3.6.1
@@ -9,6 +10,7 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
package_info_plus: ^8.3.0
path: ^1.9.0 path: ^1.9.0
path_provider: ^2.1.5 path_provider: ^2.1.5
sqflite: ^2.4.1 sqflite: ^2.4.1