feat: add recurring entries #20
19 changed files with 921 additions and 26 deletions
|
@ -4,6 +4,8 @@
|
||||||
- Create a default "no category" category, mainly to store entries with removed categories
|
- Create a default "no category" category, mainly to store entries with removed categories
|
||||||
- Categories now have changeable colors assigned to them
|
- Categories now have changeable colors assigned to them
|
||||||
- Added pie chart for expense/income data per category
|
- Added pie chart for expense/income data per category
|
||||||
|
- Added recurring entries
|
||||||
|
- Fixed wrong default sorting
|
||||||
# 1.0.0-alpha+2
|
# 1.0.0-alpha+2
|
||||||
- Fixed localization issues
|
- Fixed localization issues
|
||||||
- Added graphs for expenses and income per month/year
|
- Added graphs for expenses and income per month/year
|
||||||
|
|
52
lib/api/recurring_entry.dart
Normal file
52
lib/api/recurring_entry.dart
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import 'package:json_annotation/json_annotation.dart';
|
||||||
|
import 'package:prasule/api/category.dart';
|
||||||
|
import 'package:prasule/api/entry_data.dart';
|
||||||
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
|
|
||||||
|
part 'recurring_entry.g.dart';
|
||||||
|
|
||||||
|
/// This is a [WalletSingleEntry] that is automatically recurring
|
||||||
|
@JsonSerializable()
|
||||||
|
class RecurringWalletEntry extends WalletSingleEntry {
|
||||||
|
/// This is a [WalletSingleEntry] that is automatically recurring
|
||||||
|
RecurringWalletEntry({
|
||||||
|
required super.data,
|
||||||
|
required super.type,
|
||||||
|
required super.date,
|
||||||
|
required super.category,
|
||||||
|
required super.id,
|
||||||
|
required this.lastRunDate,
|
||||||
|
required this.recurType,
|
||||||
|
this.repeatAfter = 1,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Connects generated fromJson method
|
||||||
|
factory RecurringWalletEntry.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$RecurringWalletEntryFromJson(json);
|
||||||
|
|
||||||
|
/// Connects generated toJson method
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() => _$RecurringWalletEntryToJson(this);
|
||||||
|
|
||||||
|
/// Last date the recurring entry was added into the single entry list
|
||||||
|
DateTime lastRunDate;
|
||||||
|
|
||||||
|
/// After how many {recurType} should the entry recur
|
||||||
|
int repeatAfter;
|
||||||
|
|
||||||
|
/// What type of recurrence should happen
|
||||||
|
RecurType recurType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// How a [RecurringWalletEntry] should recur
|
||||||
|
@JsonEnum()
|
||||||
|
enum RecurType {
|
||||||
|
/// Will recur every {repeatAfter} months
|
||||||
|
month,
|
||||||
|
|
||||||
|
/// Will recur every {repeatAfter} years
|
||||||
|
year,
|
||||||
|
|
||||||
|
/// Will recur every {repeatAfter} days
|
||||||
|
day
|
||||||
|
}
|
45
lib/api/recurring_entry.g.dart
Normal file
45
lib/api/recurring_entry.g.dart
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'recurring_entry.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
RecurringWalletEntry _$RecurringWalletEntryFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
RecurringWalletEntry(
|
||||||
|
data: EntryData.fromJson(json['data'] as Map<String, dynamic>),
|
||||||
|
type: $enumDecode(_$EntryTypeEnumMap, json['type']),
|
||||||
|
date: DateTime.parse(json['date'] as String),
|
||||||
|
category:
|
||||||
|
WalletCategory.fromJson(json['category'] as Map<String, dynamic>),
|
||||||
|
id: json['id'] as int,
|
||||||
|
lastRunDate: DateTime.parse(json['lastRunDate'] as String),
|
||||||
|
repeatAfter: json['repeatAfter'] as int,
|
||||||
|
recurType: $enumDecode(_$RecurTypeEnumMap, json['recurType']),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$RecurringWalletEntryToJson(
|
||||||
|
RecurringWalletEntry instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'type': _$EntryTypeEnumMap[instance.type]!,
|
||||||
|
'data': instance.data,
|
||||||
|
'date': instance.date.toIso8601String(),
|
||||||
|
'category': instance.category,
|
||||||
|
'id': instance.id,
|
||||||
|
'lastRunDate': instance.lastRunDate.toIso8601String(),
|
||||||
|
'repeatAfter': instance.repeatAfter,
|
||||||
|
'recurType': _$RecurTypeEnumMap[instance.recurType]!,
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$EntryTypeEnumMap = {
|
||||||
|
EntryType.expense: 'expense',
|
||||||
|
EntryType.income: 'income',
|
||||||
|
};
|
||||||
|
|
||||||
|
const _$RecurTypeEnumMap = {
|
||||||
|
RecurType.month: 'month',
|
||||||
|
RecurType.year: 'year',
|
||||||
|
RecurType.day: 'day',
|
||||||
|
};
|
|
@ -1,8 +1,11 @@
|
||||||
import 'package:currency_picker/currency_picker.dart';
|
import 'package:currency_picker/currency_picker.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/walletentry.dart';
|
import 'package:prasule/api/recurring_entry.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/main.dart';
|
||||||
part 'wallet.g.dart';
|
part 'wallet.g.dart';
|
||||||
|
|
||||||
Currency _currencyFromJson(Map<String, dynamic> data) =>
|
Currency _currencyFromJson(Map<String, dynamic> data) =>
|
||||||
|
@ -19,12 +22,16 @@ class Wallet {
|
||||||
required this.currency,
|
required this.currency,
|
||||||
this.categories = const [],
|
this.categories = const [],
|
||||||
this.entries = const [],
|
this.entries = const [],
|
||||||
|
this.recurringEntries = const [],
|
||||||
this.starterBalance = 0,
|
this.starterBalance = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Connects generated fromJson method
|
/// Connects generated fromJson method
|
||||||
factory Wallet.fromJson(Map<String, dynamic> json) => _$WalletFromJson(json);
|
factory Wallet.fromJson(Map<String, dynamic> json) => _$WalletFromJson(json);
|
||||||
|
|
||||||
|
/// A list of all [RecurringWalletEntry]s
|
||||||
|
final List<RecurringWalletEntry> recurringEntries;
|
||||||
|
|
||||||
/// Name of the wallet
|
/// Name of the wallet
|
||||||
final String name;
|
final String name;
|
||||||
|
|
||||||
|
@ -65,6 +72,56 @@ class Wallet {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles adding recurring entries to the entry list
|
||||||
|
void recurEntries() {
|
||||||
|
final n = DateTime.now();
|
||||||
|
for (final ent in recurringEntries) {
|
||||||
|
var m = DateTime(
|
||||||
|
(ent.recurType == RecurType.year)
|
||||||
|
? ent.lastRunDate.year + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.year,
|
||||||
|
(ent.recurType == RecurType.month)
|
||||||
|
? ent.lastRunDate.month + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.month,
|
||||||
|
(ent.recurType == RecurType.day)
|
||||||
|
? ent.lastRunDate.day + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.day,
|
||||||
|
); // create the date after which we should recur
|
||||||
|
|
||||||
|
while (n.isAfter(
|
||||||
|
m,
|
||||||
|
)) {
|
||||||
|
logger.i("Adding recurring entry ${ent.data.name}");
|
||||||
|
recurringEntries[recurringEntries.indexOf(ent)].lastRunDate =
|
||||||
|
m; // update date on recurring entry
|
||||||
|
logger.i(recurringEntries[recurringEntries.indexOf(ent)].lastRunDate);
|
||||||
|
final addedEntry = (recurringEntries[recurringEntries.indexOf(ent)]
|
||||||
|
as WalletSingleEntry)
|
||||||
|
..date = DateTime.now()
|
||||||
|
..id = nextId; // copy entry with today's date and unique ID
|
||||||
|
entries.add(
|
||||||
|
addedEntry,
|
||||||
|
); // add it to entries
|
||||||
|
|
||||||
|
m = DateTime(
|
||||||
|
(ent.recurType == RecurType.year)
|
||||||
|
? ent.lastRunDate.year + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.year,
|
||||||
|
(ent.recurType == RecurType.month)
|
||||||
|
? ent.lastRunDate.month + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.month,
|
||||||
|
(ent.recurType == RecurType.day)
|
||||||
|
? ent.lastRunDate.day + ent.repeatAfter
|
||||||
|
: ent.lastRunDate.day,
|
||||||
|
); // add tne variable again to check if we aren't missing any entries
|
||||||
|
logger.i(
|
||||||
|
"Last recurred date is now on ${DateFormat.yMMMMd().format(m)} (${n.isAfter(m)})");
|
||||||
|
}
|
||||||
|
|
||||||
|
WalletManager.saveWallet(this); // save wallet
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes the specified category.
|
/// Removes the specified category.
|
||||||
///
|
///
|
||||||
/// All [WalletSingleEntry]s will have their category reassigned
|
/// All [WalletSingleEntry]s will have their category reassigned
|
||||||
|
|
|
@ -18,10 +18,16 @@ Wallet _$WalletFromJson(Map<String, dynamic> json) => Wallet(
|
||||||
(e) => WalletSingleEntry.fromJson(e as Map<String, dynamic>))
|
(e) => WalletSingleEntry.fromJson(e as Map<String, dynamic>))
|
||||||
.toList() ??
|
.toList() ??
|
||||||
const [],
|
const [],
|
||||||
|
recurringEntries: (json['recurringEntries'] as List<dynamic>?)
|
||||||
|
?.map((e) =>
|
||||||
|
RecurringWalletEntry.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList() ??
|
||||||
|
const [],
|
||||||
starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0,
|
starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
Map<String, dynamic> _$WalletToJson(Wallet instance) => <String, dynamic>{
|
Map<String, dynamic> _$WalletToJson(Wallet instance) => <String, dynamic>{
|
||||||
|
'recurringEntries': instance.recurringEntries,
|
||||||
'name': instance.name,
|
'name': instance.name,
|
||||||
'categories': instance.categories,
|
'categories': instance.categories,
|
||||||
'entries': instance.entries,
|
'entries': instance.entries,
|
||||||
|
|
|
@ -2,7 +2,7 @@ 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';
|
||||||
|
|
||||||
part 'walletentry.g.dart';
|
part 'wallet_entry.g.dart';
|
||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
part of 'walletentry.dart';
|
part of 'wallet_entry.dart';
|
||||||
|
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
// JsonSerializableGenerator
|
// JsonSerializableGenerator
|
|
@ -81,5 +81,11 @@
|
||||||
"noCategory":"Žádná kategorie",
|
"noCategory":"Žádná kategorie",
|
||||||
"done":"Hotovo",
|
"done":"Hotovo",
|
||||||
"pickColor":"Zvolte barvu",
|
"pickColor":"Zvolte barvu",
|
||||||
"changeDate":"Změnit ze kterého měsíce/roku brát data"
|
"changeDate":"Změnit ze kterého měsíce/roku brát data",
|
||||||
|
"recurringPayments":"Opakující se platby",
|
||||||
|
"monthCounter": "{count, plural, =1{měsíc} few{měsíce} many{měsíců} other{měsíců} }",
|
||||||
|
"dayCounter":"{count, plural, =1{den} few{dny} many{dnů} other{dnů} }",
|
||||||
|
"yearCounter":"{count, plural, =1{rok} few{rok} many{let} other{let} }",
|
||||||
|
"recurEvery":"{count, plural, =1{Opakovat každý} few{Opakovat každé} many{Opakovat každých} other{Opakovat každých}}",
|
||||||
|
"startingWithDate": "počínaje datem"
|
||||||
}
|
}
|
|
@ -161,5 +161,47 @@
|
||||||
"noCategory":"No category",
|
"noCategory":"No category",
|
||||||
"done":"Done",
|
"done":"Done",
|
||||||
"pickColor":"Pick a color",
|
"pickColor":"Pick a color",
|
||||||
"changeDate":"Change what month/year to pick data from"
|
"changeDate":"Change what month/year to pick data from",
|
||||||
|
"recurringPayments":"Recurring payments",
|
||||||
|
"recurEvery":"{count, plural, other{Recur every}}",
|
||||||
|
"@recurEvery":{
|
||||||
|
"description": "Shown when creating recurring entries, ex.: Recur every 2 months",
|
||||||
|
"placeholders": {
|
||||||
|
"count":{
|
||||||
|
"description": "Specifies how many X are being counted",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"monthCounter":"{count, plural, =1{month} other{months} }",
|
||||||
|
"@monthCounter":{
|
||||||
|
"placeholders": {
|
||||||
|
"count":{
|
||||||
|
"description": "Specifies how many months are being counted",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dayCounter":"{count, plural, =1{day} other{days} }",
|
||||||
|
"@dayCounter":{
|
||||||
|
"placeholders": {
|
||||||
|
"count":{
|
||||||
|
"description": "Specifies how many days are being counted",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"yearCounter":"{count, plural, =1{year} other{years} }",
|
||||||
|
"@yearCounter":{
|
||||||
|
"placeholders": {
|
||||||
|
"count":{
|
||||||
|
"description": "Specifies how many years are being counted",
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"startingWithDate": "starting",
|
||||||
|
"@startingWithDate":{
|
||||||
|
"description": "Shown after 'Recur every X Y', e.g. 'Recur every 2 month starting 20th June 2023'"
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,6 +3,7 @@ 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/graph_view.dart';
|
||||||
import 'package:prasule/views/home.dart';
|
import 'package:prasule/views/home.dart';
|
||||||
|
import 'package:prasule/views/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, int page) => Drawer(
|
||||||
|
@ -39,6 +40,22 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
.pushReplacement(platformRoute((p0) => const GraphView()));
|
.pushReplacement(platformRoute((p0) => const GraphView()));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
ListTile(
|
||||||
|
leading: const Icon(Icons.repeat),
|
||||||
|
title: Text(
|
||||||
|
AppLocalizations.of(context).recurringPayments,
|
||||||
|
),
|
||||||
|
selected: page == 3,
|
||||||
|
onTap: () {
|
||||||
|
if (page == 3) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Navigator.of(context).pushReplacement(
|
||||||
|
platformRoute((p0) => const RecurringEntriesView()),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,8 +5,7 @@ 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:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/walletentry.dart';
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
import 'package:prasule/main.dart';
|
|
||||||
import 'package:prasule/util/get_last_date.dart';
|
import 'package:prasule/util/get_last_date.dart';
|
||||||
import 'package:prasule/util/text_color.dart';
|
import 'package:prasule/util/text_color.dart';
|
||||||
|
|
||||||
|
|
|
@ -4,15 +4,15 @@ import 'package:flutter_gen/gen_l10n/app_localizations.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';
|
||||||
import 'package:prasule/api/walletentry.dart';
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
import 'package:prasule/pw/platformbutton.dart';
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
import 'package:prasule/pw/platformfield.dart';
|
import 'package:prasule/pw/platformfield.dart';
|
||||||
|
|
||||||
/// Used when user wants to add new entry
|
/// Used when user wants to add new entry
|
||||||
class CreateEntryView extends StatefulWidget {
|
class CreateSingleEntryView extends StatefulWidget {
|
||||||
/// Used when user wants to add new entry
|
/// Used when user wants to add new entry
|
||||||
const CreateEntryView({required this.w, super.key, this.editEntry});
|
const CreateSingleEntryView({required this.w, super.key, this.editEntry});
|
||||||
|
|
||||||
/// The wallet, where the entry will be saved to
|
/// The wallet, where the entry will be saved to
|
||||||
final Wallet w;
|
final Wallet w;
|
||||||
|
@ -23,10 +23,10 @@ class CreateEntryView extends StatefulWidget {
|
||||||
final WalletSingleEntry? editEntry;
|
final WalletSingleEntry? editEntry;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CreateEntryView> createState() => _CreateEntryViewState();
|
State createState() => _CreateSingleEntryViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CreateEntryViewState extends State<CreateEntryView> {
|
class _CreateSingleEntryViewState extends State<CreateSingleEntryView> {
|
||||||
late WalletSingleEntry newEntry;
|
late WalletSingleEntry newEntry;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
|
342
lib/views/create_recur_entry.dart
Normal file
342
lib/views/create_recur_entry.dart
Normal file
|
@ -0,0 +1,342 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.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/entry_data.dart';
|
||||||
|
import 'package:prasule/api/recurring_entry.dart';
|
||||||
|
import 'package:prasule/api/wallet.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/main.dart';
|
||||||
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
|
import 'package:prasule/pw/platformfield.dart';
|
||||||
|
|
||||||
|
/// Used when user wants to add new entry
|
||||||
|
class CreateRecurringEntryView extends StatefulWidget {
|
||||||
|
/// Used when user wants to add new entry
|
||||||
|
const CreateRecurringEntryView({
|
||||||
|
required this.w,
|
||||||
|
required this.locale,
|
||||||
|
super.key,
|
||||||
|
this.editEntry,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The wallet, where the entry will be saved to
|
||||||
|
final Wallet w;
|
||||||
|
|
||||||
|
/// Entry we want to edit
|
||||||
|
///
|
||||||
|
/// Is null unless we are editing an existing entry
|
||||||
|
final RecurringWalletEntry? editEntry;
|
||||||
|
|
||||||
|
/// Selected locale
|
||||||
|
final String locale;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State createState() => _CreateRecurringEntryViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _CreateRecurringEntryViewState extends State<CreateRecurringEntryView> {
|
||||||
|
late RecurringWalletEntry newEntry;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
if (widget.editEntry != null) {
|
||||||
|
newEntry = widget.editEntry!;
|
||||||
|
} else {
|
||||||
|
newEntry = RecurringWalletEntry(
|
||||||
|
id: widget.w.nextId,
|
||||||
|
data: EntryData(amount: 0, name: ""),
|
||||||
|
type: EntryType.expense,
|
||||||
|
date: DateTime.now(),
|
||||||
|
category: widget.w.categories.first,
|
||||||
|
lastRunDate: DateTime.now(),
|
||||||
|
recurType: RecurType.month,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context).createEntry),
|
||||||
|
),
|
||||||
|
body: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: Center(
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: PlatformField(
|
||||||
|
labelText: AppLocalizations.of(context).name,
|
||||||
|
controller: TextEditingController(text: newEntry.data.name),
|
||||||
|
onChanged: (v) {
|
||||||
|
newEntry.data.name = v;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: PlatformField(
|
||||||
|
labelText: AppLocalizations.of(context).amount,
|
||||||
|
controller: TextEditingController(
|
||||||
|
text: newEntry.data.amount.toString(),
|
||||||
|
),
|
||||||
|
keyboardType:
|
||||||
|
const TextInputType.numberWithOptions(decimal: true),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.allow(
|
||||||
|
RegExp(r'\d+[\.,]{0,1}\d{0,}'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) {
|
||||||
|
logger.i(v);
|
||||||
|
newEntry.data.amount = double.parse(v);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(AppLocalizations.of(context).type),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: DropdownButton<EntryType>(
|
||||||
|
value: newEntry.type,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: EntryType.expense,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).expense,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: EntryType.income,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
||||||
|
child: Text(AppLocalizations.of(context).income),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) return;
|
||||||
|
newEntry.type = v;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(AppLocalizations.of(context).category),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
child: DropdownButton<int>(
|
||||||
|
value: newEntry.category.id,
|
||||||
|
items: List.generate(
|
||||||
|
widget.w.categories.length,
|
||||||
|
(index) => DropdownMenuItem(
|
||||||
|
value: widget.w.categories[index].id,
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
||||||
|
child: Text(
|
||||||
|
widget.w.categories[index].name,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) return;
|
||||||
|
newEntry.category = widget.w.categories
|
||||||
|
.where((element) => element.id == v)
|
||||||
|
.first;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Text(AppLocalizations.of(context).description),
|
||||||
|
const SizedBox(
|
||||||
|
height: 10,
|
||||||
|
),
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
maxWidth: MediaQuery.of(context).size.width * 0.8,
|
||||||
|
maxHeight: 300,
|
||||||
|
),
|
||||||
|
child: PlatformField(
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: null,
|
||||||
|
controller: TextEditingController(
|
||||||
|
text: newEntry.data.description,
|
||||||
|
),
|
||||||
|
onChanged: (v) {
|
||||||
|
newEntry.data.description = v;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.recurEvery(newEntry.repeatAfter),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 50,
|
||||||
|
child: PlatformField(
|
||||||
|
controller: TextEditingController(
|
||||||
|
text: newEntry.repeatAfter.toString(),
|
||||||
|
),
|
||||||
|
inputFormatters: [
|
||||||
|
FilteringTextInputFormatter.digitsOnly,
|
||||||
|
FilteringTextInputFormatter.deny(
|
||||||
|
RegExp(r"^0$"),
|
||||||
|
replacementString: "1",
|
||||||
|
),
|
||||||
|
FilteringTextInputFormatter.deny(
|
||||||
|
r"\d+[\.,]{0,1}\d{0,}",
|
||||||
|
replacementString: "1",
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (s) {
|
||||||
|
final n = int.tryParse(s);
|
||||||
|
if (n == null) return;
|
||||||
|
newEntry.repeatAfter = n;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: DropdownButton<RecurType>(
|
||||||
|
value: newEntry.recurType,
|
||||||
|
items: [
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: RecurType.day,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 176,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.dayCounter(newEntry.repeatAfter),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: RecurType.month,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 176,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.monthCounter(newEntry.repeatAfter),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: RecurType.year,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 176,
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context)
|
||||||
|
.yearCounter(newEntry.repeatAfter),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
onChanged: (v) {
|
||||||
|
if (v == null) return;
|
||||||
|
newEntry.recurType = v;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(AppLocalizations.of(context).startingWithDate),
|
||||||
|
const SizedBox(
|
||||||
|
width: 10,
|
||||||
|
), // TODO: maybe use sizedbox on row with spaceEvenly?
|
||||||
|
PlatformButton(
|
||||||
|
text: DateFormat.yMMMMd(widget.locale)
|
||||||
|
.format(newEntry.lastRunDate),
|
||||||
|
onPressed: () async {
|
||||||
|
final d = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
firstDate: DateTime.now(),
|
||||||
|
lastDate:
|
||||||
|
DateTime.now().add(const Duration(days: 365)),
|
||||||
|
);
|
||||||
|
if (d == null) return;
|
||||||
|
newEntry.lastRunDate = d;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 15,
|
||||||
|
),
|
||||||
|
PlatformButton(
|
||||||
|
text: AppLocalizations.of(context).save,
|
||||||
|
onPressed: () {
|
||||||
|
if (newEntry.data.name.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content:
|
||||||
|
Text(AppLocalizations.of(context).errorEmptyName),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (widget.editEntry != null) {
|
||||||
|
Navigator.of(context).pop(newEntry);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
widget.w.recurringEntries.add(newEntry);
|
||||||
|
WalletManager.saveWallet(widget.w).then(
|
||||||
|
(value) => Navigator.of(context).pop(widget.w),
|
||||||
|
); // TODO loading circle?
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,7 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
import 'package:prasule/main.dart';
|
import 'package:prasule/main.dart';
|
||||||
import 'package:prasule/pw/platformbutton.dart';
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
import 'package:prasule/pw/platformroute.dart';
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
|
|
@ -15,9 +15,10 @@ import 'package:intl/date_symbol_data_local.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.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/recurring_entry.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
import 'package:prasule/api/walletentry.dart';
|
import 'package:prasule/api/wallet_entry.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
import 'package:prasule/main.dart';
|
import 'package:prasule/main.dart';
|
||||||
import 'package:prasule/network/tessdata.dart';
|
import 'package:prasule/network/tessdata.dart';
|
||||||
import 'package:prasule/pw/platformbutton.dart';
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
|
@ -67,6 +68,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedWallet = wallets.first;
|
selectedWallet = wallets.first;
|
||||||
|
selectedWallet!.recurEntries();
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,6 +88,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
// 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!.entries.clear();
|
selectedWallet!.entries.clear();
|
||||||
|
selectedWallet!.recurringEntries.clear();
|
||||||
final random = Random();
|
final random = Random();
|
||||||
for (var i = 0; i < 30; i++) {
|
for (var i = 0; i < 30; i++) {
|
||||||
selectedWallet!.entries.add(
|
selectedWallet!.entries.add(
|
||||||
|
@ -109,7 +112,41 @@ class _HomeViewState extends State<HomeView> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.i(selectedWallet!.entries.length);
|
logger.d(
|
||||||
|
"Created ${selectedWallet!.entries.length} regular entries",
|
||||||
|
);
|
||||||
|
|
||||||
|
for (var i = 0; i < 3; i++) {
|
||||||
|
final type = random.nextInt(3);
|
||||||
|
selectedWallet!.recurringEntries.add(
|
||||||
|
RecurringWalletEntry(
|
||||||
|
data: EntryData(
|
||||||
|
name: "Recurring Entry #${i + 1}",
|
||||||
|
amount: random.nextInt(20000).toDouble(),
|
||||||
|
),
|
||||||
|
type: (random.nextInt(3) > 0)
|
||||||
|
? EntryType.expense
|
||||||
|
: EntryType.income,
|
||||||
|
date: DateTime(
|
||||||
|
2023,
|
||||||
|
random.nextInt(12) + 1,
|
||||||
|
random.nextInt(28) + 1,
|
||||||
|
),
|
||||||
|
category: selectedWallet!.categories[
|
||||||
|
random.nextInt(selectedWallet!.categories.length)],
|
||||||
|
id: selectedWallet!.nextId,
|
||||||
|
lastRunDate: DateTime.now().subtract(
|
||||||
|
Duration(
|
||||||
|
days: (type > 0) ? 3 : 3 * 31,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
recurType: (type > 0) ? RecurType.day : RecurType.month,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.d(
|
||||||
|
"Created ${selectedWallet!.recurringEntries.length} recurring entries");
|
||||||
|
|
||||||
// save and reload
|
// save and reload
|
||||||
WalletManager.saveWallet(selectedWallet!).then((value) {
|
WalletManager.saveWallet(selectedWallet!).then((value) {
|
||||||
|
@ -127,7 +164,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final sw = await Navigator.of(context).push<Wallet>(
|
final sw = await Navigator.of(context).push<Wallet>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (c) => CreateEntryView(w: selectedWallet!),
|
builder: (c) => CreateSingleEntryView(w: selectedWallet!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
if (sw != null) {
|
if (sw != null) {
|
||||||
|
@ -265,8 +302,8 @@ class _HomeViewState extends State<HomeView> {
|
||||||
if (yearA == null) return 0;
|
if (yearA == null) return 0;
|
||||||
final yearB = RegExp(r'\d+').firstMatch(b);
|
final yearB = RegExp(r'\d+').firstMatch(b);
|
||||||
if (yearB == null) return 0;
|
if (yearB == null) return 0;
|
||||||
final compareYears = int.parse(yearA.group(0)!)
|
final compareYears = int.parse(yearB.group(0)!)
|
||||||
.compareTo(int.parse(yearB.group(0)!));
|
.compareTo(int.parse(yearA.group(0)!));
|
||||||
if (compareYears != 0) return compareYears;
|
if (compareYears != 0) return compareYears;
|
||||||
final months = List<String>.generate(
|
final months = List<String>.generate(
|
||||||
12,
|
12,
|
||||||
|
@ -291,7 +328,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
Navigator.of(context)
|
Navigator.of(context)
|
||||||
.push<WalletSingleEntry>(
|
.push<WalletSingleEntry>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (c) => CreateEntryView(
|
builder: (c) => CreateSingleEntryView(
|
||||||
w: selectedWallet!,
|
w: selectedWallet!,
|
||||||
editEntry: element,
|
editEntry: element,
|
||||||
),
|
),
|
||||||
|
@ -372,7 +409,9 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
title: Text(element.data.name),
|
title: Text(element.data.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
"${element.data.amount} ${selectedWallet!.currency.symbol}",
|
NumberFormat.currency(
|
||||||
|
symbol: selectedWallet!.currency.symbol,
|
||||||
|
).format(element.data.amount),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -472,7 +511,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
final newEntry =
|
final newEntry =
|
||||||
await Navigator.of(context).push<WalletSingleEntry>(
|
await Navigator.of(context).push<WalletSingleEntry>(
|
||||||
platformRoute<WalletSingleEntry>(
|
platformRoute<WalletSingleEntry>(
|
||||||
(c) => CreateEntryView(
|
(c) => CreateSingleEntryView(
|
||||||
w: selectedWallet!,
|
w: selectedWallet!,
|
||||||
editEntry: WalletSingleEntry(
|
editEntry: WalletSingleEntry(
|
||||||
data: EntryData(
|
data: EntryData(
|
||||||
|
|
288
lib/views/recurring_view.dart
Normal file
288
lib/views/recurring_view.dart
Normal file
|
@ -0,0 +1,288 @@
|
||||||
|
// ignore_for_file: inference_failure_on_function_invocation
|
||||||
|
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
import 'package:intl/date_symbol_data_local.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prasule/api/recurring_entry.dart';
|
||||||
|
import 'package:prasule/api/wallet.dart';
|
||||||
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
|
import 'package:prasule/pw/platformdialog.dart';
|
||||||
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
import 'package:prasule/util/drawer.dart';
|
||||||
|
import 'package:prasule/util/text_color.dart';
|
||||||
|
import 'package:prasule/views/create_recur_entry.dart';
|
||||||
|
import 'package:prasule/views/settings/settings.dart';
|
||||||
|
import 'package:prasule/views/setup.dart';
|
||||||
|
|
||||||
|
/// Used to set up recurring entries
|
||||||
|
class RecurringEntriesView extends StatefulWidget {
|
||||||
|
/// Used to set up recurring entries
|
||||||
|
const RecurringEntriesView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<RecurringEntriesView> createState() => _RecurringEntriesViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
||||||
|
Wallet? selectedWallet;
|
||||||
|
List<Wallet> wallets = [];
|
||||||
|
|
||||||
|
late String locale;
|
||||||
|
@override
|
||||||
|
void didChangeDependencies() {
|
||||||
|
super.didChangeDependencies();
|
||||||
|
locale = Localizations.localeOf(context).languageCode;
|
||||||
|
initializeDateFormatting(Localizations.localeOf(context).languageCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
loadWallet();
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
drawer: makeDrawer(context, 3),
|
||||||
|
appBar: AppBar(
|
||||||
|
title: DropdownButton<int>(
|
||||||
|
value:
|
||||||
|
(selectedWallet == null) ? -1 : wallets.indexOf(selectedWallet!),
|
||||||
|
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();
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
} else if (value == AppLocalizations.of(context).about) {
|
||||||
|
showAboutDialog(
|
||||||
|
context: context,
|
||||||
|
applicationLegalese: AppLocalizations.of(context).license,
|
||||||
|
applicationName: "Prašule",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
shape: const CircleBorder(),
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).push(
|
||||||
|
platformRoute(
|
||||||
|
(p0) => CreateRecurringEntryView(
|
||||||
|
w: selectedWallet!,
|
||||||
|
locale: locale,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
body: Center(
|
||||||
|
child: SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
child: (selectedWallet == null)
|
||||||
|
? const Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 40,
|
||||||
|
height: 40,
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: (selectedWallet!.recurringEntries.isEmpty)
|
||||||
|
? Column(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).noEntries,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
AppLocalizations.of(context).noEntriesSub,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
itemBuilder: (c, i) => Slidable(
|
||||||
|
endActionPane: ActionPane(
|
||||||
|
motion: const ScrollMotion(),
|
||||||
|
children: [
|
||||||
|
SlidableAction(
|
||||||
|
onPressed: (c) {
|
||||||
|
Navigator.of(context)
|
||||||
|
.push<RecurringWalletEntry>(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (c) => CreateRecurringEntryView(
|
||||||
|
w: selectedWallet!,
|
||||||
|
locale: locale,
|
||||||
|
editEntry:
|
||||||
|
selectedWallet!.recurringEntries[i],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.then(
|
||||||
|
(editedEntry) {
|
||||||
|
if (editedEntry == null) return;
|
||||||
|
selectedWallet!.entries.remove(
|
||||||
|
selectedWallet!.recurringEntries[i],
|
||||||
|
);
|
||||||
|
selectedWallet!.entries.add(editedEntry);
|
||||||
|
WalletManager.saveWallet(selectedWallet!);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.onSecondary,
|
||||||
|
icon: Icons.edit,
|
||||||
|
),
|
||||||
|
SlidableAction(
|
||||||
|
backgroundColor:
|
||||||
|
Theme.of(context).colorScheme.error,
|
||||||
|
foregroundColor:
|
||||||
|
Theme.of(context).colorScheme.onError,
|
||||||
|
icon: Icons.delete,
|
||||||
|
onPressed: (c) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (cx) => PlatformDialog(
|
||||||
|
title:
|
||||||
|
AppLocalizations.of(context).sureDialog,
|
||||||
|
content: Text(
|
||||||
|
AppLocalizations.of(context).deleteSure,
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
PlatformButton(
|
||||||
|
text: AppLocalizations.of(context).yes,
|
||||||
|
onPressed: () {
|
||||||
|
selectedWallet!.recurringEntries
|
||||||
|
.remove(
|
||||||
|
selectedWallet!.recurringEntries[i],
|
||||||
|
);
|
||||||
|
WalletManager.saveWallet(
|
||||||
|
selectedWallet!,
|
||||||
|
);
|
||||||
|
Navigator.of(cx).pop();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
PlatformButton(
|
||||||
|
text: AppLocalizations.of(context).no,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(cx).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
child: ListTile(
|
||||||
|
leading: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
color: selectedWallet!
|
||||||
|
.recurringEntries[i].category.color,
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: Icon(
|
||||||
|
selectedWallet!
|
||||||
|
.recurringEntries[i].category.icon,
|
||||||
|
color: selectedWallet!
|
||||||
|
.recurringEntries[i].category.color
|
||||||
|
.calculateTextColor(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Text(
|
||||||
|
selectedWallet!.recurringEntries[i].data.name,
|
||||||
|
),
|
||||||
|
subtitle: Text(
|
||||||
|
NumberFormat.currency(
|
||||||
|
symbol: selectedWallet!.currency.symbol,
|
||||||
|
).format(
|
||||||
|
selectedWallet!.recurringEntries[i].data.amount,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
itemCount: selectedWallet!.recurringEntries.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_iconpicker/flutter_iconpicker.dart';
|
import 'package:flutter_iconpicker/flutter_iconpicker.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
import 'package:prasule/main.dart';
|
import 'package:prasule/main.dart';
|
||||||
import 'package:prasule/pw/platformbutton.dart';
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
import 'package:prasule/pw/platformdialog.dart';
|
import 'package:prasule/pw/platformdialog.dart';
|
||||||
|
|
|
@ -10,7 +10,7 @@ import 'package:flutter_iconpicker/flutter_iconpicker.dart';
|
||||||
import 'package:introduction_screen/introduction_screen.dart';
|
import 'package:introduction_screen/introduction_screen.dart';
|
||||||
import 'package:prasule/api/category.dart';
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/wallet_manager.dart';
|
||||||
import 'package:prasule/pw/platformbutton.dart';
|
import 'package:prasule/pw/platformbutton.dart';
|
||||||
import 'package:prasule/pw/platformdialog.dart';
|
import 'package:prasule/pw/platformdialog.dart';
|
||||||
import 'package:prasule/pw/platformfield.dart';
|
import 'package:prasule/pw/platformfield.dart';
|
||||||
|
|
Loading…
Reference in a new issue