61 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
me fa852faadc Use the dispose() method to reset the global values. Works excellently!! :D 2025-03-26 13:17:08 -07:00
me 6f9d0d8afb The initial page load now delays properly and shows the correct value rather than 0. Updating an item and going back to the page still shows old data though. 2025-03-26 12:39:09 -07:00
me 75cc72678b Add TBD of where the Income duplicate name validator is going wrong. 2025-03-25 08:45:44 -07:00
me 2970431b91 Increment the version. 2025-03-21 15:03:16 -07:00
me 4d0dc03c69 Create script which builds the delverables and places them in a convenient place with convenient names. 2025-03-21 14:58:56 -07:00
me bdb3fc5c7a Ensure the README is up to date. Reword slightly. 2025-03-21 14:22:14 -07:00
me da2ae6206a Add additional Help content and modify the Code Repo button to more specifically be Source Code for the project. 2025-03-21 14:21:43 -07:00
me f5b22c6c1c Create and use a better app icon. 2025-03-21 14:08:26 -07:00
me 9f7773f724 Add functionality to Help buttons for opening URLs. 2025-03-21 13:40:11 -07:00
me dec343af09 Add plugin url_launcher for Help page. 2025-03-21 12:17:10 -07:00
me 0890e15bfb Change the double SafeArea for Linux. 2025-03-21 12:05:59 -07:00
me 55fd4092ac Prevent the Help buttons from being underneath the Nav Bar on Android. 2025-03-21 12:05:03 -07:00
me 247b88daa2 Replace placeholder with a basic message for now. 2025-03-21 12:04:48 -07:00
me d604a59ad1 Accept a recommended change since null is now handled properly. 2025-03-21 11:39:23 -07:00
me 5bf6a0889c Finish changing the database name. 2025-03-21 11:38:50 -07:00
me f3d6b8abbe Increment the app version slightly now that it's working and has an icon. 2025-03-21 11:03:02 -07:00
me 8ea85586d5 Begin using the old project's icon. 2025-03-21 11:02:57 -07:00
me d1633fd155 Add media from android-expense-tracker project. 2025-03-21 10:54:29 -07:00
me 26718a41e0 Shorten app name for home screen. 2025-03-21 10:54:14 -07:00
me 2c241d9113 Change database file name to be more user-friendly. 2025-03-21 10:53:48 -07:00
me adea1eeb02 Use the non-KTS files after all, the KTS versions do not launch successfully. 2025-03-21 10:34:56 -07:00
me f441caebf7 Unimplemented got thrown for null. Add it to the list so that lists with no items still work properly. 2025-03-21 10:34:25 -07:00
me 48615b3438 Hide another non-KTS gradle file to ensure that Android config is done properly. 2025-03-21 10:27:41 -07:00
me bc3b3a4109 Ensure project is properly named across all files. 2025-03-21 10:24:46 -07:00
64 changed files with 567 additions and 207 deletions

No files matched your search

