Compare commits
64 commits
Author | SHA1 | Date | |
---|---|---|---|
513a276031 | |||
|
2d50509163 | ||
ef52caa836 | |||
300f359070 | |||
6530b58c19 | |||
2339739ff8 | |||
5f99e35709 | |||
fd04bc1dc4 | |||
1b7eac52ee | |||
2cc628ce89 | |||
68a89444dd | |||
4cd4913d35 | |||
8665db6c0f | |||
6a5ddedffd | |||
dbcc7ad972 | |||
d7dbf4fff0 | |||
01b9decfdc | |||
5733f52572 | |||
66848fdc2d | |||
031d9c2ea3 | |||
537b520288 | |||
4808a142aa | |||
1c2ef630c3 | |||
ecc14affe8 | |||
cb5dbeffcf | |||
1d1391d0df | |||
32432d760c | |||
ba3b963d84 | |||
e1d3a54ebf | |||
8e46b936a8 | |||
d93eac4f04 | |||
94eb563217 | |||
cddb58c118 | |||
62e0c2ba3d | |||
07908797d5 | |||
1855ed4e6b | |||
09d5bbb953 | |||
|
80f2ae9f7f | ||
|
481716a3b2 | ||
|
8ff63c9492 | ||
|
1faa5e2f78 | ||
|
5cbb9a12dc | ||
|
58dd06f39f | ||
|
d832080a6d | ||
|
a90a39feff | ||
41d45e96fb | |||
|
3ff35fd427 | ||
|
f7b6219d31 | ||
|
638b6eef67 | ||
|
92e185dcb1 | ||
|
85fefda4d8 | ||
|
7c8535b99d | ||
|
15d2590a8e | ||
|
bd17e12e1b | ||
|
af04accabf | ||
|
c7e40b12b7 | ||
|
4a3f097b02 | ||
|
e5e803057a | ||
|
a6872408fa | ||
|
4d7144267e | ||
|
aeda571f35 | ||
|
776e9b056a | ||
|
7dbf4c969a | ||
|
76da494804 |
67 changed files with 1988 additions and 1003 deletions
2
.flutter
2
.flutter
|
@ -1 +1 @@
|
||||||
Subproject commit 312b9e81e93663b38f8e58d540be860fc75372b9
|
Subproject commit 2feea7a4071e25c1e3aac9c17016531bc4442f2a
|
|
@ -1,3 +1,9 @@
|
||||||
|
# 2.0.0
|
||||||
|
- Upgrade dependencies
|
||||||
|
- Use less `await`s in WalletManager class
|
||||||
|
- Added debt management
|
||||||
|
- Changed android app ID
|
||||||
|
|
||||||
# 1.1.1
|
# 1.1.1
|
||||||
- Removed deprecated code
|
- Removed deprecated code
|
||||||
- Updated dependencies
|
- Updated dependencies
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
# prasule
|
# prasule
|
||||||
[![Codemagic build status](https://api.codemagic.io/apps/64faee78aae8c48abc70dbc6/64faee78aae8c48abc70dbc5/status_badge.svg)](https://codemagic.io/apps/64faee78aae8c48abc70dbc6/64faee78aae8c48abc70dbc5/latest_build) [![Bug issue count](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.mnau.xyz%2Fapi%2Fv1%2Frepos%2Fhernik%2Fprasule%2Fissues%3Flabels%3DKind%2FBug&query=%24.length&logo=forgejo&label=bug%20issues&color=red)](https://git.mnau.xyz/hernik/prasule/issues?q=&type=all&sort=&state=open&labels=144&milestone=0&project=0&assignee=0&poster=0) [![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/bf1f40b0-c8c0-4f72-8ad6-c861ecdcc90c.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/bf1f40b0-c8c0-4f72-8ad6-c861ecdcc90c) [![Translation status](https://hosted.weblate.org/widget/prasule/svg-badge.svg)](https://hosted.weblate.org/engage/prasule/) [![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page)
|
[![Coverage](https://sq.mnau.xyz/api/project_badges/measure?project=prasule&metric=coverage&token=sqb_098860bff1a308465ca633fe569ec3d24c9d8e4b)](https://sq.mnau.xyz/dashboard?id=prasule) [![Maintainability Rating](https://sq.mnau.xyz/api/project_badges/measure?project=prasule&metric=sqale_rating&token=sqb_098860bff1a308465ca633fe569ec3d24c9d8e4b)](https://sq.mnau.xyz/dashboard?id=prasule) [![Bug issue count](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fgit.mnau.xyz%2Fapi%2Fv1%2Frepos%2Fhernik%2Fprasule%2Fissues%3Flabels%3DKind%2FBug&query=%24.length&logo=forgejo&label=bug%20issues&color=red)](https://git.mnau.xyz/hernik/prasule/issues?q=&type=all&sort=&state=open&labels=144&milestone=0&project=0&assignee=0&poster=0) [![Time spent on project](https://wt.mnau.xyz/api/badge/hernik/interval:today/project:prasule)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/bf1f40b0-c8c0-4f72-8ad6-c861ecdcc90c) [![Translation status](https://hosted.weblate.org/widget/prasule/svg-badge.svg)](https://hosted.weblate.org/engage/prasule/) [![Please don't upload to GitHub](https://nogithub.codeberg.page/badge.svg)](https://nogithub.codeberg.page) [![Codemagic build status](https://api.codemagic.io/apps/64faee78aae8c48abc70dbc6/6698dab78938fa88eaef9f82/status_badge.svg)](https://codemagic.io/app/64faee78aae8c48abc70dbc6/6698dab78938fa88eaef9f82/latest_build)
|
||||||
|
|
||||||
Expense manager
|
Expense manager
|
||||||
|
|
||||||
- [Apple Testflight](https://testflight.apple.com/join/C22pcnPc)
|
- [Apple Testflight](https://testflight.apple.com/join/C22pcnPc)
|
||||||
- [Google Play beta testing](https://play.google.com/store/apps/details?id=cafe.caras.prasule)
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
``` Prašule - simple, private & open-source expense tracker
|
``` Prašule - simple, private & open-source expense tracker
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
# packages, and plugins designed to encourage good coding practices.
|
# packages, and plugins designed to encourage good coding practices.
|
||||||
include: package:very_good_analysis/analysis_options.yaml
|
include: package:very_good_analysis/analysis_options.yaml
|
||||||
|
|
||||||
|
analyzer:
|
||||||
|
exclude: [/**/*.g.dart]
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
# The lint rules applied to this project can be customized in the
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
||||||
|
|
|
@ -48,11 +48,11 @@ android {
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
applicationId "cafe.caras.prasule"
|
applicationId "wtf.caras.prasule"
|
||||||
// You can update the following values to match your application needs.
|
// You can update the following values to match your application needs.
|
||||||
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 33
|
targetSdkVersion 34
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
}
|
}
|
||||||
|
|
3
assets/icon/dynamic_foreground.png.license
Normal file
3
assets/icon/dynamic_foreground.png.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
3
assets/icon/full_ico.png.license
Normal file
3
assets/icon/full_ico.png.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
3
assets/tessdata/eng.traineddata.license
Normal file
3
assets/tessdata/eng.traineddata.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Tesseract-OCR contributors
|
||||||
|
|
||||||
|
SPDX-License-Identifier: Apache-2.0
|
3
assets/tessdata_config.json.license
Normal file
3
assets/tessdata_config.json.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
|
||||||
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
@ -1,7 +1,12 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:integration_test/integration_test.dart';
|
import 'package:integration_test/integration_test.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/entry_data.dart';
|
import 'package:prasule/api/entry_data.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
|
@ -14,15 +19,18 @@ import 'package:prasule/pw/platformfield.dart';
|
||||||
void main() {
|
void main() {
|
||||||
final logger = Logger();
|
final logger = Logger();
|
||||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
setUp(() async {
|
||||||
|
WalletManager.walletPath =
|
||||||
|
"${(await getApplicationDocumentsDirectory()).path}/wallets";
|
||||||
|
});
|
||||||
group("Test classes and API", () {
|
group("Test classes and API", () {
|
||||||
test("Test wallet operations", () async {
|
test("Test wallet operations", () async {
|
||||||
expect(
|
expect(
|
||||||
(await WalletManager.listWallets()).length,
|
WalletManager.listWallets().length,
|
||||||
equals(0),
|
equals(0),
|
||||||
); // check that there are no other wallets
|
); // check that there are no other wallets
|
||||||
await WalletManager.saveWallet(Wallet.empty);
|
WalletManager.saveWallet(Wallet.empty);
|
||||||
var w = (await WalletManager.listWallets()).firstOrNull;
|
var w = WalletManager.listWallets().firstOrNull;
|
||||||
expect(w, isNotNull); // check that the wallet was successfully saved
|
expect(w, isNotNull); // check that the wallet was successfully saved
|
||||||
expect(w!.categories.length, equals(1));
|
expect(w!.categories.length, equals(1));
|
||||||
w.categories.add(
|
w.categories.add(
|
||||||
|
@ -43,8 +51,8 @@ void main() {
|
||||||
id: w.nextId,
|
id: w.nextId,
|
||||||
),
|
),
|
||||||
); // create test entry
|
); // create test entry
|
||||||
await WalletManager.saveWallet(w); // save again
|
WalletManager.saveWallet(w); // save again
|
||||||
w = await WalletManager.loadWallet(w.name); // try loading manually
|
w = WalletManager.loadWallet(w.name); // try loading manually
|
||||||
final e = w.entries.where((element) => element.id == testId).firstOrNull;
|
final e = w.entries.where((element) => element.id == testId).firstOrNull;
|
||||||
expect(
|
expect(
|
||||||
e,
|
e,
|
||||||
|
@ -55,9 +63,9 @@ void main() {
|
||||||
equals(1),
|
equals(1),
|
||||||
); // check that the category exists too
|
); // check that the category exists too
|
||||||
|
|
||||||
await WalletManager.deleteWallet(w);
|
WalletManager.deleteWallet(w);
|
||||||
expect(
|
expect(
|
||||||
(await WalletManager.listWallets()).length,
|
WalletManager.listWallets().length,
|
||||||
equals(0),
|
equals(0),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -66,7 +74,7 @@ void main() {
|
||||||
group("Test app functionality:", () {
|
group("Test app functionality:", () {
|
||||||
testWidgets('First-time setup', (WidgetTester tester) async {
|
testWidgets('First-time setup', (WidgetTester tester) async {
|
||||||
// Delete all data
|
// Delete all data
|
||||||
await WalletManager.deleteAllData();
|
WalletManager.deleteAllData();
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
const MyApp(
|
const MyApp(
|
||||||
|
@ -113,13 +121,12 @@ void main() {
|
||||||
|
|
||||||
testWidgets('Test rendering of entries', (WidgetTester tester) async {
|
testWidgets('Test rendering of entries', (WidgetTester tester) async {
|
||||||
// Delete all data
|
// Delete all data
|
||||||
await WalletManager.deleteAllData();
|
WalletManager.deleteAllData();
|
||||||
expect((await WalletManager.listWallets()).length, equals(0));
|
expect(WalletManager.listWallets().length, equals(0));
|
||||||
|
|
||||||
// Create test data
|
// Create test data
|
||||||
final w = Wallet.empty;
|
Wallet.empty.createTestEntries();
|
||||||
await w.createTestEntries();
|
expect(WalletManager.listWallets().length, equals(1));
|
||||||
expect((await WalletManager.listWallets()).length, equals(1));
|
|
||||||
|
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
|
|
|
@ -465,7 +465,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD)";
|
ARCHS = x86_64;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
@ -647,7 +647,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD)";
|
ARCHS = x86_64;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
@ -672,7 +672,7 @@
|
||||||
isa = XCBuildConfiguration;
|
isa = XCBuildConfiguration;
|
||||||
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
buildSettings = {
|
buildSettings = {
|
||||||
ARCHS = "$(ARCHS_STANDARD)";
|
ARCHS = x86_64;
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
CLANG_ENABLE_MODULES = YES;
|
CLANG_ENABLE_MODULES = YES;
|
||||||
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
part 'category.g.dart';
|
part 'category.g.dart';
|
||||||
|
@ -14,25 +18,43 @@ class WalletCategory {
|
||||||
required this.color,
|
required this.color,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Connects the generated fromJson method
|
/// Generates a class instance from a Map
|
||||||
factory WalletCategory.fromJson(Map<String, dynamic> json) =>
|
factory WalletCategory.fromJson(Map<String, dynamic> json) =>
|
||||||
_$WalletCategoryFromJson(json);
|
_$WalletCategoryFromJson(json);
|
||||||
|
|
||||||
|
/// Default [WalletCategory] instance for json_serializable
|
||||||
|
factory WalletCategory.unknown() => WalletCategory(
|
||||||
|
name: "Unknown",
|
||||||
|
id: -1,
|
||||||
|
icon: Icons.question_mark,
|
||||||
|
color: Colors.green,
|
||||||
|
);
|
||||||
|
|
||||||
/// User-defined name
|
/// User-defined name
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
/// Unique identificator of the category
|
/// Unique identificator of the category
|
||||||
|
@JsonKey(required: true, disallowNullValue: true)
|
||||||
final int id;
|
final int id;
|
||||||
|
|
||||||
/// Selected Icon for the category
|
/// Selected Icon for the category
|
||||||
@JsonKey(fromJson: _iconDataFromJson, toJson: _iconDataToJson)
|
@JsonKey(
|
||||||
|
fromJson: _iconDataFromJson,
|
||||||
|
toJson: _iconDataToJson,
|
||||||
|
defaultValue: _defaultIcon,
|
||||||
|
)
|
||||||
IconData icon;
|
IconData icon;
|
||||||
|
|
||||||
/// The color that will be displayed with entry
|
/// The color that will be displayed with entry
|
||||||
@JsonKey(fromJson: _colorFromJson, toJson: _colorToJson)
|
@JsonKey(
|
||||||
|
fromJson: _colorFromJson,
|
||||||
|
toJson: _colorToJson,
|
||||||
|
defaultValue: _defaultColor,
|
||||||
|
)
|
||||||
Color color;
|
Color color;
|
||||||
|
|
||||||
/// Connects the generated toJson method
|
/// Converts the data in this instance into a Map
|
||||||
Map<String, dynamic> toJson() => _$WalletCategoryToJson(this);
|
Map<String, dynamic> toJson() => _$WalletCategoryToJson(this);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -58,3 +80,6 @@ enum EntryType {
|
||||||
/// Income
|
/// Income
|
||||||
income
|
income
|
||||||
}
|
}
|
||||||
|
|
||||||
|
IconData _defaultIcon() => Icons.question_mark;
|
||||||
|
Color _defaultColor() => Colors.green;
|
||||||
|
|
|
@ -6,13 +6,23 @@ part of 'category.dart';
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
WalletCategory _$WalletCategoryFromJson(Map<String, dynamic> json) =>
|
WalletCategory _$WalletCategoryFromJson(Map<String, dynamic> json) {
|
||||||
WalletCategory(
|
$checkKeys(
|
||||||
name: json['name'] as String,
|
json,
|
||||||
id: json['id'] as int,
|
requiredKeys: const ['id'],
|
||||||
icon: _iconDataFromJson(json['icon'] as Map<String, dynamic>),
|
disallowNullValues: const ['id'],
|
||||||
color: _colorFromJson(json['color'] as int),
|
);
|
||||||
);
|
return WalletCategory(
|
||||||
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
icon: json['icon'] == null
|
||||||
|
? _defaultIcon()
|
||||||
|
: _iconDataFromJson(json['icon'] as Map<String, dynamic>),
|
||||||
|
color: json['color'] == null
|
||||||
|
? _defaultColor()
|
||||||
|
: _colorFromJson((json['color'] as num).toInt()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$WalletCategoryToJson(WalletCategory instance) =>
|
Map<String, dynamic> _$WalletCategoryToJson(WalletCategory instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|
44
lib/api/debt_entry.dart
Normal file
44
lib/api/debt_entry.dart
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:prasule/api/debt_person.dart';
|
||||||
|
part 'debt_entry.g.dart';
|
||||||
|
|
||||||
|
/// Single transaction
|
||||||
|
///
|
||||||
|
/// The debt will be split between people who are not in [whoPayed]
|
||||||
|
@JsonSerializable()
|
||||||
|
class DebtEntry {
|
||||||
|
/// Single transaction
|
||||||
|
///
|
||||||
|
/// The debt will be split between people who are not in [whoPayed]
|
||||||
|
DebtEntry({
|
||||||
|
required this.id,
|
||||||
|
required this.amount,
|
||||||
|
required this.name,
|
||||||
|
required this.whoPayed,
|
||||||
|
}) : assert(whoPayed.isNotEmpty, "There has to be at least one payer");
|
||||||
|
|
||||||
|
/// Generates a class instance from a Map
|
||||||
|
factory DebtEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DebtEntryFromJson(json);
|
||||||
|
|
||||||
|
/// Converts the data in this instance into a Map
|
||||||
|
Map<String, dynamic> toJson() => _$DebtEntryToJson(this);
|
||||||
|
|
||||||
|
/// Unique identifier
|
||||||
|
@JsonKey(required: true, disallowNullValue: true)
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// The payed amount
|
||||||
|
@JsonKey(defaultValue: 0)
|
||||||
|
int amount;
|
||||||
|
|
||||||
|
/// User-friendly identifier for the transaction
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
|
String name;
|
||||||
|
|
||||||
|
/// List of people who payed
|
||||||
|
@JsonKey(defaultValue: _defaultDebtPayers)
|
||||||
|
final List<DebtPerson> whoPayed;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DebtPerson> _defaultDebtPayers() => [DebtPerson.unknownPerson()];
|
31
lib/api/debt_entry.g.dart
Normal file
31
lib/api/debt_entry.g.dart
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'debt_entry.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
DebtEntry _$DebtEntryFromJson(Map<String, dynamic> json) {
|
||||||
|
$checkKeys(
|
||||||
|
json,
|
||||||
|
requiredKeys: const ['id'],
|
||||||
|
disallowNullValues: const ['id'],
|
||||||
|
);
|
||||||
|
return DebtEntry(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
amount: (json['amount'] as num?)?.toInt() ?? 0,
|
||||||
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
|
whoPayed: (json['whoPayed'] as List<dynamic>?)
|
||||||
|
?.map((e) => DebtPerson.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
_defaultDebtPayers(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DebtEntryToJson(DebtEntry instance) => <String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'amount': instance.amount,
|
||||||
|
'name': instance.name,
|
||||||
|
'whoPayed': instance.whoPayed,
|
||||||
|
};
|
33
lib/api/debt_person.dart
Normal file
33
lib/api/debt_person.dart
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
part 'debt_person.g.dart';
|
||||||
|
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
/// Represents a single person in a debt scenario
|
||||||
|
class DebtPerson {
|
||||||
|
/// Represents a single person in a debt scenario
|
||||||
|
DebtPerson({required this.id, required this.name, this.bankAccount});
|
||||||
|
|
||||||
|
/// Default [DebtPerson] instance for json_serializable
|
||||||
|
factory DebtPerson.unknownPerson() => DebtPerson(id: -1, name: "Unknown");
|
||||||
|
|
||||||
|
/// Generates a class instance from a Map
|
||||||
|
factory DebtPerson.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DebtPersonFromJson(json);
|
||||||
|
|
||||||
|
/// Converts the data in this instance into a Map
|
||||||
|
Map<String, dynamic> toJson() => _$DebtPersonToJson(this);
|
||||||
|
|
||||||
|
/// Unique identifier
|
||||||
|
@JsonKey(required: true, disallowNullValue: true)
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// Identifier that the user will see
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
|
String name;
|
||||||
|
|
||||||
|
/// Person's bank account
|
||||||
|
///
|
||||||
|
/// Used to generate a QR code payment
|
||||||
|
String? bankAccount;
|
||||||
|
}
|
27
lib/api/debt_person.g.dart
Normal file
27
lib/api/debt_person.g.dart
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'debt_person.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
DebtPerson _$DebtPersonFromJson(Map<String, dynamic> json) {
|
||||||
|
$checkKeys(
|
||||||
|
json,
|
||||||
|
requiredKeys: const ['id'],
|
||||||
|
disallowNullValues: const ['id'],
|
||||||
|
);
|
||||||
|
return DebtPerson(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
|
bankAccount: json['bankAccount'] as String?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DebtPersonToJson(DebtPerson instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'bankAccount': instance.bankAccount,
|
||||||
|
};
|
64
lib/api/debt_scenario.dart
Normal file
64
lib/api/debt_scenario.dart
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:prasule/api/debt_entry.dart';
|
||||||
|
import 'package:prasule/api/debt_person.dart';
|
||||||
|
part 'debt_scenario.g.dart';
|
||||||
|
|
||||||
|
/// A folder for different entries and people
|
||||||
|
@JsonSerializable()
|
||||||
|
class DebtScenario {
|
||||||
|
/// A folder for different entries and people
|
||||||
|
DebtScenario({
|
||||||
|
required this.id,
|
||||||
|
required this.name,
|
||||||
|
required this.isArchived,
|
||||||
|
required this.people,
|
||||||
|
this.entries = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Generates a class instance from a Map
|
||||||
|
factory DebtScenario.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$DebtScenarioFromJson(json);
|
||||||
|
|
||||||
|
/// Converts the data in this instance into a Map
|
||||||
|
Map<String, dynamic> toJson() => _$DebtScenarioToJson(this);
|
||||||
|
|
||||||
|
/// Unique identifier
|
||||||
|
@JsonKey(disallowNullValue: true)
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
/// User-friendly identifier
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
|
String name;
|
||||||
|
|
||||||
|
/// Whether this scenario should be shown under archived ones
|
||||||
|
@JsonKey(defaultValue: false)
|
||||||
|
bool isArchived;
|
||||||
|
|
||||||
|
/// All entries
|
||||||
|
@JsonKey(defaultValue: [])
|
||||||
|
final List<DebtEntry> entries;
|
||||||
|
|
||||||
|
/// All people
|
||||||
|
@JsonKey(defaultValue: _defaultPeopleList)
|
||||||
|
final List<DebtPerson> people;
|
||||||
|
|
||||||
|
/// Getter for the next unused unique number ID for a [DebtPerson]
|
||||||
|
int get nextPersonId {
|
||||||
|
var id = 1;
|
||||||
|
while (people.where((element) => element.id == id).isNotEmpty) {
|
||||||
|
id++; // create unique ID
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Getter for the next unused unique number ID for a [DebtEntry]
|
||||||
|
int get nextEntryId {
|
||||||
|
var id = 1;
|
||||||
|
while (entries.where((element) => element.id == id).isNotEmpty) {
|
||||||
|
id++; // create unique ID
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<DebtPerson> _defaultPeopleList() => [DebtPerson.unknownPerson()];
|
36
lib/api/debt_scenario.g.dart
Normal file
36
lib/api/debt_scenario.g.dart
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'debt_scenario.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
DebtScenario _$DebtScenarioFromJson(Map<String, dynamic> json) {
|
||||||
|
$checkKeys(
|
||||||
|
json,
|
||||||
|
disallowNullValues: const ['id'],
|
||||||
|
);
|
||||||
|
return DebtScenario(
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
|
isArchived: json['isArchived'] as bool? ?? false,
|
||||||
|
entries: (json['entries'] as List<dynamic>?)
|
||||||
|
?.map((e) => DebtEntry.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
|
people: (json['people'] as List<dynamic>?)
|
||||||
|
?.map((e) => DebtPerson.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
_defaultPeopleList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> _$DebtScenarioToJson(DebtScenario instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'isArchived': instance.isArchived,
|
||||||
|
'entries': instance.entries,
|
||||||
|
'people': instance.people,
|
||||||
|
};
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
part 'entry_data.g.dart';
|
part 'entry_data.g.dart';
|
||||||
|
|
||||||
|
@ -7,19 +11,25 @@ class EntryData {
|
||||||
/// Contains raw data
|
/// Contains raw data
|
||||||
EntryData({required this.name, required this.amount, this.description = ""});
|
EntryData({required this.name, required this.amount, this.description = ""});
|
||||||
|
|
||||||
/// Connects the generated fromJson method
|
/// Generates a class instance from a Map
|
||||||
factory EntryData.fromJson(Map<String, dynamic> json) =>
|
factory EntryData.fromJson(Map<String, dynamic> json) =>
|
||||||
_$EntryDataFromJson(json);
|
_$EntryDataFromJson(json);
|
||||||
|
|
||||||
|
/// [EntryData] instance used as a default value for json_serializable
|
||||||
|
factory EntryData.unknown() => EntryData(name: "Unknown", amount: 0);
|
||||||
|
|
||||||
/// Name of entry
|
/// Name of entry
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
String name;
|
String name;
|
||||||
|
|
||||||
/// Optional description, default is empty
|
/// Optional description, default is empty
|
||||||
|
@JsonKey(defaultValue: "")
|
||||||
String description;
|
String description;
|
||||||
|
|
||||||
/// Amount for entry
|
/// Amount for entry
|
||||||
|
@JsonKey(defaultValue: 0)
|
||||||
double amount;
|
double amount;
|
||||||
|
|
||||||
/// Connects the generated toJson method
|
/// Converts the data in this instance into a Map
|
||||||
Map<String, dynamic> toJson() => _$EntryDataToJson(this);
|
Map<String, dynamic> toJson() => _$EntryDataToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,9 +7,9 @@ part of 'entry_data.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
EntryData _$EntryDataFromJson(Map<String, dynamic> json) => EntryData(
|
EntryData _$EntryDataFromJson(Map<String, dynamic> json) => EntryData(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
amount: (json['amount'] as num).toDouble(),
|
amount: (json['amount'] as num?)?.toDouble() ?? 0,
|
||||||
description: json['description'] as String? ?? "",
|
description: json['description'] as String? ?? '',
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$EntryDataToJson(EntryData instance) => <String, dynamic>{
|
Map<String, dynamic> _$EntryDataToJson(EntryData instance) => <String, dynamic>{
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/entry_data.dart';
|
import 'package:prasule/api/entry_data.dart';
|
||||||
|
@ -20,21 +24,24 @@ class RecurringWalletEntry extends WalletSingleEntry {
|
||||||
this.repeatAfter = 1,
|
this.repeatAfter = 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Connects the generated fromJson method
|
/// Generates a class instance from a Map
|
||||||
factory RecurringWalletEntry.fromJson(Map<String, dynamic> json) =>
|
factory RecurringWalletEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
_$RecurringWalletEntryFromJson(json);
|
_$RecurringWalletEntryFromJson(json);
|
||||||
|
|
||||||
/// Connects the generated toJson method
|
/// Converts the data in this instance into a Map
|
||||||
@override
|
@override
|
||||||
Map<String, dynamic> toJson() => _$RecurringWalletEntryToJson(this);
|
Map<String, dynamic> toJson() => _$RecurringWalletEntryToJson(this);
|
||||||
|
|
||||||
/// Last date the recurring entry was added into the single entry list
|
/// Last date the recurring entry was added into the single entry list
|
||||||
|
@JsonKey(defaultValue: DateTime.now)
|
||||||
DateTime lastRunDate;
|
DateTime lastRunDate;
|
||||||
|
|
||||||
/// After how many {recurType} should the entry recur
|
/// After how many {recurType} should the entry recur
|
||||||
|
@JsonKey(defaultValue: 1)
|
||||||
int repeatAfter;
|
int repeatAfter;
|
||||||
|
|
||||||
/// What type of recurrence should happen
|
/// What type of recurrence should happen
|
||||||
|
@JsonKey(defaultValue: RecurType.month)
|
||||||
RecurType recurType;
|
RecurType recurType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,19 +6,33 @@ part of 'recurring_entry.dart';
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
RecurringWalletEntry _$RecurringWalletEntryFromJson(
|
RecurringWalletEntry _$RecurringWalletEntryFromJson(Map<String, dynamic> json) {
|
||||||
Map<String, dynamic> json) =>
|
$checkKeys(
|
||||||
RecurringWalletEntry(
|
json,
|
||||||
data: EntryData.fromJson(json['data'] as Map<String, dynamic>),
|
requiredKeys: const ['id'],
|
||||||
type: $enumDecode(_$EntryTypeEnumMap, json['type']),
|
disallowNullValues: const ['id'],
|
||||||
date: DateTime.parse(json['date'] as String),
|
);
|
||||||
category:
|
return RecurringWalletEntry(
|
||||||
WalletCategory.fromJson(json['category'] as Map<String, dynamic>),
|
data: json['data'] == null
|
||||||
id: json['id'] as int,
|
? EntryData.unknown()
|
||||||
lastRunDate: DateTime.parse(json['lastRunDate'] as String),
|
: EntryData.fromJson(json['data'] as Map<String, dynamic>),
|
||||||
repeatAfter: json['repeatAfter'] as int,
|
type: $enumDecodeNullable(_$EntryTypeEnumMap, json['type']) ??
|
||||||
recurType: $enumDecode(_$RecurTypeEnumMap, json['recurType']),
|
EntryType.expense,
|
||||||
);
|
date: json['date'] == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.parse(json['date'] as String),
|
||||||
|
category: json['category'] == null
|
||||||
|
? WalletCategory.unknown()
|
||||||
|
: WalletCategory.fromJson(json['category'] as Map<String, dynamic>),
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
lastRunDate: json['lastRunDate'] == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.parse(json['lastRunDate'] as String),
|
||||||
|
recurType: $enumDecodeNullable(_$RecurTypeEnumMap, json['recurType']) ??
|
||||||
|
RecurType.month,
|
||||||
|
repeatAfter: (json['repeatAfter'] as num?)?.toInt() ?? 1,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$RecurringWalletEntryToJson(
|
Map<String, dynamic> _$RecurringWalletEntryToJson(
|
||||||
RecurringWalletEntry instance) =>
|
RecurringWalletEntry instance) =>
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:currency_picker/currency_picker.dart';
|
import 'package:currency_picker/currency_picker.dart';
|
||||||
|
@ -5,6 +9,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
|
import 'package:prasule/api/debt_scenario.dart';
|
||||||
import 'package:prasule/api/entry_data.dart';
|
import 'package:prasule/api/entry_data.dart';
|
||||||
import 'package:prasule/api/recurring_entry.dart';
|
import 'package:prasule/api/recurring_entry.dart';
|
||||||
import 'package:prasule/api/wallet_entry.dart';
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
|
@ -27,34 +32,47 @@ class Wallet {
|
||||||
this.categories = const [],
|
this.categories = const [],
|
||||||
this.entries = const [],
|
this.entries = const [],
|
||||||
this.recurringEntries = const [],
|
this.recurringEntries = const [],
|
||||||
|
this.debts = const [],
|
||||||
this.starterBalance = 0,
|
this.starterBalance = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Connects the generated fromJson method
|
/// Generates a class instance from a Map
|
||||||
factory Wallet.fromJson(Map<String, dynamic> json) => _$WalletFromJson(json);
|
factory Wallet.fromJson(Map<String, dynamic> json) => _$WalletFromJson(json);
|
||||||
|
|
||||||
/// A list of all [RecurringWalletEntry]s
|
/// A list of all [RecurringWalletEntry]s
|
||||||
|
@JsonKey(defaultValue: [])
|
||||||
final List<RecurringWalletEntry> recurringEntries;
|
final List<RecurringWalletEntry> recurringEntries;
|
||||||
|
|
||||||
/// Name of the wallet
|
/// Name of the wallet
|
||||||
|
@JsonKey(defaultValue: "Unknown")
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
/// A list of available categories
|
/// A list of available categories
|
||||||
|
@JsonKey(defaultValue: _defaultWalletCategory)
|
||||||
final List<WalletCategory> categories;
|
final List<WalletCategory> categories;
|
||||||
|
|
||||||
/// List of saved entries
|
/// List of saved entries
|
||||||
|
@JsonKey(defaultValue: [])
|
||||||
final List<WalletSingleEntry> entries;
|
final List<WalletSingleEntry> entries;
|
||||||
|
|
||||||
|
/// List of user's [DebtScenario]s
|
||||||
|
@JsonKey(defaultValue: [])
|
||||||
|
final List<DebtScenario> debts;
|
||||||
|
|
||||||
/// The starting balance of the wallet
|
/// The starting balance of the wallet
|
||||||
///
|
///
|
||||||
/// Used to calculate current balance
|
/// Used to calculate current balance
|
||||||
|
@JsonKey(defaultValue: 0)
|
||||||
double starterBalance;
|
double starterBalance;
|
||||||
|
|
||||||
/// Selected currency
|
/// Selected currency
|
||||||
@JsonKey(fromJson: _currencyFromJson)
|
@JsonKey(
|
||||||
|
fromJson: _currencyFromJson,
|
||||||
|
defaultValue: _defaultCurrency,
|
||||||
|
)
|
||||||
final Currency currency;
|
final Currency currency;
|
||||||
|
|
||||||
/// Connects the generated toJson method
|
/// Converts the data in this instance into a Map
|
||||||
Map<String, dynamic> toJson() => _$WalletToJson(this);
|
Map<String, dynamic> toJson() => _$WalletToJson(this);
|
||||||
|
|
||||||
/// Getter for the next unused unique number ID in the wallet's **entry** list
|
/// Getter for the next unused unique number ID in the wallet's **entry** list
|
||||||
|
@ -76,6 +94,16 @@ class Wallet {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Getter for the next unused unique number ID in the wallet's **debts**
|
||||||
|
/// list
|
||||||
|
int get nextDebtId {
|
||||||
|
var id = 0;
|
||||||
|
while (debts.where((element) => element.id == id).isNotEmpty) {
|
||||||
|
id++; // create unique ID
|
||||||
|
}
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles adding recurring entries to the entry list
|
/// Handles adding recurring entries to the entry list
|
||||||
void recurEntries() {
|
void recurEntries() {
|
||||||
final n = DateTime.now();
|
final n = DateTime.now();
|
||||||
|
@ -147,7 +175,7 @@ class Wallet {
|
||||||
///
|
///
|
||||||
/// All [WalletSingleEntry]s will have their category reassigned
|
/// All [WalletSingleEntry]s will have their category reassigned
|
||||||
/// to the default *No category*
|
/// to the default *No category*
|
||||||
Future<void> removeCategory(WalletCategory category) async {
|
void removeCategory(WalletCategory category) {
|
||||||
// First remove the category from existing entries
|
// First remove the category from existing entries
|
||||||
for (final entryToChange
|
for (final entryToChange
|
||||||
in entries.where((element) => element.category.id == category.id)) {
|
in entries.where((element) => element.category.id == category.id)) {
|
||||||
|
@ -157,7 +185,7 @@ class Wallet {
|
||||||
// Remove the category
|
// Remove the category
|
||||||
categories.removeWhere((element) => element.id == category.id);
|
categories.removeWhere((element) => element.id == category.id);
|
||||||
// Save
|
// Save
|
||||||
await WalletManager.saveWallet(this);
|
WalletManager.saveWallet(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the current balance
|
/// Returns the current balance
|
||||||
|
@ -216,7 +244,7 @@ class Wallet {
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Creates test data used for debugging purposes
|
/// Creates test data used for debugging purposes
|
||||||
Future<void> createTestEntries() async {
|
void createTestEntries() {
|
||||||
entries.clear();
|
entries.clear();
|
||||||
recurringEntries.clear();
|
recurringEntries.clear();
|
||||||
final random = Random();
|
final random = Random();
|
||||||
|
@ -274,6 +302,23 @@ class Wallet {
|
||||||
);
|
);
|
||||||
|
|
||||||
// save and reload
|
// save and reload
|
||||||
await WalletManager.saveWallet(this);
|
WalletManager.saveWallet(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<WalletCategory> _defaultWalletCategory() => [WalletCategory.unknown()];
|
||||||
|
Currency _defaultCurrency() => Currency.from(
|
||||||
|
json: {
|
||||||
|
"code": "USD",
|
||||||
|
"name": "United States Dollar",
|
||||||
|
"symbol": r"$",
|
||||||
|
"flag": "USD",
|
||||||
|
"decimal_digits": 2,
|
||||||
|
"number": 840,
|
||||||
|
"name_plural": "US dollars",
|
||||||
|
"thousands_separator": ",",
|
||||||
|
"decimal_separator": ".",
|
||||||
|
"space_between_amount_and_symbol": false,
|
||||||
|
"symbol_on_left": true,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
|
@ -7,22 +7,28 @@ part of 'wallet.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
Wallet _$WalletFromJson(Map<String, dynamic> json) => Wallet(
|
Wallet _$WalletFromJson(Map<String, dynamic> json) => Wallet(
|
||||||
name: json['name'] as String,
|
name: json['name'] as String? ?? 'Unknown',
|
||||||
currency: _currencyFromJson(json['currency'] as Map<String, dynamic>),
|
currency: json['currency'] == null
|
||||||
|
? _defaultCurrency()
|
||||||
|
: _currencyFromJson(json['currency'] as Map<String, dynamic>),
|
||||||
categories: (json['categories'] as List<dynamic>?)
|
categories: (json['categories'] as List<dynamic>?)
|
||||||
?.map((e) => WalletCategory.fromJson(e as Map<String, dynamic>))
|
?.map((e) => WalletCategory.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
_defaultWalletCategory(),
|
||||||
entries: (json['entries'] as List<dynamic>?)
|
entries: (json['entries'] as List<dynamic>?)
|
||||||
?.map(
|
?.map(
|
||||||
(e) => WalletSingleEntry.fromJson(e as Map<String, dynamic>))
|
(e) => WalletSingleEntry.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
[],
|
||||||
recurringEntries: (json['recurringEntries'] as List<dynamic>?)
|
recurringEntries: (json['recurringEntries'] as List<dynamic>?)
|
||||||
?.map((e) =>
|
?.map((e) =>
|
||||||
RecurringWalletEntry.fromJson(e as Map<String, dynamic>))
|
RecurringWalletEntry.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
[],
|
||||||
|
debts: (json['debts'] as List<dynamic>?)
|
||||||
|
?.map((e) => DebtScenario.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
[],
|
||||||
starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0,
|
starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -31,6 +37,7 @@ Map<String, dynamic> _$WalletToJson(Wallet instance) => <String, dynamic>{
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'categories': instance.categories,
|
'categories': instance.categories,
|
||||||
'entries': instance.entries,
|
'entries': instance.entries,
|
||||||
|
'debts': instance.debts,
|
||||||
'starterBalance': instance.starterBalance,
|
'starterBalance': instance.starterBalance,
|
||||||
'currency': instance.currency,
|
'currency': instance.currency,
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:json_annotation/json_annotation.dart';
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/entry_data.dart';
|
import 'package:prasule/api/entry_data.dart';
|
||||||
|
@ -17,25 +21,30 @@ class WalletSingleEntry {
|
||||||
required this.id,
|
required this.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Connects the generated fromJson method
|
/// Generates a class instance from a Map
|
||||||
factory WalletSingleEntry.fromJson(Map<String, dynamic> json) =>
|
factory WalletSingleEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
_$WalletSingleEntryFromJson(json);
|
_$WalletSingleEntryFromJson(json);
|
||||||
|
|
||||||
/// Expense or income
|
/// Expense or income
|
||||||
|
@JsonKey(defaultValue: EntryType.expense)
|
||||||
EntryType type;
|
EntryType type;
|
||||||
|
|
||||||
/// Actual entry data
|
/// Actual entry data
|
||||||
|
@JsonKey(defaultValue: EntryData.unknown)
|
||||||
EntryData data;
|
EntryData data;
|
||||||
|
|
||||||
/// Date of entry creation
|
/// Date of entry creation
|
||||||
|
@JsonKey(defaultValue: DateTime.now)
|
||||||
DateTime date;
|
DateTime date;
|
||||||
|
|
||||||
/// Selected category
|
/// Selected category
|
||||||
|
@JsonKey(defaultValue: WalletCategory.unknown)
|
||||||
WalletCategory category;
|
WalletCategory category;
|
||||||
|
|
||||||
/// Unique entry ID
|
/// Unique entry ID
|
||||||
int id;
|
@JsonKey(required: true, disallowNullValue: true)
|
||||||
|
final int id;
|
||||||
|
|
||||||
/// Connects the generated toJson method
|
/// Converts the data in this instance into a Map
|
||||||
Map<String, dynamic> toJson() => _$WalletSingleEntryToJson(this);
|
Map<String, dynamic> toJson() => _$WalletSingleEntryToJson(this);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,15 +6,27 @@ part of 'wallet_entry.dart';
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
WalletSingleEntry _$WalletSingleEntryFromJson(Map<String, dynamic> json) =>
|
WalletSingleEntry _$WalletSingleEntryFromJson(Map<String, dynamic> json) {
|
||||||
WalletSingleEntry(
|
$checkKeys(
|
||||||
data: EntryData.fromJson(json['data'] as Map<String, dynamic>),
|
json,
|
||||||
type: $enumDecode(_$EntryTypeEnumMap, json['type']),
|
requiredKeys: const ['id'],
|
||||||
date: DateTime.parse(json['date'] as String),
|
disallowNullValues: const ['id'],
|
||||||
category:
|
);
|
||||||
WalletCategory.fromJson(json['category'] as Map<String, dynamic>),
|
return WalletSingleEntry(
|
||||||
id: json['id'] as int,
|
data: json['data'] == null
|
||||||
);
|
? EntryData.unknown()
|
||||||
|
: EntryData.fromJson(json['data'] as Map<String, dynamic>),
|
||||||
|
type: $enumDecodeNullable(_$EntryTypeEnumMap, json['type']) ??
|
||||||
|
EntryType.expense,
|
||||||
|
date: json['date'] == null
|
||||||
|
? DateTime.now()
|
||||||
|
: DateTime.parse(json['date'] as String),
|
||||||
|
category: json['category'] == null
|
||||||
|
? WalletCategory.unknown()
|
||||||
|
: WalletCategory.fromJson(json['category'] as Map<String, dynamic>),
|
||||||
|
id: (json['id'] as num).toInt(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Map<String, dynamic> _$WalletSingleEntryToJson(WalletSingleEntry instance) =>
|
Map<String, dynamic> _$WalletSingleEntryToJson(WalletSingleEntry instance) =>
|
||||||
<String, dynamic>{
|
<String, dynamic>{
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -11,18 +15,28 @@ import 'package:prasule/main.dart';
|
||||||
|
|
||||||
/// Used for [Wallet]-managing operations
|
/// Used for [Wallet]-managing operations
|
||||||
class WalletManager {
|
class WalletManager {
|
||||||
|
/// Currently selected wallet
|
||||||
|
static late Wallet selectedWallet;
|
||||||
|
|
||||||
|
/// Path to the directory with wallet files
|
||||||
|
///
|
||||||
|
/// Saved beforehand so we don't have to use async everywhere
|
||||||
|
static late String walletPath;
|
||||||
|
|
||||||
/// Returns a list of all [Wallet]s
|
/// Returns a list of all [Wallet]s
|
||||||
static Future<List<Wallet>> listWallets() async {
|
static List<Wallet> listWallets() {
|
||||||
final path =
|
final path = Directory(walletPath);
|
||||||
Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
|
|
||||||
if (!path.existsSync()) {
|
if (!path.existsSync()) {
|
||||||
path.createSync();
|
path.createSync();
|
||||||
}
|
}
|
||||||
final wallets = <Wallet>[];
|
final wallets = <Wallet>[];
|
||||||
for (final w
|
for (final w in path
|
||||||
in path.listSync().map((e) => e.path.split("/").last).toList()) {
|
.listSync()
|
||||||
|
.whereType<File>()
|
||||||
|
.map((e) => e.path.split("/").last)
|
||||||
|
.toList()) {
|
||||||
try {
|
try {
|
||||||
wallets.add(await loadWallet(w));
|
wallets.add(loadWallet(w));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.e(e);
|
logger.e(e);
|
||||||
// TODO: do something with unreadable wallets
|
// TODO: do something with unreadable wallets
|
||||||
|
@ -32,9 +46,8 @@ class WalletManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes all [Wallet]s
|
/// Deletes all [Wallet]s
|
||||||
static Future<void> deleteAllData() async {
|
static void deleteAllData() {
|
||||||
final path =
|
final path = Directory(walletPath);
|
||||||
Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
|
|
||||||
if (!path.existsSync()) {
|
if (!path.existsSync()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -124,10 +137,10 @@ class WalletManager {
|
||||||
d = File(filePath).readAsStringSync();
|
d = File(filePath).readAsStringSync();
|
||||||
}
|
}
|
||||||
final w = Wallet.fromJson(jsonDecode(d) as Map<String, dynamic>);
|
final w = Wallet.fromJson(jsonDecode(d) as Map<String, dynamic>);
|
||||||
if (await WalletManager.exists(w.name)) {
|
if (WalletManager.exists(w.name)) {
|
||||||
throw Exception("Wallet already exists!");
|
throw Exception("Wallet already exists!");
|
||||||
}
|
}
|
||||||
await WalletManager.saveWallet(
|
WalletManager.saveWallet(
|
||||||
w,
|
w,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -166,15 +179,14 @@ class WalletManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Loads and returns a single [Wallet] by name
|
/// Loads and returns a single [Wallet] by name
|
||||||
static Future<Wallet> loadWallet(String name) async {
|
static Wallet loadWallet(String name) {
|
||||||
final path =
|
final path = Directory(walletPath);
|
||||||
Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
|
|
||||||
final wallet = File("${path.path}/$name");
|
final wallet = File("${path.path}/$name");
|
||||||
if (!path.existsSync()) {
|
if (!path.existsSync()) {
|
||||||
path.createSync();
|
path.createSync();
|
||||||
}
|
}
|
||||||
if (!wallet.existsSync()) {
|
if (!wallet.existsSync()) {
|
||||||
return Future.error("Wallet does not exist");
|
throw Exception("Wallet does not exist");
|
||||||
}
|
}
|
||||||
return Wallet.fromJson(
|
return Wallet.fromJson(
|
||||||
jsonDecode(wallet.readAsStringSync()) as Map<String, dynamic>,
|
jsonDecode(wallet.readAsStringSync()) as Map<String, dynamic>,
|
||||||
|
@ -182,29 +194,26 @@ class WalletManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts [Wallet] to JSON and saves it to AppData
|
/// Converts [Wallet] to JSON and saves it to AppData
|
||||||
static Future<bool> saveWallet(Wallet w) async {
|
static void saveWallet(Wallet w) {
|
||||||
final path =
|
final path = Directory(walletPath);
|
||||||
Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
|
|
||||||
final wallet = File("${path.path}/${w.name}");
|
final wallet = File("${path.path}/${w.name}");
|
||||||
if (!path.existsSync()) {
|
if (!path.existsSync()) {
|
||||||
path.createSync();
|
path.createSync();
|
||||||
}
|
}
|
||||||
// if (!wallet.existsSync()) return false;
|
// if (!wallet.existsSync()) return false;
|
||||||
wallet.writeAsStringSync(jsonEncode(w.toJson()));
|
wallet.writeAsStringSync(jsonEncode(w.toJson()));
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Deletes the corresponding [Wallet] file
|
/// Deletes the corresponding [Wallet] file
|
||||||
static Future<void> deleteWallet(Wallet w) async {
|
static void deleteWallet(Wallet w) {
|
||||||
final path =
|
final path = Directory(walletPath);
|
||||||
Directory("${(await getApplicationDocumentsDirectory()).path}/wallets");
|
|
||||||
File("${path.path}/${w.name}").deleteSync();
|
File("${path.path}/${w.name}").deleteSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Checks if the wallet exists
|
/// Checks if the wallet exists
|
||||||
static Future<bool> exists(String name) async {
|
static bool exists(String name) {
|
||||||
return File(
|
return File(
|
||||||
"${(await getApplicationDocumentsDirectory()).path}/wallets/$name",
|
"$walletPath/$name",
|
||||||
).existsSync();
|
).existsSync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -122,5 +122,14 @@
|
||||||
"incomePerYearCategory":"Příjmy podle kategorie za rok {year}",
|
"incomePerYearCategory":"Příjmy podle kategorie za rok {year}",
|
||||||
"incomePerMonthCategory":"Příjmy podle kategorie za měsíc {monthYear}",
|
"incomePerMonthCategory":"Příjmy podle kategorie za měsíc {monthYear}",
|
||||||
"selectYear":"Zvolte rok",
|
"selectYear":"Zvolte rok",
|
||||||
"selectMonth":"Zvolte měsíc a rok"
|
"selectMonth":"Zvolte měsíc a rok",
|
||||||
|
"debts":"Dlužníček",
|
||||||
|
"debtNamePlaceholder":"Dluhy přátel",
|
||||||
|
"people":"Lidé",
|
||||||
|
"addSomePeople":"Přidej lidi pomocí tlačítka níže",
|
||||||
|
"bankAccount":"Číslo bankovního účtu",
|
||||||
|
"noDebtScenarios":"Žádné seznamy dlužníků :(",
|
||||||
|
"noDebtScenariosSub":"Nový můžete vytvořit pomocí plovoucího tlačítka.",
|
||||||
|
"noPersonError":"Musíte vložit alespoň jednoho člověka!",
|
||||||
|
"unnamed":"Bez jména"
|
||||||
}
|
}
|
3
lib/l10n/app_cs.arb.license
Normal file
3
lib/l10n/app_cs.arb.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
|
@ -313,5 +313,14 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"selectYear":"Select a year",
|
"selectYear":"Select a year",
|
||||||
"selectMonth":"Select a month and year"
|
"selectMonth":"Select a month and year",
|
||||||
|
"debts":"Debts",
|
||||||
|
"debtNamePlaceholder":"Friends' debts",
|
||||||
|
"people":"People",
|
||||||
|
"addSomePeople":"Add people using the button below",
|
||||||
|
"bankAccount":"Bank account number",
|
||||||
|
"noDebtScenarios":"No debt scenarios :(",
|
||||||
|
"noDebtScenariosSub":"Create one using the floating action button.",
|
||||||
|
"noPersonError":"You need to add at least one person!",
|
||||||
|
"unnamed":"Unnamed"
|
||||||
}
|
}
|
3
lib/l10n/app_en.arb.license
Normal file
3
lib/l10n/app_en.arb.license
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
|
||||||
|
SPDX-License-Identifier: CC0-1.0
|
1
lib/l10n/app_sk.arb
Normal file
1
lib/l10n/app_sk.arb
Normal file
|
@ -0,0 +1 @@
|
||||||
|
{}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
@ -7,7 +11,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:logger/logger.dart';
|
import 'package:logger/logger.dart';
|
||||||
import 'package:prasule/util/color_schemes.g.dart';
|
import 'package:prasule/util/color_schemes.g.dart';
|
||||||
import 'package:prasule/views/home.dart';
|
import 'package:prasule/views/initialization_screen.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
var _materialYou = false;
|
var _materialYou = false;
|
||||||
|
@ -20,6 +24,7 @@ void main() async {
|
||||||
}
|
}
|
||||||
|
|
||||||
_materialYou = s.getBool("useMaterialYou") ?? true;
|
_materialYou = s.getBool("useMaterialYou") ?? true;
|
||||||
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,6 +44,7 @@ class MyApp extends StatelessWidget {
|
||||||
/// Override locale, used for testing
|
/// Override locale, used for testing
|
||||||
final Locale? locale;
|
final Locale? locale;
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return (Platform.isAndroid)
|
return (Platform.isAndroid)
|
||||||
|
@ -66,7 +72,7 @@ class MyApp extends StatelessWidget {
|
||||||
colorScheme:
|
colorScheme:
|
||||||
_materialYou ? dark ?? darkColorScheme : darkColorScheme,
|
_materialYou ? dark ?? darkColorScheme : darkColorScheme,
|
||||||
),
|
),
|
||||||
home: const HomeView(),
|
home: const InitializationScreen(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -85,7 +91,7 @@ class MyApp extends StatelessWidget {
|
||||||
...GlobalCupertinoLocalizations.delegates,
|
...GlobalCupertinoLocalizations.delegates,
|
||||||
],
|
],
|
||||||
title: 'Prašule',
|
title: 'Prašule',
|
||||||
home: HomeView(),
|
home: InitializationScreen(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
@ -6,26 +10,26 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:prasule/pw/platformwidget.dart';
|
import 'package:prasule/pw/platformwidget.dart';
|
||||||
|
|
||||||
/// A [PlatformWidget] implementation of a text field
|
/// A [PlatformWidget] implementation of a text field
|
||||||
class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
class PlatformField extends PlatformWidget<TextFormField, CupertinoTextField> {
|
||||||
const PlatformField({
|
const PlatformField(
|
||||||
super.key,
|
{super.key,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.enabled,
|
this.enabled,
|
||||||
this.labelText,
|
this.labelText,
|
||||||
this.obscureText = false,
|
this.obscureText = false,
|
||||||
this.autocorrect = false,
|
this.autocorrect = false,
|
||||||
this.keyboardType,
|
this.keyboardType,
|
||||||
this.inputFormatters = const [],
|
this.inputFormatters = const [],
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.autofillHints,
|
this.autofillHints,
|
||||||
this.textStyle,
|
this.textStyle,
|
||||||
this.textAlign = TextAlign.start,
|
this.textAlign = TextAlign.start,
|
||||||
this.maxLines = 1,
|
this.maxLines = 1,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.inputBorder = const OutlineInputBorder(),
|
this.inputBorder = const OutlineInputBorder(),
|
||||||
this.suffix,
|
this.suffix,
|
||||||
this.prefix,
|
this.prefix,
|
||||||
});
|
this.validator});
|
||||||
final TextEditingController? controller;
|
final TextEditingController? controller;
|
||||||
final bool? enabled;
|
final bool? enabled;
|
||||||
final bool obscureText;
|
final bool obscureText;
|
||||||
|
@ -42,9 +46,10 @@ class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
final Widget? suffix;
|
final Widget? suffix;
|
||||||
final Widget? prefix;
|
final Widget? prefix;
|
||||||
|
final String? Function(String?)? validator;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextField createAndroidWidget(BuildContext context) => TextField(
|
TextFormField createAndroidWidget(BuildContext context) => TextFormField(
|
||||||
textAlign: textAlign,
|
textAlign: textAlign,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
|
@ -63,6 +68,7 @@ class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
||||||
onChanged: onChanged,
|
onChanged: onChanged,
|
||||||
autofillHints: autofillHints,
|
autofillHints: autofillHints,
|
||||||
maxLines: maxLines,
|
maxLines: maxLines,
|
||||||
|
validator: validator,
|
||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
// ignore_for_file: public_member_api_docs
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,12 +1,17 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:prasule/pw/platformroute.dart';
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
import 'package:prasule/views/graph_view.dart';
|
import 'package:prasule/views/debts/debt_view.dart';
|
||||||
|
import 'package:prasule/views/graphs/graph_view.dart';
|
||||||
import 'package:prasule/views/home.dart';
|
import 'package:prasule/views/home.dart';
|
||||||
import 'package:prasule/views/recurring_view.dart';
|
import 'package:prasule/views/recurring/recurring_view.dart';
|
||||||
|
|
||||||
/// Makes the drawer because I won't enter the same code in every view
|
/// Makes the drawer because I won't enter the same code in every view
|
||||||
Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
Drawer makeDrawer(BuildContext context, Pages page) => Drawer(
|
||||||
child: ListView(
|
child: ListView(
|
||||||
children: [
|
children: [
|
||||||
const DrawerHeader(child: Text("Prašule")),
|
const DrawerHeader(child: Text("Prašule")),
|
||||||
|
@ -15,9 +20,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).home,
|
AppLocalizations.of(context).home,
|
||||||
),
|
),
|
||||||
selected: page == 1,
|
selected: page == Pages.home,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (page == 1) {
|
if (page == Pages.home) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -30,9 +35,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).graphs,
|
AppLocalizations.of(context).graphs,
|
||||||
),
|
),
|
||||||
selected: page == 2,
|
selected: page == Pages.graphs,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (page == 2) {
|
if (page == Pages.graphs) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -45,9 +50,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context).recurringPayments,
|
AppLocalizations.of(context).recurringPayments,
|
||||||
),
|
),
|
||||||
selected: page == 3,
|
selected: page == Pages.recurringEntries,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (page == 3) {
|
if (page == Pages.recurringEntries) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -56,6 +61,37 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.people),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context).debts,
|
||||||
|
),
|
||||||
|
selected: page == Pages.debts,
|
||||||
|
onTap: () {
|
||||||
|
if (page == Pages.debts) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
platformRoute((p0) => const DebtView()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// All the pages that drawer can navigate to
|
||||||
|
enum Pages {
|
||||||
|
/// [HomeView]
|
||||||
|
home,
|
||||||
|
|
||||||
|
/// [GraphView]
|
||||||
|
graphs,
|
||||||
|
|
||||||
|
/// [RecurringEntriesView]
|
||||||
|
recurringEntries,
|
||||||
|
|
||||||
|
/// [DebtView]
|
||||||
|
debts
|
||||||
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
/// Extension to get last day of the month
|
/// Extension to get last day of the month
|
||||||
extension LastDay on DateTime {
|
extension LastDay on DateTime {
|
||||||
/// Returns the last day of the month as [int]
|
/// Returns the last day of the month as [int]
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:currency_picker/currency_picker.dart';
|
import 'package:currency_picker/currency_picker.dart';
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:grouped_list/grouped_list.dart';
|
import 'package:grouped_list/grouped_list.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
/// Used to add [calculateTextColor] to the [Color] class
|
/// Used to add [calculateTextColor] to the [Color] class
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
141
lib/views/debts/debt_view.dart
Normal file
141
lib/views/debts/debt_view.dart
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:prasule/api/debt_scenario.dart';
|
||||||
|
import 'package:prasule/api/wallet.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/main.dart';
|
||||||
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
import 'package:prasule/util/drawer.dart';
|
||||||
|
import 'package:prasule/views/debts/setup_debt_scenario.dart';
|
||||||
|
import 'package:prasule/views/setup.dart';
|
||||||
|
|
||||||
|
/// Shows the selected [DebtScenario]
|
||||||
|
class DebtView extends StatefulWidget {
|
||||||
|
/// Shows the selected [DebtScenario]
|
||||||
|
const DebtView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<DebtView> createState() => _DebtViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _DebtViewState extends State<DebtView> {
|
||||||
|
List<Wallet> wallets = [];
|
||||||
|
void loadWallet() {
|
||||||
|
wallets = WalletManager.listWallets();
|
||||||
|
if (wallets.isEmpty && mounted) {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacement(platformRoute((c) => const SetupView())),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: WalletManager.selectedWallet.debts.isEmpty
|
||||||
|
? 1
|
||||||
|
: WalletManager.selectedWallet.debts.length,
|
||||||
|
child: Scaffold(
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(platformRoute((c) => const SetupDebtScenario()))
|
||||||
|
.then((v) {
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
appBar: AppBar(
|
||||||
|
title: DropdownButton<int>(
|
||||||
|
value: wallets.indexOf(
|
||||||
|
wallets
|
||||||
|
.where((w) => w.name == WalletManager.selectedWallet.name)
|
||||||
|
.first,
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
...wallets.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: wallets.indexOf(
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
child: Text(e.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: -1,
|
||||||
|
child: Text(AppLocalizations.of(context).newWallet),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) async {
|
||||||
|
if (v == null || v == -1) {
|
||||||
|
await Navigator.of(context).push(
|
||||||
|
platformRoute(
|
||||||
|
(c) => const SetupView(
|
||||||
|
newWallet: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
wallets = WalletManager.listWallets();
|
||||||
|
logger.i(wallets.length);
|
||||||
|
WalletManager.selectedWallet = wallets.last;
|
||||||
|
setState(() {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WalletManager.selectedWallet = wallets[v];
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
bottom: TabBar(
|
||||||
|
tabs: (WalletManager.selectedWallet.debts.isEmpty)
|
||||||
|
? [Text(AppLocalizations.of(context).welcome)]
|
||||||
|
: List.generate(
|
||||||
|
WalletManager.selectedWallet.debts.length,
|
||||||
|
(index) => Tab(
|
||||||
|
text: WalletManager.selectedWallet.debts[index].name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
drawer: makeDrawer(context, Pages.debts),
|
||||||
|
body: TabBarView(
|
||||||
|
children: (WalletManager.selectedWallet.debts.isEmpty)
|
||||||
|
? [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).noDebtScenarios,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(AppLocalizations.of(context).noDebtScenariosSub),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: List.generate(
|
||||||
|
WalletManager.selectedWallet.debts.length,
|
||||||
|
(c) => const Placeholder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
254
lib/views/debts/setup_debt_scenario.dart
Normal file
254
lib/views/debts/setup_debt_scenario.dart
Normal file
|
@ -0,0 +1,254 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:prasule/api/debt_person.dart';
|
||||||
|
import 'package:prasule/api/debt_scenario.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/pw/platformfield.dart';
|
||||||
|
import 'package:prasule/util/show_message.dart';
|
||||||
|
|
||||||
|
/// Used to create and/or edit [DebtScenario]s
|
||||||
|
class SetupDebtScenario extends StatefulWidget {
|
||||||
|
/// Used to create and/or edit [DebtScenario]s
|
||||||
|
const SetupDebtScenario({super.key, this.toEdit});
|
||||||
|
|
||||||
|
/// If not null, loads this [DebtScenario]'s data for editing
|
||||||
|
final DebtScenario? toEdit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SetupDebtScenario> createState() => _SetupDebtScenarioState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SetupDebtScenarioState extends State<SetupDebtScenario> {
|
||||||
|
late DebtScenario _scenario;
|
||||||
|
final _nameController = TextEditingController();
|
||||||
|
final GlobalKey<FormState> _formKey = GlobalKey();
|
||||||
|
|
||||||
|
/// Stores data for each [ExpansionPanel]
|
||||||
|
final List<bool> _isOpen = [];
|
||||||
|
final List<TextEditingController> _panelControllers = [];
|
||||||
|
bool _isLoading = true;
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
if (!_isLoading) return;
|
||||||
|
_scenario = widget.toEdit ??
|
||||||
|
DebtScenario(
|
||||||
|
id: WalletManager.selectedWallet.nextDebtId,
|
||||||
|
name: AppLocalizations.of(context).debtNamePlaceholder,
|
||||||
|
isArchived: false,
|
||||||
|
people: [],
|
||||||
|
entries: [],
|
||||||
|
);
|
||||||
|
|
||||||
|
// Load stuff from the scenario
|
||||||
|
_nameController.text = _scenario.name;
|
||||||
|
_isOpen.addAll(List.filled(_scenario.people.length, false));
|
||||||
|
_panelControllers.addAll(
|
||||||
|
List.filled(_scenario.people.length * 2, TextEditingController()),
|
||||||
|
);
|
||||||
|
|
||||||
|
_isLoading = false;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.95,
|
||||||
|
child: Form(
|
||||||
|
key: _formKey,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: PlatformField(
|
||||||
|
labelText: AppLocalizations.of(context).name,
|
||||||
|
controller: _nameController,
|
||||||
|
validator: (input) {
|
||||||
|
if (input == null || input.isEmpty) {
|
||||||
|
return AppLocalizations.of(context).errorEmptyName;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
Text(AppLocalizations.of(context).people),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
LimitedBox(
|
||||||
|
maxHeight: MediaQuery.of(context).size.height * 0.6,
|
||||||
|
child: Container(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: (_scenario.people.isEmpty)
|
||||||
|
? Text(AppLocalizations.of(context).addSomePeople)
|
||||||
|
: ExpansionPanelList(
|
||||||
|
expansionCallback: (panelIndex, isExpanded) {
|
||||||
|
_isOpen[panelIndex] = isExpanded;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
children: List<ExpansionPanel>.generate(
|
||||||
|
_scenario.people.length,
|
||||||
|
(index) => ExpansionPanel(
|
||||||
|
isExpanded: _isOpen[index],
|
||||||
|
headerBuilder: (context, isOpen) => Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context)
|
||||||
|
.size
|
||||||
|
.width *
|
||||||
|
0.45,
|
||||||
|
child: PlatformField(
|
||||||
|
labelText:
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.name,
|
||||||
|
controller:
|
||||||
|
_panelControllers[index],
|
||||||
|
onChanged: (input) {
|
||||||
|
_scenario.people[index].name =
|
||||||
|
input;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
validator: (input) {
|
||||||
|
if (input == null ||
|
||||||
|
input.isEmpty) {
|
||||||
|
return AppLocalizations.of(
|
||||||
|
context,
|
||||||
|
).errorEmptyName;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_scenario.people.removeAt(index);
|
||||||
|
_panelControllers.removeRange(
|
||||||
|
index,
|
||||||
|
index + 2,
|
||||||
|
);
|
||||||
|
_isOpen.removeAt(index);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete_forever,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
body: Column(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context)
|
||||||
|
.size
|
||||||
|
.width *
|
||||||
|
0.8,
|
||||||
|
child: PlatformField(
|
||||||
|
labelText:
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.bankAccount,
|
||||||
|
controller: _panelControllers[
|
||||||
|
index + 1],
|
||||||
|
onChanged: (input) {
|
||||||
|
_scenario.people[index]
|
||||||
|
.bankAccount =
|
||||||
|
(input.isEmpty)
|
||||||
|
? null
|
||||||
|
: input;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
_scenario.people.add(
|
||||||
|
DebtPerson(id: _scenario.nextPersonId, name: ""),
|
||||||
|
);
|
||||||
|
_isOpen.add(false);
|
||||||
|
_panelControllers.addAll(
|
||||||
|
[TextEditingController(), TextEditingController()],
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
if (!_formKey.currentState!.validate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_scenario.people.isEmpty) {
|
||||||
|
showMessage(
|
||||||
|
AppLocalizations.of(context).noPersonError,
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_scenario.name = _nameController.text;
|
||||||
|
if (widget.toEdit != null) {
|
||||||
|
// If editing, only replace
|
||||||
|
WalletManager.selectedWallet.debts[WalletManager
|
||||||
|
.selectedWallet.debts
|
||||||
|
.indexWhere((d) => d.id == widget.toEdit!.id)] =
|
||||||
|
_scenario;
|
||||||
|
} else {
|
||||||
|
// else add new
|
||||||
|
WalletManager.selectedWallet.debts.add(_scenario);
|
||||||
|
}
|
||||||
|
WalletManager.saveWallet(WalletManager.selectedWallet);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context).save),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -236,9 +240,8 @@ class _CreateSingleEntryViewState extends State<CreateSingleEntryView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
widget.w.entries.add(newEntry);
|
widget.w.entries.add(newEntry);
|
||||||
WalletManager.saveWallet(widget.w).then(
|
WalletManager.saveWallet(widget.w); // TODO loading circle?
|
||||||
(value) => Navigator.of(context).pop(widget.w),
|
Navigator.of(context).pop(widget.w);
|
||||||
); // TODO loading circle?
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
|
@ -1,719 +0,0 @@
|
||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
|
||||||
import 'package:intl/intl.dart';
|
|
||||||
import 'package:prasule/api/category.dart';
|
|
||||||
import 'package:prasule/api/wallet.dart';
|
|
||||||
import 'package:prasule/api/wallet_manager.dart';
|
|
||||||
import 'package:prasule/main.dart';
|
|
||||||
import 'package:prasule/pw/platformroute.dart';
|
|
||||||
import 'package:prasule/util/drawer.dart';
|
|
||||||
import 'package:prasule/util/graphs.dart';
|
|
||||||
import 'package:prasule/util/utils.dart';
|
|
||||||
import 'package:prasule/views/settings/settings.dart';
|
|
||||||
import 'package:prasule/views/setup.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:wheel_chooser/wheel_chooser.dart';
|
|
||||||
|
|
||||||
/// Shows data from a [Wallet] in graphs
|
|
||||||
class GraphView extends StatefulWidget {
|
|
||||||
/// Shows data from a [Wallet] in graphs
|
|
||||||
const GraphView({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<GraphView> createState() => _GraphViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GraphViewState extends State<GraphView> {
|
|
||||||
var _selectedDate = DateTime.now();
|
|
||||||
Wallet? selectedWallet;
|
|
||||||
List<Wallet> wallets = [];
|
|
||||||
String? locale;
|
|
||||||
bool yearly = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeDependencies() {
|
|
||||||
super.didChangeDependencies();
|
|
||||||
locale ??= Localizations.localeOf(context).languageCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<double> generateChartData(EntryType type) {
|
|
||||||
final d = _selectedDate.add(const Duration(days: 31));
|
|
||||||
final data = List<double>.filled(
|
|
||||||
yearly ? 12 : DateTime(d.year, d.month, 0).day,
|
|
||||||
0,
|
|
||||||
);
|
|
||||||
if (selectedWallet == null) return [];
|
|
||||||
for (var i = 0; i < data.length; i++) {
|
|
||||||
final entriesForRange = selectedWallet!.entries.where(
|
|
||||||
(element) =>
|
|
||||||
((!yearly)
|
|
||||||
? element.date.month == _selectedDate.month &&
|
|
||||||
element.date.year == _selectedDate.year &&
|
|
||||||
element.date.day == i + 1
|
|
||||||
: element.date.month == i + 1 &&
|
|
||||||
element.date.year == _selectedDate.year) &&
|
|
||||||
element.type == type,
|
|
||||||
);
|
|
||||||
var sum = 0.0;
|
|
||||||
for (final e in entriesForRange) {
|
|
||||||
sum += e.data.amount;
|
|
||||||
}
|
|
||||||
data[i] = sum;
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
final availableYears = <WheelChoice<int>>[];
|
|
||||||
|
|
||||||
Future<void> loadWallet() async {
|
|
||||||
wallets = await WalletManager.listWallets();
|
|
||||||
if (wallets.isEmpty && mounted) {
|
|
||||||
unawaited(
|
|
||||||
Navigator.of(context)
|
|
||||||
.pushReplacement(platformRoute((c) => const SetupView())),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedWallet = wallets.first;
|
|
||||||
availableYears.clear();
|
|
||||||
for (final entry in selectedWallet!.entries) {
|
|
||||||
if (!availableYears.any((element) => element.value == entry.date.year)) {
|
|
||||||
availableYears.add(
|
|
||||||
WheelChoice<int>(
|
|
||||||
value: entry.date.year,
|
|
||||||
title: entry.date.year.toString(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setState(() {});
|
|
||||||
}
|
|
||||||
|
|
||||||
int? chartType;
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
loadWallet();
|
|
||||||
SharedPreferences.getInstance().then((s) {
|
|
||||||
chartType = s.getInt("yearlygraph") ?? 1;
|
|
||||||
logger.d(chartType);
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return DefaultTabController(
|
|
||||||
length: 2,
|
|
||||||
child: Scaffold(
|
|
||||||
floatingActionButton: Tooltip(
|
|
||||||
message: AppLocalizations.of(context).changeDate,
|
|
||||||
child: FloatingActionButton(
|
|
||||||
child: const Icon(Icons.calendar_month),
|
|
||||||
onPressed: () async {
|
|
||||||
var selectedYear = _selectedDate.year;
|
|
||||||
var selectedMonth = _selectedDate.month;
|
|
||||||
await showAdaptiveDialog<void>(
|
|
||||||
context: context,
|
|
||||||
builder: (c) => AlertDialog.adaptive(
|
|
||||||
title: Text(
|
|
||||||
yearly
|
|
||||||
? AppLocalizations.of(context).selectYear
|
|
||||||
: AppLocalizations.of(context).selectMonth,
|
|
||||||
),
|
|
||||||
content: LimitedBox(
|
|
||||||
maxHeight: MediaQuery.of(context).size.width * 0.7,
|
|
||||||
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
|
||||||
child: Wrap(
|
|
||||||
alignment: WrapAlignment.center,
|
|
||||||
spacing: 5,
|
|
||||||
children: [
|
|
||||||
if (!yearly)
|
|
||||||
SizedBox(
|
|
||||||
width: 120,
|
|
||||||
height: 100,
|
|
||||||
child: WheelChooser<int>.choices(
|
|
||||||
onChoiceChanged: (v) {
|
|
||||||
selectedMonth = v as int;
|
|
||||||
},
|
|
||||||
startPosition: _selectedDate.month - 1,
|
|
||||||
choices: List<WheelChoice<int>>.generate(
|
|
||||||
12,
|
|
||||||
(index) => WheelChoice(
|
|
||||||
value: index + 1,
|
|
||||||
title: DateFormat.MMMM(locale ?? "en").format(
|
|
||||||
DateTime(
|
|
||||||
_selectedDate.year,
|
|
||||||
index + 1,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
height: 100,
|
|
||||||
width: 80,
|
|
||||||
child: WheelChooser<int>.choices(
|
|
||||||
startPosition: availableYears.indexWhere(
|
|
||||||
(element) => element.value == _selectedDate.year,
|
|
||||||
),
|
|
||||||
onChoiceChanged: (v) {
|
|
||||||
selectedYear = v as int;
|
|
||||||
},
|
|
||||||
choices: availableYears,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
TextButton(
|
|
||||||
onPressed: () {
|
|
||||||
_selectedDate = DateTime(selectedYear, selectedMonth);
|
|
||||||
Navigator.of(c).pop();
|
|
||||||
},
|
|
||||||
child: Text(AppLocalizations.of(context).ok),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
appBar: AppBar(
|
|
||||||
bottom: TabBar(
|
|
||||||
tabs: [
|
|
||||||
Tab(
|
|
||||||
child: Text(AppLocalizations.of(context).expenses),
|
|
||||||
),
|
|
||||||
Tab(
|
|
||||||
child: Text(AppLocalizations.of(context).incomePlural),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
title: DropdownButton<int>(
|
|
||||||
value: (selectedWallet == null)
|
|
||||||
? -1
|
|
||||||
: wallets.indexOf(
|
|
||||||
wallets.where((w) => w.name == selectedWallet!.name).first,
|
|
||||||
),
|
|
||||||
items: [
|
|
||||||
...wallets.map(
|
|
||||||
(e) => DropdownMenuItem(
|
|
||||||
value: wallets.indexOf(
|
|
||||||
e,
|
|
||||||
),
|
|
||||||
child: Text(e.name),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
DropdownMenuItem(
|
|
||||||
value: -1,
|
|
||||||
child: Text(AppLocalizations.of(context).newWallet),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
onChanged: (v) async {
|
|
||||||
if (v == null || v == -1) {
|
|
||||||
await Navigator.of(context).push(
|
|
||||||
platformRoute(
|
|
||||||
(c) => const SetupView(
|
|
||||||
newWallet: true,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
wallets = await WalletManager.listWallets();
|
|
||||||
logger.i(wallets.length);
|
|
||||||
selectedWallet = wallets.last;
|
|
||||||
setState(() {});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
selectedWallet = wallets[v];
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
PopupMenuButton(
|
|
||||||
itemBuilder: (context) => [
|
|
||||||
AppLocalizations.of(context).settings,
|
|
||||||
AppLocalizations.of(context).about,
|
|
||||||
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
|
||||||
onSelected: (value) {
|
|
||||||
if (value == AppLocalizations.of(context).settings) {
|
|
||||||
Navigator.of(context)
|
|
||||||
.push(
|
|
||||||
platformRoute(
|
|
||||||
(context) => const SettingsView(),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.then((value) async {
|
|
||||||
selectedWallet =
|
|
||||||
await WalletManager.loadWallet(selectedWallet!.name);
|
|
||||||
final s = await SharedPreferences.getInstance();
|
|
||||||
chartType = s.getInt("monthlygraph") ?? 2;
|
|
||||||
setState(() {});
|
|
||||||
});
|
|
||||||
} else if (value == AppLocalizations.of(context).about) {
|
|
||||||
showAbout(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
drawer: makeDrawer(context, 2),
|
|
||||||
body: TabBarView(
|
|
||||||
children: [
|
|
||||||
// EXPENSE TAB
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: (selectedWallet == null)
|
|
||||||
? const CircularProgressIndicator(
|
|
||||||
strokeWidth: 5,
|
|
||||||
)
|
|
||||||
: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).monthly,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch.adaptive(
|
|
||||||
value: yearly,
|
|
||||||
onChanged: (v) async {
|
|
||||||
yearly = v;
|
|
||||||
final s =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
chartType = yearly
|
|
||||||
? (s.getInt("yearlygraph") ?? 1)
|
|
||||||
: (s.getInt("monthlygraph") ?? 2);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).yearly,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.light)
|
|
||||||
? [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
spreadRadius: 3,
|
|
||||||
blurRadius: 7,
|
|
||||||
offset: const Offset(
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
color: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.dark)
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
yearly
|
|
||||||
? AppLocalizations.of(context)
|
|
||||||
.expensesPerYear(
|
|
||||||
_selectedDate.year,
|
|
||||||
)
|
|
||||||
: AppLocalizations.of(context)
|
|
||||||
.expensesPerMonth(
|
|
||||||
DateFormat.yMMMM(locale)
|
|
||||||
.format(_selectedDate),
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width *
|
|
||||||
0.9,
|
|
||||||
height:
|
|
||||||
MediaQuery.of(context).size.height *
|
|
||||||
0.35,
|
|
||||||
child: (chartType == null)
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: (chartType == 1)
|
|
||||||
? ExpensesBarChart(
|
|
||||||
currency:
|
|
||||||
selectedWallet!.currency,
|
|
||||||
date: _selectedDate,
|
|
||||||
locale: locale ?? "en",
|
|
||||||
yearly: yearly,
|
|
||||||
expenseData:
|
|
||||||
generateChartData(
|
|
||||||
EntryType.expense,
|
|
||||||
),
|
|
||||||
incomeData: const [],
|
|
||||||
)
|
|
||||||
: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.all(8),
|
|
||||||
child: ExpensesLineChart(
|
|
||||||
currency: selectedWallet!
|
|
||||||
.currency,
|
|
||||||
date: _selectedDate,
|
|
||||||
locale: locale ?? "en",
|
|
||||||
yearly: yearly,
|
|
||||||
expenseData:
|
|
||||||
generateChartData(
|
|
||||||
EntryType.expense,
|
|
||||||
),
|
|
||||||
incomeData: const [],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 25,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.light)
|
|
||||||
? [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
spreadRadius: 3,
|
|
||||||
blurRadius: 7,
|
|
||||||
offset: const Offset(
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
color: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.dark)
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
width: MediaQuery.of(context).size.width * 0.95,
|
|
||||||
height: MediaQuery.of(context).size.height * 0.4,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
yearly
|
|
||||||
? AppLocalizations.of(context)
|
|
||||||
.expensesPerYearCategory(
|
|
||||||
_selectedDate.year,
|
|
||||||
)
|
|
||||||
: AppLocalizations.of(context)
|
|
||||||
.expensesPerMonthCategory(
|
|
||||||
DateFormat.yMMMM(locale)
|
|
||||||
.format(_selectedDate),
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
child: CategoriesPieChart(
|
|
||||||
// TODO: better size adaptivity without overflow
|
|
||||||
locale: locale ?? "en",
|
|
||||||
symbol: selectedWallet!.currency.symbol,
|
|
||||||
entries: selectedWallet!.entries
|
|
||||||
.where(
|
|
||||||
(element) =>
|
|
||||||
((!yearly)
|
|
||||||
? element.date.month ==
|
|
||||||
_selectedDate
|
|
||||||
.month &&
|
|
||||||
element.date.year ==
|
|
||||||
_selectedDate.year
|
|
||||||
: element.date.year ==
|
|
||||||
_selectedDate.year) &&
|
|
||||||
element.type ==
|
|
||||||
EntryType.expense,
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
categories: selectedWallet!.categories,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), // Expense Tab END
|
|
||||||
SingleChildScrollView(
|
|
||||||
child: Center(
|
|
||||||
child: (selectedWallet == null)
|
|
||||||
? const CircularProgressIndicator(
|
|
||||||
strokeWidth: 5,
|
|
||||||
)
|
|
||||||
: SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
height: MediaQuery.of(context).size.height,
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 200,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).monthly,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Switch.adaptive(
|
|
||||||
value: yearly,
|
|
||||||
onChanged: (v) async {
|
|
||||||
yearly = v;
|
|
||||||
final s =
|
|
||||||
await SharedPreferences.getInstance();
|
|
||||||
chartType = yearly
|
|
||||||
? (s.getInt("yearlygraph") ?? 1)
|
|
||||||
: (s.getInt("monthlygraph") ?? 2);
|
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
AppLocalizations.of(context).yearly,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.light)
|
|
||||||
? [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
spreadRadius: 3,
|
|
||||||
blurRadius: 7,
|
|
||||||
offset: const Offset(
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
color: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.dark)
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8),
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
yearly
|
|
||||||
? AppLocalizations.of(context)
|
|
||||||
.incomePerYear(
|
|
||||||
_selectedDate.year,
|
|
||||||
)
|
|
||||||
: AppLocalizations.of(context)
|
|
||||||
.incomePerMonth(
|
|
||||||
DateFormat.yMMMM(locale)
|
|
||||||
.format(_selectedDate),
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 15,
|
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width *
|
|
||||||
0.9,
|
|
||||||
height:
|
|
||||||
MediaQuery.of(context).size.height *
|
|
||||||
0.35,
|
|
||||||
child: (chartType == null)
|
|
||||||
? const CircularProgressIndicator()
|
|
||||||
: (chartType == 1)
|
|
||||||
? ExpensesBarChart(
|
|
||||||
currency:
|
|
||||||
selectedWallet!.currency,
|
|
||||||
date: _selectedDate,
|
|
||||||
locale: locale ?? "en",
|
|
||||||
yearly: yearly,
|
|
||||||
expenseData: const [],
|
|
||||||
incomeData: generateChartData(
|
|
||||||
EntryType.income,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: Padding(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.all(8),
|
|
||||||
child: ExpensesLineChart(
|
|
||||||
currency: selectedWallet!
|
|
||||||
.currency,
|
|
||||||
date: _selectedDate,
|
|
||||||
locale: locale ?? "en",
|
|
||||||
yearly: yearly,
|
|
||||||
expenseData: const [],
|
|
||||||
incomeData:
|
|
||||||
generateChartData(
|
|
||||||
EntryType.income,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 25,
|
|
||||||
),
|
|
||||||
Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: BorderRadius.circular(8),
|
|
||||||
boxShadow: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.light)
|
|
||||||
? [
|
|
||||||
BoxShadow(
|
|
||||||
color: Colors.grey.withOpacity(0.5),
|
|
||||||
spreadRadius: 3,
|
|
||||||
blurRadius: 7,
|
|
||||||
offset: const Offset(
|
|
||||||
0,
|
|
||||||
3,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
: null,
|
|
||||||
color: (MediaQuery.of(context)
|
|
||||||
.platformBrightness ==
|
|
||||||
Brightness.dark)
|
|
||||||
? Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.secondaryContainer
|
|
||||||
: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
width: MediaQuery.of(context).size.width * 0.95,
|
|
||||||
height: MediaQuery.of(context).size.height * 0.4,
|
|
||||||
child: Column(
|
|
||||||
children: [
|
|
||||||
const SizedBox(
|
|
||||||
height: 10,
|
|
||||||
),
|
|
||||||
Flexible(
|
|
||||||
child: Text(
|
|
||||||
yearly
|
|
||||||
? AppLocalizations.of(context)
|
|
||||||
.incomePerYearCategory(
|
|
||||||
_selectedDate.year,
|
|
||||||
)
|
|
||||||
: AppLocalizations.of(context)
|
|
||||||
.incomePerMonthCategory(
|
|
||||||
DateFormat.yMMMM(locale)
|
|
||||||
.format(_selectedDate),
|
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(6),
|
|
||||||
child: CategoriesPieChart(
|
|
||||||
locale: locale ?? "en",
|
|
||||||
symbol: selectedWallet!.currency.symbol,
|
|
||||||
entries: selectedWallet!.entries
|
|
||||||
.where(
|
|
||||||
(element) =>
|
|
||||||
((!yearly)
|
|
||||||
? element.date.month ==
|
|
||||||
_selectedDate
|
|
||||||
.month &&
|
|
||||||
element.date.year ==
|
|
||||||
_selectedDate.year
|
|
||||||
: element.date.year ==
|
|
||||||
_selectedDate.year) &&
|
|
||||||
element.type ==
|
|
||||||
EntryType.income,
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
categories: selectedWallet!.categories,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
), // Income Tab END
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
688
lib/views/graphs/graph_view.dart
Normal file
688
lib/views/graphs/graph_view.dart
Normal file
|
@ -0,0 +1,688 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prasule/api/category.dart';
|
||||||
|
import 'package:prasule/api/wallet.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/main.dart';
|
||||||
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
import 'package:prasule/util/drawer.dart';
|
||||||
|
import 'package:prasule/util/graphs.dart';
|
||||||
|
import 'package:prasule/util/utils.dart';
|
||||||
|
import 'package:prasule/views/settings/settings.dart';
|
||||||
|
import 'package:prasule/views/setup.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:wheel_chooser/wheel_chooser.dart';
|
||||||
|
|
||||||
|
/// Shows data from a [Wallet] in graphs
|
||||||
|
class GraphView extends StatefulWidget {
|
||||||
|
/// Shows data from a [Wallet] in graphs
|
||||||
|
const GraphView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GraphView> createState() => _GraphViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GraphViewState extends State<GraphView> {
|
||||||
|
var _selectedDate = DateTime.now();
|
||||||
|
List<Wallet> wallets = [];
|
||||||
|
String? locale;
|
||||||
|
bool yearly = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
locale ??= Localizations.localeOf(context).languageCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<double> generateChartData(EntryType type) {
|
||||||
|
final d = _selectedDate.add(const Duration(days: 31));
|
||||||
|
final data = List<double>.filled(
|
||||||
|
yearly ? 12 : DateTime(d.year, d.month, 0).day,
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
for (var i = 0; i < data.length; i++) {
|
||||||
|
final entriesForRange = WalletManager.selectedWallet.entries.where(
|
||||||
|
(element) =>
|
||||||
|
((!yearly)
|
||||||
|
? element.date.month == _selectedDate.month &&
|
||||||
|
element.date.year == _selectedDate.year &&
|
||||||
|
element.date.day == i + 1
|
||||||
|
: element.date.month == i + 1 &&
|
||||||
|
element.date.year == _selectedDate.year) &&
|
||||||
|
element.type == type,
|
||||||
|
);
|
||||||
|
var sum = 0.0;
|
||||||
|
for (final e in entriesForRange) {
|
||||||
|
sum += e.data.amount;
|
||||||
|
}
|
||||||
|
data[i] = sum;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
final availableYears = <WheelChoice<int>>[];
|
||||||
|
|
||||||
|
void loadWallet() {
|
||||||
|
wallets = WalletManager.listWallets();
|
||||||
|
if (wallets.isEmpty && mounted) {
|
||||||
|
unawaited(
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacement(platformRoute((c) => const SetupView())),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WalletManager.selectedWallet = wallets.first;
|
||||||
|
availableYears.clear();
|
||||||
|
for (final entry in WalletManager.selectedWallet.entries) {
|
||||||
|
if (!availableYears.any((element) => element.value == entry.date.year)) {
|
||||||
|
availableYears.add(
|
||||||
|
WheelChoice<int>(
|
||||||
|
value: entry.date.year,
|
||||||
|
title: entry.date.year.toString(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
int? chartType;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadWallet();
|
||||||
|
SharedPreferences.getInstance().then((s) {
|
||||||
|
chartType = s.getInt("yearlygraph") ?? 1;
|
||||||
|
logger.d(chartType);
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return DefaultTabController(
|
||||||
|
length: 2,
|
||||||
|
child: Scaffold(
|
||||||
|
floatingActionButton: Tooltip(
|
||||||
|
message: AppLocalizations.of(context).changeDate,
|
||||||
|
child: FloatingActionButton(
|
||||||
|
child: const Icon(Icons.calendar_month),
|
||||||
|
onPressed: () async {
|
||||||
|
var selectedYear = _selectedDate.year;
|
||||||
|
var selectedMonth = _selectedDate.month;
|
||||||
|
await showAdaptiveDialog<void>(
|
||||||
|
context: context,
|
||||||
|
builder: (c) => AlertDialog.adaptive(
|
||||||
|
title: Text(
|
||||||
|
yearly
|
||||||
|
? AppLocalizations.of(context).selectYear
|
||||||
|
: AppLocalizations.of(context).selectMonth,
|
||||||
|
),
|
||||||
|
content: LimitedBox(
|
||||||
|
maxHeight: MediaQuery.of(context).size.width * 0.7,
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: Wrap(
|
||||||
|
alignment: WrapAlignment.center,
|
||||||
|
spacing: 5,
|
||||||
|
children: [
|
||||||
|
if (!yearly)
|
||||||
|
SizedBox(
|
||||||
|
width: 120,
|
||||||
|
height: 100,
|
||||||
|
child: WheelChooser<int>.choices(
|
||||||
|
onChoiceChanged: (v) {
|
||||||
|
selectedMonth = v as int;
|
||||||
|
},
|
||||||
|
startPosition: _selectedDate.month - 1,
|
||||||
|
choices: List<WheelChoice<int>>.generate(
|
||||||
|
12,
|
||||||
|
(index) => WheelChoice(
|
||||||
|
value: index + 1,
|
||||||
|
title: DateFormat.MMMM(locale ?? "en").format(
|
||||||
|
DateTime(
|
||||||
|
_selectedDate.year,
|
||||||
|
index + 1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
height: 100,
|
||||||
|
width: 80,
|
||||||
|
child: WheelChooser<int>.choices(
|
||||||
|
startPosition: availableYears.indexWhere(
|
||||||
|
(element) => element.value == _selectedDate.year,
|
||||||
|
),
|
||||||
|
onChoiceChanged: (v) {
|
||||||
|
selectedYear = v as int;
|
||||||
|
},
|
||||||
|
choices: availableYears,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
_selectedDate = DateTime(selectedYear, selectedMonth);
|
||||||
|
Navigator.of(c).pop();
|
||||||
|
},
|
||||||
|
child: Text(AppLocalizations.of(context).ok),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
appBar: AppBar(
|
||||||
|
bottom: TabBar(
|
||||||
|
tabs: [
|
||||||
|
Tab(
|
||||||
|
child: Text(AppLocalizations.of(context).expenses),
|
||||||
|
),
|
||||||
|
Tab(
|
||||||
|
child: Text(AppLocalizations.of(context).incomePlural),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
title: DropdownButton<int>(
|
||||||
|
value: wallets.indexOf(
|
||||||
|
wallets
|
||||||
|
.where((w) => w.name == WalletManager.selectedWallet.name)
|
||||||
|
.first,
|
||||||
|
),
|
||||||
|
items: [
|
||||||
|
...wallets.map(
|
||||||
|
(e) => DropdownMenuItem(
|
||||||
|
value: wallets.indexOf(
|
||||||
|
e,
|
||||||
|
),
|
||||||
|
child: Text(e.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: -1,
|
||||||
|
child: Text(AppLocalizations.of(context).newWallet),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) async {
|
||||||
|
if (v == null || v == -1) {
|
||||||
|
await Navigator.of(context).push(
|
||||||
|
platformRoute(
|
||||||
|
(c) => const SetupView(
|
||||||
|
newWallet: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
wallets = WalletManager.listWallets();
|
||||||
|
logger.i(wallets.length);
|
||||||
|
WalletManager.selectedWallet = wallets.last;
|
||||||
|
setState(() {});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WalletManager.selectedWallet = wallets[v];
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton(
|
||||||
|
itemBuilder: (context) => [
|
||||||
|
AppLocalizations.of(context).settings,
|
||||||
|
AppLocalizations.of(context).about,
|
||||||
|
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
||||||
|
onSelected: (value) {
|
||||||
|
if (value == AppLocalizations.of(context).settings) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push(
|
||||||
|
platformRoute(
|
||||||
|
(context) => const SettingsView(),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then((value) async {
|
||||||
|
WalletManager.selectedWallet = WalletManager.loadWallet(
|
||||||
|
WalletManager.selectedWallet.name);
|
||||||
|
final s = await SharedPreferences.getInstance();
|
||||||
|
chartType = s.getInt("monthlygraph") ?? 2;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
} else if (value == AppLocalizations.of(context).about) {
|
||||||
|
showAbout(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
drawer: makeDrawer(context, Pages.graphs),
|
||||||
|
body: TabBarView(
|
||||||
|
children: [
|
||||||
|
// EXPENSE TAB
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).monthly,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch.adaptive(
|
||||||
|
value: yearly,
|
||||||
|
onChanged: (v) async {
|
||||||
|
yearly = v;
|
||||||
|
final s = await SharedPreferences.getInstance();
|
||||||
|
chartType = yearly
|
||||||
|
? (s.getInt("yearlygraph") ?? 1)
|
||||||
|
: (s.getInt("monthlygraph") ?? 2);
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).yearly,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow:
|
||||||
|
(MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.light)
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.5),
|
||||||
|
spreadRadius: 3,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
color: (MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark)
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
yearly
|
||||||
|
? AppLocalizations.of(context)
|
||||||
|
.expensesPerYear(
|
||||||
|
_selectedDate.year,
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context)
|
||||||
|
.expensesPerMonth(
|
||||||
|
DateFormat.yMMMM(locale)
|
||||||
|
.format(_selectedDate),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 0.35,
|
||||||
|
child: (chartType == null)
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: (chartType == 1)
|
||||||
|
? ExpensesBarChart(
|
||||||
|
currency: WalletManager
|
||||||
|
.selectedWallet.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: generateChartData(
|
||||||
|
EntryType.expense,
|
||||||
|
),
|
||||||
|
incomeData: const [],
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: ExpensesLineChart(
|
||||||
|
currency: WalletManager
|
||||||
|
.selectedWallet.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: generateChartData(
|
||||||
|
EntryType.expense,
|
||||||
|
),
|
||||||
|
incomeData: const [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 25,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow:
|
||||||
|
(MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.light)
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.5),
|
||||||
|
spreadRadius: 3,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
color: (MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark)
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
width: MediaQuery.of(context).size.width * 0.95,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.4,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
yearly
|
||||||
|
? AppLocalizations.of(context)
|
||||||
|
.expensesPerYearCategory(
|
||||||
|
_selectedDate.year,
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context)
|
||||||
|
.expensesPerMonthCategory(
|
||||||
|
DateFormat.yMMMM(locale)
|
||||||
|
.format(_selectedDate),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: CategoriesPieChart(
|
||||||
|
// TODO: better size adaptivity without overflow
|
||||||
|
locale: locale ?? "en",
|
||||||
|
symbol: WalletManager
|
||||||
|
.selectedWallet.currency.symbol,
|
||||||
|
entries: WalletManager.selectedWallet.entries
|
||||||
|
.where(
|
||||||
|
(element) =>
|
||||||
|
((!yearly)
|
||||||
|
? element.date.month ==
|
||||||
|
_selectedDate.month &&
|
||||||
|
element.date.year ==
|
||||||
|
_selectedDate.year
|
||||||
|
: element.date.year ==
|
||||||
|
_selectedDate.year) &&
|
||||||
|
element.type == EntryType.expense,
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
categories:
|
||||||
|
WalletManager.selectedWallet.categories,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), // Expense Tab END
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).monthly,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Switch.adaptive(
|
||||||
|
value: yearly,
|
||||||
|
onChanged: (v) async {
|
||||||
|
yearly = v;
|
||||||
|
final s = await SharedPreferences.getInstance();
|
||||||
|
chartType = yearly
|
||||||
|
? (s.getInt("yearlygraph") ?? 1)
|
||||||
|
: (s.getInt("monthlygraph") ?? 2);
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).yearly,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow:
|
||||||
|
(MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.light)
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.5),
|
||||||
|
spreadRadius: 3,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
color: (MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark)
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
yearly
|
||||||
|
? AppLocalizations.of(context)
|
||||||
|
.incomePerYear(
|
||||||
|
_selectedDate.year,
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context)
|
||||||
|
.incomePerMonth(
|
||||||
|
DateFormat.yMMMM(locale)
|
||||||
|
.format(_selectedDate),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
height:
|
||||||
|
MediaQuery.of(context).size.height * 0.35,
|
||||||
|
child: (chartType == null)
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: (chartType == 1)
|
||||||
|
? ExpensesBarChart(
|
||||||
|
currency: WalletManager
|
||||||
|
.selectedWallet.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: const [],
|
||||||
|
incomeData: generateChartData(
|
||||||
|
EntryType.income,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: ExpensesLineChart(
|
||||||
|
currency: WalletManager
|
||||||
|
.selectedWallet.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: const [],
|
||||||
|
incomeData: generateChartData(
|
||||||
|
EntryType.income,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 25,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
boxShadow:
|
||||||
|
(MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.light)
|
||||||
|
? [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.grey.withOpacity(0.5),
|
||||||
|
spreadRadius: 3,
|
||||||
|
blurRadius: 7,
|
||||||
|
offset: const Offset(
|
||||||
|
0,
|
||||||
|
3,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: null,
|
||||||
|
color: (MediaQuery.of(context).platformBrightness ==
|
||||||
|
Brightness.dark)
|
||||||
|
? Theme.of(context).colorScheme.secondaryContainer
|
||||||
|
: Theme.of(context).colorScheme.surface,
|
||||||
|
),
|
||||||
|
width: MediaQuery.of(context).size.width * 0.95,
|
||||||
|
height: MediaQuery.of(context).size.height * 0.4,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Text(
|
||||||
|
yearly
|
||||||
|
? AppLocalizations.of(context)
|
||||||
|
.incomePerYearCategory(
|
||||||
|
_selectedDate.year,
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context)
|
||||||
|
.incomePerMonthCategory(
|
||||||
|
DateFormat.yMMMM(locale)
|
||||||
|
.format(_selectedDate),
|
||||||
|
),
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(6),
|
||||||
|
child: CategoriesPieChart(
|
||||||
|
locale: locale ?? "en",
|
||||||
|
symbol: WalletManager
|
||||||
|
.selectedWallet.currency.symbol,
|
||||||
|
entries: WalletManager.selectedWallet.entries
|
||||||
|
.where(
|
||||||
|
(element) =>
|
||||||
|
((!yearly)
|
||||||
|
? element.date.month ==
|
||||||
|
_selectedDate.month &&
|
||||||
|
element.date.year ==
|
||||||
|
_selectedDate.year
|
||||||
|
: element.date.year ==
|
||||||
|
_selectedDate.year) &&
|
||||||
|
element.type == EntryType.income,
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
categories:
|
||||||
|
WalletManager.selectedWallet.categories,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), // Income Tab END
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
|
@ -25,7 +29,7 @@ import 'package:prasule/util/drawer.dart';
|
||||||
import 'package:prasule/util/sorting.dart';
|
import 'package:prasule/util/sorting.dart';
|
||||||
import 'package:prasule/util/text_color.dart';
|
import 'package:prasule/util/text_color.dart';
|
||||||
import 'package:prasule/util/utils.dart';
|
import 'package:prasule/util/utils.dart';
|
||||||
import 'package:prasule/views/create_entry.dart';
|
import 'package:prasule/views/entry/create_entry.dart';
|
||||||
import 'package:prasule/views/settings/settings.dart';
|
import 'package:prasule/views/settings/settings.dart';
|
||||||
import 'package:prasule/views/settings/tessdata_list.dart';
|
import 'package:prasule/views/settings/tessdata_list.dart';
|
||||||
import 'package:prasule/views/setup.dart';
|
import 'package:prasule/views/setup.dart';
|
||||||
|
@ -63,8 +67,8 @@ class _HomeViewState extends State<HomeView> {
|
||||||
loadWallet();
|
loadWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadWallet() async {
|
void loadWallet() {
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
if (wallets.isEmpty && mounted) {
|
if (wallets.isEmpty && mounted) {
|
||||||
unawaited(
|
unawaited(
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
|
@ -82,7 +86,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: !_searchActive, // don't pop when we just want
|
canPop: !_searchActive, // don't pop when we just want
|
||||||
// to deactivate searchfield
|
// to deactivate searchfield
|
||||||
onPopInvoked: (b) {
|
onPopInvokedWithResult: (b, d) {
|
||||||
if (b) return;
|
if (b) return;
|
||||||
_searchActive = false;
|
_searchActive = false;
|
||||||
_filter = "";
|
_filter = "";
|
||||||
|
@ -90,8 +94,10 @@ class _HomeViewState extends State<HomeView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
drawer: makeDrawer(context, 1),
|
drawer: makeDrawer(context, Pages.home),
|
||||||
floatingActionButton: SpeedDial(
|
floatingActionButton: SpeedDial(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||||
shape: const RoundedRectangleBorder(
|
shape: const RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
),
|
),
|
||||||
|
@ -105,13 +111,12 @@ class _HomeViewState extends State<HomeView> {
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// debug option to quickly fill a wallet with data
|
// debug option to quickly fill a wallet with data
|
||||||
if (selectedWallet == null) return;
|
if (selectedWallet == null) return;
|
||||||
selectedWallet!.createTestEntries().then((_) {
|
selectedWallet!.createTestEntries();
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
platformRoute(
|
platformRoute(
|
||||||
(p0) => const HomeView(),
|
(p0) => const HomeView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
SpeedDialChild(
|
SpeedDialChild(
|
||||||
|
@ -194,7 +199,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
selectedWallet = wallets.last;
|
selectedWallet = wallets.last;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
return;
|
return;
|
||||||
|
@ -248,9 +253,9 @@ class _HomeViewState extends State<HomeView> {
|
||||||
(context) => const SettingsView(),
|
(context) => const SettingsView(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then((value) async {
|
.then((value) {
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
selectedWallet = await WalletManager.loadWallet(
|
selectedWallet = WalletManager.loadWallet(
|
||||||
selectedWallet!.name,
|
selectedWallet!.name,
|
||||||
);
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -630,11 +635,14 @@ class _HomeViewState extends State<HomeView> {
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.push(
|
.push(
|
||||||
platformRoute(
|
platformRoute(
|
||||||
(c) => const TessdataListView(),
|
(c) => const TessdataListView(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.then((value) => Navigator.of(c).pop());
|
.then((value) {
|
||||||
|
if (!c.mounted) return;
|
||||||
|
Navigator.of(c).pop();
|
||||||
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
|
@ -741,7 +749,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
);
|
);
|
||||||
if (newEntry == null) return;
|
if (newEntry == null) return;
|
||||||
selectedWallet!.entries.add(newEntry);
|
selectedWallet!.entries.add(newEntry);
|
||||||
await WalletManager.saveWallet(selectedWallet!);
|
WalletManager.saveWallet(selectedWallet!);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: const Text("Ok"),
|
child: const Text("Ok"),
|
||||||
|
|
48
lib/views/initialization_screen.dart
Normal file
48
lib/views/initialization_screen.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
import 'package:prasule/views/home.dart';
|
||||||
|
import 'package:prasule/views/setup.dart';
|
||||||
|
|
||||||
|
/// Intermediate screen used to initialize variables relying on context
|
||||||
|
class InitializationScreen extends StatefulWidget {
|
||||||
|
/// Intermediate screen used to initialize variables relying on context
|
||||||
|
const InitializationScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<InitializationScreen> createState() => _InitializationScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _InitializationScreenState extends State<InitializationScreen> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
getApplicationDocumentsDirectory().then((v) {
|
||||||
|
WalletManager.walletPath = "${v.path}/wallets";
|
||||||
|
if (!mounted) return;
|
||||||
|
final wallets = WalletManager.listWallets();
|
||||||
|
if (wallets.isEmpty && mounted) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacement(platformRoute((c) => const SetupView()));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
WalletManager.selectedWallet = wallets.first;
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushReplacement(platformRoute((c) => const HomeView()));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return const Scaffold(
|
||||||
|
body: Align(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
@ -323,9 +327,8 @@ class _CreateRecurringEntryViewState extends State<CreateRecurringEntryView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
widget.w.recurringEntries.add(newEntry);
|
widget.w.recurringEntries.add(newEntry);
|
||||||
WalletManager.saveWallet(widget.w).then(
|
WalletManager.saveWallet(widget.w); // TODO loading circle?
|
||||||
(value) => Navigator.of(context).pop(widget.w),
|
Navigator.of(context).pop(widget.w);
|
||||||
); // TODO loading circle?
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
@ -13,7 +17,7 @@ import 'package:prasule/pw/platformroute.dart';
|
||||||
import 'package:prasule/util/drawer.dart';
|
import 'package:prasule/util/drawer.dart';
|
||||||
import 'package:prasule/util/text_color.dart';
|
import 'package:prasule/util/text_color.dart';
|
||||||
import 'package:prasule/util/utils.dart';
|
import 'package:prasule/util/utils.dart';
|
||||||
import 'package:prasule/views/create_recur_entry.dart';
|
import 'package:prasule/views/recurring/create_recur_entry.dart';
|
||||||
import 'package:prasule/views/settings/settings.dart';
|
import 'package:prasule/views/settings/settings.dart';
|
||||||
import 'package:prasule/views/setup.dart';
|
import 'package:prasule/views/setup.dart';
|
||||||
|
|
||||||
|
@ -44,8 +48,8 @@ class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
||||||
loadWallet();
|
loadWallet();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadWallet() async {
|
void loadWallet() {
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
if (wallets.isEmpty && mounted) {
|
if (wallets.isEmpty && mounted) {
|
||||||
unawaited(
|
unawaited(
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
|
@ -60,7 +64,7 @@ class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
drawer: makeDrawer(context, 3),
|
drawer: makeDrawer(context, Pages.recurringEntries),
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: DropdownButton<int>(
|
title: DropdownButton<int>(
|
||||||
value: (selectedWallet == null)
|
value: (selectedWallet == null)
|
||||||
|
@ -91,7 +95,7 @@ class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
selectedWallet = wallets.last;
|
selectedWallet = wallets.last;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
return;
|
return;
|
||||||
|
@ -116,7 +120,7 @@ class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
||||||
)
|
)
|
||||||
.then((value) async {
|
.then((value) async {
|
||||||
selectedWallet =
|
selectedWallet =
|
||||||
await WalletManager.loadWallet(selectedWallet!.name);
|
WalletManager.loadWallet(selectedWallet!.name);
|
||||||
});
|
});
|
||||||
} else if (value == AppLocalizations.of(context).about) {
|
} else if (value == AppLocalizations.of(context).about) {
|
||||||
showAbout(context);
|
showAbout(context);
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -39,7 +43,7 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadWallet() async {
|
Future<void> loadWallet() async {
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
if (wallets.isEmpty && mounted) {
|
if (wallets.isEmpty && mounted) {
|
||||||
unawaited(
|
unawaited(
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
|
@ -82,7 +86,7 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
wallets = await WalletManager.listWallets();
|
wallets = WalletManager.listWallets();
|
||||||
logger.i(wallets.length);
|
logger.i(wallets.length);
|
||||||
selectedWallet = wallets.last;
|
selectedWallet = wallets.last;
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -122,7 +126,7 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
selectedWallet!.categories.add(
|
selectedWallet!.categories.add(
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)
|
name: AppLocalizations.of(context)
|
||||||
|
@ -137,7 +141,7 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await WalletManager.saveWallet(selectedWallet!);
|
WalletManager.saveWallet(selectedWallet!);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add),
|
||||||
|
@ -206,7 +210,7 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
await WalletManager.saveWallet(selectedWallet!);
|
WalletManager.saveWallet(selectedWallet!);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -226,8 +230,8 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
icon: const Icon(Icons.cancel),
|
icon: const Icon(Icons.cancel),
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
await selectedWallet!.removeCategory(
|
selectedWallet!.removeCategory(
|
||||||
selectedWallet!.categories[i],
|
selectedWallet!.categories[i],
|
||||||
);
|
);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
@ -243,11 +247,11 @@ class _EditCategoriesViewState extends State<EditCategoriesView> {
|
||||||
builder: (c) => AlertDialog.adaptive(
|
builder: (c) => AlertDialog.adaptive(
|
||||||
actions: [
|
actions: [
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () async {
|
onPressed: () {
|
||||||
if (controller.text.isEmpty) return;
|
if (controller.text.isEmpty) return;
|
||||||
selectedWallet!.categories[i].name =
|
selectedWallet!.categories[i].name =
|
||||||
controller.text;
|
controller.text;
|
||||||
await WalletManager.saveWallet(
|
WalletManager.saveWallet(
|
||||||
selectedWallet!,
|
selectedWallet!,
|
||||||
);
|
);
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -117,7 +121,7 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
description:
|
description:
|
||||||
Text(AppLocalizations.of(context).exportSingleDesc),
|
Text(AppLocalizations.of(context).exportSingleDesc),
|
||||||
onPressed: (ctx) async {
|
onPressed: (ctx) async {
|
||||||
final all = await WalletManager.listWallets();
|
final all = WalletManager.listWallets();
|
||||||
if (!ctx.mounted) return;
|
if (!ctx.mounted) return;
|
||||||
final w = await showAdaptiveDialog<String>(
|
final w = await showAdaptiveDialog<String>(
|
||||||
context: ctx,
|
context: ctx,
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: (C) 2024 Matyáš Caras
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
@ -131,7 +135,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
next: Text(AppLocalizations.of(context).next),
|
next: Text(AppLocalizations.of(context).next),
|
||||||
back: Text(AppLocalizations.of(context).back),
|
back: Text(AppLocalizations.of(context).back),
|
||||||
done: Text(AppLocalizations.of(context).finish),
|
done: Text(AppLocalizations.of(context).finish),
|
||||||
onDone: () async {
|
onDone: () {
|
||||||
if (_nameController.text.isEmpty) {
|
if (_nameController.text.isEmpty) {
|
||||||
unawaited(
|
unawaited(
|
||||||
showMessage(
|
showMessage(
|
||||||
|
@ -141,7 +145,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (await WalletManager.exists(_nameController.text) &&
|
if (WalletManager.exists(_nameController.text) &&
|
||||||
context.mounted) {
|
context.mounted) {
|
||||||
unawaited(
|
unawaited(
|
||||||
showMessage(
|
showMessage(
|
||||||
|
@ -157,12 +161,15 @@ class _SetupViewState extends State<SetupView> {
|
||||||
categories: categories,
|
categories: categories,
|
||||||
starterBalance: double.parse(_balanceController.text),
|
starterBalance: double.parse(_balanceController.text),
|
||||||
);
|
);
|
||||||
await WalletManager.saveWallet(wallet);
|
WalletManager.saveWallet(wallet);
|
||||||
|
|
||||||
if (widget.newWallet && context.mounted) {
|
if (widget.newWallet && context.mounted) {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WalletManager.selectedWallet = wallet;
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
unawaited(
|
unawaited(
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
|
|
193
pubspec.lock
193
pubspec.lock
|
@ -5,26 +5,31 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: _fe_analyzer_shared
|
name: _fe_analyzer_shared
|
||||||
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
|
sha256: "5aaf60d96c4cd00fe7f21594b5ad6a1b699c80a27420f8a837f4d68473ef09e3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "67.0.0"
|
version: "68.0.0"
|
||||||
|
_macros:
|
||||||
|
dependency: transitive
|
||||||
|
description: dart
|
||||||
|
source: sdk
|
||||||
|
version: "0.1.5"
|
||||||
analyzer:
|
analyzer:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: analyzer
|
name: analyzer
|
||||||
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
|
sha256: "21f1d3720fd1c70316399d5e2bccaebb415c434592d778cce8acb967b8578808"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.4.1"
|
version: "6.5.0"
|
||||||
archive:
|
archive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: archive
|
name: archive
|
||||||
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
|
sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.10"
|
version: "3.6.1"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -69,10 +74,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_daemon
|
name: build_daemon
|
||||||
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
|
sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.2"
|
||||||
build_resolvers:
|
build_resolvers:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -85,18 +90,18 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
|
sha256: "644dc98a0f179b872f612d3eb627924b578897c629788e858157fa5e704ca0c7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.9"
|
version: "2.4.11"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
|
sha256: e3c79f69a64bdfcd8a776a3c28db4eb6e3fb5356d013ae5eb2e52007706d5dbe
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "7.3.0"
|
version: "7.3.1"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -173,10 +178,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
|
sha256: "3945034e86ea203af7a056d98e98e42a5518fff200d6e8e6647e1886b07e936e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.2"
|
version: "1.8.0"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -285,18 +290,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flex_color_picker
|
name: flex_color_picker
|
||||||
sha256: "5c846437069fb7afdd7ade6bf37e628a71d2ab0787095ddcb1253bf9345d5f3a"
|
sha256: "809af4ec82ede3b140ed0219b97d548de99e47aa4b99b14a10f705a2dbbcba5e"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "3.5.1"
|
||||||
flex_seed_scheme:
|
flex_seed_scheme:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: flex_seed_scheme
|
name: flex_seed_scheme
|
||||||
sha256: "4cee2f1d07259f77e8b36f4ec5f35499d19e74e17c7dce5b819554914082bc01"
|
sha256: "6c595e545b0678e1fe17e8eec3d1fbca7237482da194fadc20ad8607dc7a7f3d"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.5.0"
|
version: "3.0.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -379,14 +384,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.13.1"
|
version: "0.13.1"
|
||||||
flutter_lints:
|
|
||||||
dependency: "direct dev"
|
|
||||||
description:
|
|
||||||
name: flutter_lints
|
|
||||||
sha256: "9e8c3858111da373efc5aa341de011d9bd23e2c5c5e0c62bccf32438e192d7b1"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.2"
|
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -412,10 +409,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: flutter_tesseract_ocr
|
name: flutter_tesseract_ocr
|
||||||
sha256: "4a8d0e3f562ee01d94a464ff9d31d9e907b1e374aeff29bf696f979417c70bcf"
|
sha256: c3af11e3a8803b36bd24f7ec8ff3e0e21168dac917407357ae177ddf9fe10180
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.24"
|
version: "0.4.26"
|
||||||
flutter_test:
|
flutter_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -430,10 +427,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fluttertoast
|
name: fluttertoast
|
||||||
sha256: "81b68579e23fcbcada2db3d50302813d2371664afe6165bc78148050ab94bf66"
|
sha256: "7eae679e596a44fdf761853a706f74979f8dd3cd92cf4e23cae161fda091b847"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.2.5"
|
version: "8.2.6"
|
||||||
font_awesome_flutter:
|
font_awesome_flutter:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -499,10 +496,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image
|
name: image
|
||||||
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
|
sha256: "2237616a36c0d69aef7549ab439b833fb7f9fb9fc861af2cc9ac3eedddd69ca8"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.7"
|
version: "4.2.0"
|
||||||
integration_test:
|
integration_test:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -544,34 +541,34 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: json_annotation
|
name: json_annotation
|
||||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.1"
|
version: "4.9.0"
|
||||||
json_serializable:
|
json_serializable:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: json_serializable
|
name: json_serializable
|
||||||
sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969
|
sha256: ea1432d167339ea9b5bb153f0571d0039607a873d6e04e0117af043f14a1fd4b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.7.1"
|
version: "6.8.0"
|
||||||
leak_tracker:
|
leak_tracker:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: leak_tracker
|
name: leak_tracker
|
||||||
sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a"
|
sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.4"
|
version: "10.0.5"
|
||||||
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: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8"
|
sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.0.5"
|
||||||
leak_tracker_testing:
|
leak_tracker_testing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -580,22 +577,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
lints:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: lints
|
|
||||||
sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.0.0"
|
|
||||||
logger:
|
logger:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: logger
|
name: logger
|
||||||
sha256: "8c94b8c219e7e50194efc8771cd0e9f10807d8d3e219af473d89b06cc2ee4e04"
|
sha256: af05cc8714f356fd1f3888fb6741cbe9fbe25cdb6eedbab80e1a6db21047d4a4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.3.0"
|
||||||
logging:
|
logging:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -604,6 +593,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.0"
|
version: "1.2.0"
|
||||||
|
macros:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: macros
|
||||||
|
sha256: a8403c89b36483b4cbf9f1fcd24562f483cb34a5c9bf101cf2b0d8a083cf1239
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.0-main.5"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -616,18 +613,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: material_color_utilities
|
name: material_color_utilities
|
||||||
sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
|
sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.0"
|
version: "0.11.1"
|
||||||
meta:
|
meta:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: meta
|
name: meta
|
||||||
sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136"
|
sha256: "25dfcaf170a0190f47ca6355bdd4552cb8924b430512ff0cafb8db9bd41fe33b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.12.0"
|
version: "1.14.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -680,18 +677,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_android
|
name: path_provider_android
|
||||||
sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d
|
sha256: bca87b0165ffd7cdb9cad8edd22d18d2201e886d9a9f19b4fb3452ea7df3a72a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.4"
|
version: "2.2.6"
|
||||||
path_provider_foundation:
|
path_provider_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_foundation
|
name: path_provider_foundation
|
||||||
sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f"
|
sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.4.0"
|
||||||
path_provider_linux:
|
path_provider_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -740,14 +737,6 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.8"
|
version: "2.1.8"
|
||||||
pointycastle:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: pointycastle
|
|
||||||
sha256: "79fbafed02cfdbe85ef3fd06c7f4bc2cbcba0177e61b765264853d4253b21744"
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "3.9.0"
|
|
||||||
pool:
|
pool:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -784,10 +773,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: pubspec_parse
|
name: pubspec_parse
|
||||||
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
|
sha256: c799b721d79eb6ee6fa56f00c04b472dcd44a30d258fac2174a6ec57302678f8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.3"
|
version: "1.3.0"
|
||||||
settings_ui:
|
settings_ui:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
@ -808,18 +797,18 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_android
|
name: shared_preferences_android
|
||||||
sha256: "1ee8bf911094a1b592de7ab29add6f826a7331fb854273d55918693d5364a1f2"
|
sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.2"
|
version: "2.2.3"
|
||||||
shared_preferences_foundation:
|
shared_preferences_foundation:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_foundation
|
name: shared_preferences_foundation
|
||||||
sha256: "7708d83064f38060c7b39db12aefe449cb8cdc031d6062280087bc4cdb988f5c"
|
sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.5"
|
version: "2.4.0"
|
||||||
shared_preferences_linux:
|
shared_preferences_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -880,10 +869,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shelf_web_socket
|
name: shelf_web_socket
|
||||||
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
|
sha256: "073c147238594ecd0d193f3456a5fe91c4b0abbcc68bf5cd95b36c4e194ac611"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "2.0.0"
|
||||||
sky_engine:
|
sky_engine:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -981,26 +970,26 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: test
|
name: test
|
||||||
sha256: "7ee446762c2c50b3bd4ea96fe13ffac69919352bd3b4b17bac3f3465edc58073"
|
sha256: d11b55850c68c1f6c0cf00eabded4e66c4043feaf6c0d7ce4a36785137df6331
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.25.2"
|
version: "1.25.5"
|
||||||
test_api:
|
test_api:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_api
|
name: test_api
|
||||||
sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f"
|
sha256: "2419f20b0c8677b2d67c8ac4d1ac7372d862dc6c460cdbb052b40155408cd794"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.0"
|
version: "0.7.1"
|
||||||
test_core:
|
test_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: test_core
|
name: test_core
|
||||||
sha256: "2bc4b4ecddd75309300d8096f781c0e3280ca1ef85beda558d33fcbedc2eead4"
|
sha256: "4d070a6bc36c1c4e89f20d353bfd71dc30cdf2bd0e14349090af360a029ab292"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.6.2"
|
||||||
timing:
|
timing:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1021,26 +1010,26 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e"
|
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.6"
|
version: "6.3.0"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775"
|
sha256: ceb2625f0c24ade6ef6778d1de0b2e44f2db71fded235eb52295247feba8c5cf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.3.1"
|
version: "6.3.3"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5"
|
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.5"
|
version: "6.3.0"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1053,10 +1042,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_macos
|
name: url_launcher_macos
|
||||||
sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234
|
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.2.0"
|
||||||
url_launcher_platform_interface:
|
url_launcher_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1093,18 +1082,18 @@ packages:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: very_good_analysis
|
name: very_good_analysis
|
||||||
sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8"
|
sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.1.0"
|
version: "6.0.0"
|
||||||
vm_service:
|
vm_service:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vm_service
|
name: vm_service
|
||||||
sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec"
|
sha256: "7475cb4dd713d57b6f7464c0e13f06da0d535d8b2067e188962a59bac2cf280b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "14.2.1"
|
version: "14.2.2"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1121,14 +1110,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.1"
|
version: "0.5.1"
|
||||||
|
web_socket:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: web_socket
|
||||||
|
sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.5"
|
||||||
web_socket_channel:
|
web_socket_channel:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
|
sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.5"
|
version: "3.0.0"
|
||||||
webdriver:
|
webdriver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1157,10 +1154,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "0a989dc7ca2bb51eac91e8fd00851297cfffd641aa7538b165c62637ca0eaa4a"
|
sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.4.0"
|
version: "5.5.1"
|
||||||
xdg_directories:
|
xdg_directories:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1186,5 +1183,5 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.2"
|
version: "3.1.2"
|
||||||
sdks:
|
sdks:
|
||||||
dart: ">=3.3.0 <4.0.0"
|
dart: ">=3.4.0 <4.0.0"
|
||||||
flutter: ">=3.19.0"
|
flutter: ">=3.22.0"
|
||||||
|
|
|
@ -28,7 +28,7 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_slidable: ^3.0.0
|
flutter_slidable: ^3.0.0
|
||||||
flutter_speed_dial: ^7.0.0
|
flutter_speed_dial: ^7.0.0
|
||||||
flutter_tesseract_ocr: ^0.4.23
|
flutter_tesseract_ocr: ^0.4.26
|
||||||
fluttertoast: ^8.2.4
|
fluttertoast: ^8.2.4
|
||||||
grouped_list: ^5.1.2
|
grouped_list: ^5.1.2
|
||||||
intl: any
|
intl: any
|
||||||
|
@ -51,13 +51,12 @@ dev_dependencies:
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
flutter_lints: ^3.0.0
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
integration_test:
|
integration_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
test: ^1.24.6
|
test: ^1.24.6
|
||||||
very_good_analysis: ^5.1.0
|
very_good_analysis: ^6.0.0
|
||||||
yaml: ^3.1.2
|
yaml: ^3.1.2
|
||||||
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
|
|
6
sonar-project.properties
Normal file
6
sonar-project.properties
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
sonar.projectKey=prasule
|
||||||
|
sonar.sources=lib,pubspec.yaml
|
||||||
|
sonar.tests=integration_test,test_driver
|
||||||
|
sonar.dart.analyzer.mode=FLUTTER
|
||||||
|
sonar.dart.analyzer.options.override=true
|
||||||
|
sonar.host.url=https://sq.mnau.xyz
|
3
test_driver/integration_test.dart
Normal file
3
test_driver/integration_test.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
import 'package:integration_test/integration_test_driver.dart';
|
||||||
|
|
||||||
|
Future<void> main() => integrationDriver();
|
Loading…
Reference in a new issue