+7
View File
@@ -44,3 +44,10 @@ app.*.map.json
/android/app/release
/android/app/.*
# Ignore the releases folder.
releases/*
# Ignore build files from Android.
android/build/*
android/app/build/*
+4 -4
View File
@@ -1,6 +1,6 @@
# flutter_expense_tracker
# Recurring Expense Tracker
Recurring expense tracker app for Android.
Recurring expense tracker app for Linux and 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.
Add an expense as daily, weekly, monthly, etc, then see how it affects your
liquid assets based on your reported income over different time projections.
+11
View File
@@ -1 +1,12 @@
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 = "../.."
}
+12 -13
View File
@@ -1,29 +1,30 @@
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
id("com.android.application")
id("kotlin-android")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
// AGP 9 bundles Kotlin natively - no explicit plugin needed here
id("dev.flutter.flutter-gradle-plugin")
}
android {
namespace = "com.hyperling.flutter_expense_tracker"
namespace = "com.hyperling.expense_tracker"
compileSdk = flutter.compileSdkVersion
ndkVersion = flutter.ndkVersion
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
// AGP 9's bundled Kotlin - use the native DSL
kotlin {
compilerOptions {
jvmTarget.set(JvmTarget.JVM_17)
}
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.hyperling.flutter_expense_tracker"
// You can update the following values to match your application needs.
// For more information, see: https://flutter.dev/to/review-gradle-config.
applicationId = "com.hyperling.expense_tracker"
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode
@@ -32,8 +33,6 @@ android {
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.getByName("debug")
}
}
+6 -1
View File
@@ -1,6 +1,6 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application
android:label="Recurring Expense Tracker"
android:label="Expenses"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
@@ -41,5 +41,10 @@
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
<!-- Allow opening of web URLs -->
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
</queries>
</manifest>
Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

@@ -1,5 +0,0 @@
package com.example.flutter_empty
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity()
@@ -1,4 +1,4 @@
package com.hyperling.flutter_expense_tracker
package com.hyperling.expense_tracker
import io.flutter.embedding.android.FlutterActivity
@@ -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.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 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 {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = "../build"
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(":app")
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}
// Expose Kotlin version for Flutter's plugin validation (AGP 9 bundles Kotlin natively)
ext.kotlin_version = "2.4.20"
+6
View File
@@ -1,3 +1,9 @@
org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=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
zipStoreBase=GRADLE_USER_HOME
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 {
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")
+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'
+46
View File
@@ -0,0 +1,46 @@
#!/usr/bin/env bash
DIR="$(dirname -- "${BASH_SOURCE[0]}")"
RELEASE_DIR=$DIR/releases
echo "*** Prepare for Builds ***"
cd $DIR
pwd
mkdir -pv $RELEASE_DIR
echo "*** Get Version ***"
grep 'version:' pubspec.yaml | while read v_text v_number; do
# Skip any newlines found while grepping.
if [[ -z $v_number ]]; then
continue
fi
echo "Creating assets for version '$v_number'."
# Set Up Variables
ANDROID_APK="build/app/outputs/flutter-apk/app-release.apk"
APK_RENAME="$RELEASE_DIR/ExpenseTracker_$v_number.apk"
LINUX_BUNDLE="build/linux/x64/release/bundle"
BUNDLE_RENAME="LinuxBundle_$v_number"
# Build Android App
echo -e "\n*** Android APK ***"
rm -v "$APK_RENAME"
flutter build apk
mv -v $ANDROID_APK "$APK_RENAME"
ls -sh "$APK_RENAME"
# Build Linux Project
echo -e "\n*** Linux Bundle ***"
rm -rv "$RELEASE_DIR/$BUNDLE_RENAME"*
flutter build linux
mv -v $LINUX_BUNDLE "$RELEASE_DIR/$BUNDLE_RENAME"
cd $RELEASE_DIR
zip -r $BUNDLE_RENAME.zip $BUNDLE_RENAME
rm -rv $BUNDLE_RENAME
ls -sh $BUNDLE_RENAME.zip
# Only one version should be found, but just in case, only use the top one!
break
done
+1 -1
View File
@@ -26,7 +26,7 @@ class DatabaseHelper {
Future<Database> _initDatabase() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, "com_hyperling_expense.db");
String path = join(documentsDirectory.path, "ExpenseTracker.sqlite.db");
return await openDatabase(
path,
version: 2,
+1 -1
View File
@@ -27,7 +27,7 @@ void main() {
if (testing) {
() async {
Directory documentsDirectory = await getApplicationDocumentsDirectory();
String path = join(documentsDirectory.path, 'com_hyperling_expense.db');
String path = join(documentsDirectory.path, 'ExpenseTracker.sqlite.db');
await deleteDatabase(path);
}();
}
+1 -1
View File
@@ -1,5 +1,5 @@
// Local
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/item_type.dart';
import '/models/tracked_item.dart';
import '/models/frequency.dart';
+6 -1
View File
@@ -2,21 +2,26 @@ enum ItemType {
expense(
title: "Expense",
plural: "Expenses",
description: "Items which cost revenue, or decrease asset value.",
),
income(
title: "Income",
plural: "Incomes",
plural: "Income",
description: "Items which bring in revenue, or increase asset value.",
),
asset(
title: "Asset",
plural: "Assets",
description: "Value which has been earned and can be spent.",
);
const ItemType({
required this.title,
required this.plural,
required this.description,
});
final String title;
final String plural;
final String description;
}
+5 -3
View File
@@ -1,5 +1,5 @@
// Local
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/item_type.dart';
import '/models/frequency.dart';
@@ -37,12 +37,14 @@ abstract class TrackedItem {
}
Map<String, dynamic> toMap() {
return frequency == null ? {
return frequency == null
? {
'id': id,
'name': name,
'amount': amount,
'description': description,
} : {
}
: {
'id': id,
'name': name,
'amount': amount,
+61 -6
View File
@@ -1,4 +1,25 @@
// Flutter
import '/models/item_type.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart';
// Local
import '/widgets/cards.dart';
_launchSite(String url) async {
try {
if (await canLaunchUrlString(url)) {
await launchUrlString(
url,
mode: LaunchMode.externalApplication,
);
} else {
throw "System does not think it can launch '$url'.";
}
} on Exception catch (e) {
throw e.toString();
}
}
class HelpPage extends StatelessWidget {
const HelpPage({
@@ -21,9 +42,35 @@ class HelpPage extends StatelessWidget {
),
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."),
TitleCard(title: "Help"),
Text(
"\t\t 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(
"\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"
" from highest to lowest so that the biggest impacts are"
" always in view."
"",
),
Text(
"\n\t\t To subscribe to Android updates, install Obtanium,"
" then use the URL from the Source Code button below."
" Otherwise the app needs installed manually by downloading"
" APKs from the Source Code /releases/ page. Linux users"
" currently need to install and update manually."
"",
),
//Text("Another paragraph.")
],
),
@@ -41,9 +88,13 @@ class HelpPage extends StatelessWidget {
color: theme.colorScheme.onPrimary,
),
child: TextButton.icon(
onPressed: () {},
onPressed: () {
_launchSite(
"https://git.hyperling.com/me/flutter-expense-tracker",
);
},
icon: Icon(Icons.code),
label: Text("Code Repository"),
label: Text("Source Code"),
),
),
),
@@ -57,7 +108,11 @@ class HelpPage extends StatelessWidget {
color: theme.colorScheme.onPrimary,
),
child: TextButton.icon(
onPressed: () {},
onPressed: () {
_launchSite(
"https://hyperling.com",
);
},
icon: Icon(Icons.web_asset),
label: Text("Personal Website"),
),
+38 -10
View File
@@ -1,14 +1,14 @@
// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/item_type.dart';
import 'dart:io';
import 'package:package_info_plus/package_info_plus.dart';
// Local
import '/pages/tracked_item.dart';
import '/pages/report.dart';
import '/pages/settings.dart';
import '/pages/help.dart';
import '/db.dart';
class HomePage extends StatefulWidget {
const HomePage({
@@ -26,14 +26,36 @@ class _HomePageState extends State<HomePage> {
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
Widget build(BuildContext context) {
Widget page;
Widget? dialog;
switch (pageSelected) {
case 0:
page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getExpenses(),
assetType: ItemType.expense,
notifyParent: refresh,
);
dialog = TrackedItemInputDialog(
@@ -43,7 +65,7 @@ class _HomePageState extends State<HomePage> {
break;
case 1:
page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getIncomes(),
assetType: ItemType.income,
notifyParent: refresh,
);
dialog = TrackedItemInputDialog(
@@ -53,7 +75,7 @@ class _HomePageState extends State<HomePage> {
break;
case 2:
page = TrackedItemPage(
assetsToLoad: DatabaseHelper.instance.getAssets(),
assetType: ItemType.asset,
notifyParent: refresh,
);
dialog = TrackedItemInputDialog(
@@ -97,15 +119,15 @@ class _HomePageState extends State<HomePage> {
destinations: [
NavigationRailDestination(
icon: Icon(Icons.payment),
label: Text('Expenses'),
label: Text(ItemType.expense.plural),
),
NavigationRailDestination(
icon: Icon(Icons.account_balance),
label: Text('Income'),
label: Text(ItemType.income.plural),
),
NavigationRailDestination(
icon: Icon(Icons.attach_money),
label: Text('Liquid Assets'),
label: Text(ItemType.asset.plural),
),
NavigationRailDestination(
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(
@@ -136,10 +160,11 @@ class _HomePageState extends State<HomePage> {
child: Center(child: page),
);
Widget? drawer, body;
Widget? drawer;
Widget body;
if (Platform.isAndroid || Platform.isIOS) {
drawer = navigation;
body = main;
body = SafeArea(child: main);
} else {
drawer = null;
body = Row(
@@ -158,6 +183,9 @@ class _HomePageState extends State<HomePage> {
drawer: drawer,
body: body,
floatingActionButton: floatingButton,
extendBody: false,
extendBodyBehindAppBar: false,
resizeToAvoidBottomInset: true,
);
});
}
+64 -25
View File
@@ -1,6 +1,7 @@
// Flutter
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_expense_tracker/models/item_type.dart';
import '/models/item_type.dart';
// Local
import '/db.dart';
@@ -10,14 +11,12 @@ import '/models/tracked_item.dart';
/// TODO:
/// - Projected Assets:
/// - 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 = 0,
_expenseMonthly = 0,
_expenseYearly = 0,
_incomeMonthly = 0,
_incomeYearly = 0;
double _assetTotal = -1,
_expenseMonthly = -1,
_expenseYearly = -1,
_incomeMonthly = -1,
_incomeYearly = -1;
class ProjectionPage extends StatefulWidget {
const ProjectionPage({
@@ -29,23 +28,51 @@ class ProjectionPage extends StatefulWidget {
}
class _ProjectionPageState extends State<ProjectionPage> {
bool _showProjections = true;
@override
void dispose() {
_assetTotal = -2;
_expenseMonthly = -2;
_expenseYearly = -2;
_incomeMonthly = -2;
_incomeYearly = -2;
super.dispose();
}
@override
Widget build(BuildContext context) {
// Summaries for display as well as calculation of totals for projections.
Widget expenseSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getExpenses(),
summaryTypeLabel: ItemType.expense.title,
itemType: ItemType.expense,
);
Widget incomeSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getIncomes(),
summaryTypeLabel: ItemType.income.title,
itemType: ItemType.income,
);
Widget assetSummary = SummaryCardForTotals(
list: DatabaseHelper.instance.getAssets(),
summaryTypeLabel: ItemType.asset.title,
itemType: ItemType.asset,
);
// Calculations for the projections.
Widget projections;
if (_assetTotal < 0 ||
_incomeMonthly < 0 ||
_incomeYearly < 0 ||
_expenseMonthly < 0 ||
_expenseYearly < 0) {
_showProjections = false;
Future.delayed(Duration(seconds: 1), () {
setState(() {
_showProjections = true;
});
});
}
if (_showProjections) {
double oneMonth = _assetTotal + _incomeMonthly - _expenseMonthly,
threeMonths = _assetTotal + (3 * (_incomeMonthly - _expenseMonthly)),
sixMonths = _assetTotal + (6 * (_incomeMonthly - _expenseMonthly)),
@@ -91,6 +118,24 @@ class _ProjectionPageState extends State<ProjectionPage> {
rightText: "",
);
projections = Column(
children: [
proj1,
proj2,
proj3,
proj4,
proj5,
proj6,
],
);
} else {
projections = Center(
child: SizedBox(
child: CircularProgressIndicator(),
),
);
}
// Return all of the UI elements.
return ListView(
children: [
@@ -99,12 +144,7 @@ class _ProjectionPageState extends State<ProjectionPage> {
incomeSummary,
assetSummary,
TitleCard(title: "Projections"),
proj1,
proj2,
proj3,
proj4,
proj5,
proj6,
projections,
],
);
}
@@ -114,14 +154,16 @@ class SummaryCardForTotals extends StatelessWidget {
const SummaryCardForTotals({
super.key,
required this.list,
required this.summaryTypeLabel,
required this.itemType,
});
final Future<List<TrackedItem>> list;
final String summaryTypeLabel;
final ItemType itemType;
@override
Widget build(BuildContext context) {
String summaryTypeLabel = itemType.title.toString();
return FutureBuilder<List<TrackedItem>>(
future: list,
builder: (
@@ -134,12 +176,9 @@ class SummaryCardForTotals extends StatelessWidget {
// Calculate the total fields based on item type.
double dailyTotal = 0, monthlyTotal = 0, yearlyTotal = 0;
ItemType? itemType;
for (TrackedItem e in snapshot.data!) {
if (itemType == null) {
itemType = e.type!;
} else if (itemType != e.type) {
throw "List in SummaryCardForTotals has multiple item types, abort!";
if (e.type != itemType) {
throw "List in SummaryCardForTotals has incorrect item types, abort!";
}
if (e.type == ItemType.asset) {
@@ -169,7 +208,7 @@ class SummaryCardForTotals extends StatelessWidget {
default:
throw UnimplementedError(
"Item type ${itemType!.title} not handled in SummaryCardForTotals!",
"Item type ${itemType.title} not handled in SummaryCardForTotals!",
);
}
+15 -1
View File
@@ -1,5 +1,9 @@
// Flutter
import 'package:flutter/material.dart';
// Local
import '/widgets/cards.dart';
/// TODO:
/// - Export DB (JSON?)
/// - Import DB (JSON?)
@@ -13,6 +17,16 @@ class SettingsPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Placeholder();
return Center(
child: Column(
mainAxisSize: MainAxisSize.max,
children: [
TitleCard(title: "Settings"),
Text(
"No settings exist yet. :)",
),
],
),
);
}
}
+85 -18
View File
@@ -1,7 +1,7 @@
// Flutter
import 'package:flutter/material.dart';
import 'package:flutter_expense_tracker/models/asset.dart';
import 'package:flutter_expense_tracker/models/income.dart';
import '/models/asset.dart';
import '/models/income.dart';
// Local
import '/models/tracked_item.dart';
@@ -9,14 +9,15 @@ import '/models/item_type.dart';
import '/models/expense.dart';
import '/models/frequency.dart';
import '/db.dart';
import '/widgets/cards.dart';
class TrackedItemPage extends StatefulWidget {
final Future<List<TrackedItem>> assetsToLoad;
final ItemType assetType;
final Function() notifyParent;
const TrackedItemPage({
super.key,
required this.assetsToLoad,
required this.assetType,
required this.notifyParent,
});
@@ -25,12 +26,28 @@ class TrackedItemPage extends StatefulWidget {
}
class _TrackedItemPageState extends State<TrackedItemPage> {
late Future<List<TrackedItem>> _assetsToLoad;
@override
Widget build(BuildContext 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>>(
future: widget.assetsToLoad,
future: _assetsToLoad,
builder:
(BuildContext context, AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) {
@@ -46,7 +63,19 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
"Add items to get started.",
softWrap: true,
)
: ListView.builder(
: Column(
children: [
TitleCard(title: widget.assetType.plural),
/*Text(
"${widget.assetType.description}",
style: TextStyle(
fontSize: 16.0,
decoration: TextDecoration.none,
fontWeight: FontWeight.bold,
),
),*/
Expanded(
child: ListView.builder(
itemCount: snapshot.data!.length,
itemBuilder: (_, index) {
final TrackedItem curr = snapshot.data![index];
@@ -63,7 +92,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
}
final String itemDescription = curr.description;
final double itemDayAmount, itemMonthAmount, itemYearAmount;
final double itemDayAmount,
itemMonthAmount,
itemYearAmount;
final String estimateSymbolDaily,
estimateSymbolMonthly,
estimateSymbolYearly;
@@ -73,7 +104,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolDaily = curr.frequency!.numDays
.toStringAsFixed(2)
.endsWith(".00") &&
itemDayAmount.toStringAsFixed(3).endsWith("0")
itemDayAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
@@ -82,7 +115,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolMonthly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemMonthAmount.toStringAsFixed(3).endsWith("0")
itemMonthAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
@@ -90,7 +125,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolYearly = curr.frequency!.timesPerYear
.toStringAsFixed(2)
.endsWith(".00") &&
itemYearAmount.toStringAsFixed(3).endsWith("0")
itemYearAmount
.toStringAsFixed(3)
.endsWith("0")
? ""
: "~";
} else {
@@ -102,7 +139,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
estimateSymbolYearly = "";
}
final String monthlyTitle = curr.type == ItemType.asset
final String monthlyTitle =
curr.type == ItemType.asset
? ""
: " ${Frequency.monthly.title}";
@@ -196,7 +234,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
children: [
Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
Text(
itemTitle,
@@ -221,7 +260,8 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
),
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment:
CrossAxisAlignment.end,
children: [
Text(
itemTopText,
@@ -244,6 +284,9 @@ class _TrackedItemPageState extends State<TrackedItemPage> {
),
);
},
),
),
],
);
});
}
@@ -297,6 +340,21 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
String 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(
// prevent AlertDialog from taking full vertical height.
mainAxisSize: MainAxisSize.min,
@@ -336,14 +394,14 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
? Text("New ${_type!.title}")
: Text("Edit ${_type!.title}"),
),
content: FutureBuilder<List<Expense>>(
future: DatabaseHelper.instance.getExpenses(),
content: FutureBuilder<List<TrackedItem>>(
future: items,
builder: (BuildContext context,
AsyncSnapshot<List<Expense>> snapshot) {
AsyncSnapshot<List<TrackedItem>> snapshot) {
if (!snapshot.hasData) {
return Center(child: Text('Loading...'));
}
List<Expense> expenses = snapshot.data!;
List<TrackedItem> expenses = snapshot.data!;
return Form(
key: _formKey,
child: Column(
@@ -391,7 +449,16 @@ class _TrackedItemInputDialogState extends State<TrackedItemInputDialog> {
return "$amountText must be a valid number.";
}
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) {
return "$amountText must be one hundreth (0.01) or higher.";
+6 -1
View File
@@ -1,3 +1,4 @@
// Flutter
import 'package:flutter/material.dart';
class TitleCard extends StatelessWidget {
@@ -15,7 +16,11 @@ class TitleCard extends StatelessWidget {
child: Center(
child: Text(
title,
style: TextStyle(fontSize: 20),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
decoration: TextDecoration.underline,
),
),
),
);
@@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
}
+1
View File
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -5,10 +5,14 @@
import FlutterMacOS
import Foundation
import package_info_plus
import path_provider_foundation
import sqflite_darwin
import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

+130 -21
View File
@@ -21,10 +21,10 @@ packages:
dependency: transitive
description:
name: characters
sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
url: "https://pub.dev"
source: hosted
version: "1.4.0"
version: "1.4.1"
clock:
dependency: transitive
description:
@@ -45,10 +45,10 @@ packages:
dependency: transitive
description:
name: fake_async
sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc"
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
url: "https://pub.dev"
source: hosted
version: "1.3.2"
version: "1.3.3"
ffi:
dependency: transitive
description:
@@ -75,30 +75,51 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
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:
dependency: transitive
description:
name: leak_tracker
sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
url: "https://pub.dev"
source: hosted
version: "10.0.8"
version: "11.0.2"
leak_tracker_flutter_testing:
dependency: transitive
description:
name: leak_tracker_flutter_testing
sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
url: "https://pub.dev"
source: hosted
version: "3.0.9"
version: "3.0.10"
leak_tracker_testing:
dependency: transitive
description:
name: leak_tracker_testing
sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3"
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
version: "3.0.2"
lints:
dependency: transitive
description:
@@ -111,26 +132,42 @@ packages:
dependency: transitive
description:
name: matcher
sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
url: "https://pub.dev"
source: hosted
version: "0.12.17"
version: "0.12.20"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
url: "https://pub.dev"
source: hosted
version: "0.11.1"
version: "0.13.0"
meta:
dependency: transitive
description:
name: meta
sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
url: "https://pub.dev"
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:
dependency: "direct main"
description:
@@ -316,10 +353,10 @@ packages:
dependency: transitive
description:
name: test_api
sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
url: "https://pub.dev"
source: hosted
version: "0.7.4"
version: "0.7.12"
typed_data:
dependency: transitive
description:
@@ -328,14 +365,78 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.0"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "1d0eae19bd7606ef60fe69ef3b312a437a16549476c42321d5dc1506c9ca3bf4"
url: "https://pub.dev"
source: hosted
version: "6.3.15"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9"
url: "https://pub.dev"
source: hosted
version: "2.4.0"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
vector_math:
dependency: transitive
description:
name: vector_math
sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
sha256: "92b9910f66ed1057fd4da7b040ae7c74cafacf885bdc81be496928d5049b032d"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version: "2.4.3"
vm_service:
dependency: transitive
description:
@@ -352,6 +453,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: dc6ecaa00a7c708e5b4d10ee7bec8c270e9276dfcab1783f57e9962d7884305f
url: "https://pub.dev"
source: hosted
version: "5.12.0"
xdg_directories:
dependency: transitive
description:
@@ -361,5 +470,5 @@ packages:
source: hosted
version: "1.1.0"
sdks:
dart: ">=3.7.0 <4.0.0"
dart: ">=3.11.0-0 <4.0.0"
flutter: ">=3.27.0"
+5 -2
View File
@@ -1,7 +1,8 @@
name: flutter_expense_tracker
name: expense_tracker
homepage: https://git.hyperling.com/me/flutter-expense-tracker
description: Track recurring expenses against income and liquid assets.
publish_to: 'none'
version: 0.1.0
version: 0.1.4
environment:
sdk: ^3.6.1
@@ -9,10 +10,12 @@ environment:
dependencies:
flutter:
sdk: flutter
package_info_plus: ^8.3.0
path: ^1.9.0
path_provider: ^2.1.5
sqflite: ^2.4.1
sqflite_common_ffi: ^2.3.4+4
url_launcher: ^6.3.1
dev_dependencies:
flutter_test:
+2 -2
View File
@@ -8,12 +8,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_expense_tracker/main.dart';
import 'package: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());
await tester.pumpWidget(const MainApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
@@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}
+1
View File
@@ -3,6 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST