feat: creating debt scenarios
also changed way of loading selected wallet
This commit is contained in:
parent
300f359070
commit
ef52caa836
20 changed files with 946 additions and 478 deletions
|
@ -1,6 +1,8 @@
|
|||
# newVersion
|
||||
# 2.0.0
|
||||
- Upgrade dependencies
|
||||
- Use less `await`s in WalletManager class
|
||||
- Added debt management
|
||||
- Changed android app ID
|
||||
|
||||
# 1.1.1
|
||||
- Removed deprecated code
|
||||
|
|
|
@ -37,6 +37,8 @@ class DebtEntry {
|
|||
String name;
|
||||
|
||||
/// List of people who payed
|
||||
@JsonKey(defaultValue: DebtPerson.unknownPerson)
|
||||
List<DebtPerson> whoPayed;
|
||||
@JsonKey(defaultValue: _defaultDebtPayers)
|
||||
final List<DebtPerson> whoPayed;
|
||||
}
|
||||
|
||||
List<DebtPerson> _defaultDebtPayers() => [DebtPerson.unknownPerson()];
|
||||
|
|
|
@ -19,7 +19,7 @@ DebtEntry _$DebtEntryFromJson(Map<String, dynamic> json) {
|
|||
whoPayed: (json['whoPayed'] as List<dynamic>?)
|
||||
?.map((e) => DebtPerson.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
DebtPerson.unknownPerson(),
|
||||
_defaultDebtPayers(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ part 'debt_person.g.dart';
|
|||
/// 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});
|
||||
DebtPerson({required this.id, required this.name, this.bankAccount});
|
||||
|
||||
/// Default [DebtPerson] instance for json_serializable
|
||||
factory DebtPerson.unknownPerson() => DebtPerson(id: -1, name: "Unknown");
|
||||
|
@ -25,4 +25,9 @@ class DebtPerson {
|
|||
/// Identifier that the user will see
|
||||
@JsonKey(defaultValue: "Unknown")
|
||||
String name;
|
||||
|
||||
/// Person's bank account
|
||||
///
|
||||
/// Used to generate a QR code payment
|
||||
String? bankAccount;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ DebtPerson _$DebtPersonFromJson(Map<String, dynamic> json) {
|
|||
return DebtPerson(
|
||||
id: (json['id'] as num).toInt(),
|
||||
name: json['name'] as String? ?? 'Unknown',
|
||||
bankAccount: json['bankAccount'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -22,4 +23,5 @@ Map<String, dynamic> _$DebtPersonToJson(DebtPerson instance) =>
|
|||
<String, dynamic>{
|
||||
'id': instance.id,
|
||||
'name': instance.name,
|
||||
'bankAccount': instance.bankAccount,
|
||||
};
|
||||
|
|
|
@ -11,8 +11,8 @@ class DebtScenario {
|
|||
required this.id,
|
||||
required this.name,
|
||||
required this.isArchived,
|
||||
required this.people,
|
||||
this.entries = const [],
|
||||
this.people = const [],
|
||||
});
|
||||
|
||||
/// Generates a class instance from a Map
|
||||
|
@ -22,7 +22,7 @@ class DebtScenario {
|
|||
/// Converts the data in this instance into a Map
|
||||
Map<String, dynamic> toJson() => _$DebtScenarioToJson(this);
|
||||
|
||||
/// Unique identified
|
||||
/// Unique identifier
|
||||
@JsonKey(disallowNullValue: true)
|
||||
final int id;
|
||||
|
||||
|
@ -36,11 +36,29 @@ class DebtScenario {
|
|||
|
||||
/// All entries
|
||||
@JsonKey(defaultValue: [])
|
||||
List<DebtEntry> entries;
|
||||
final List<DebtEntry> entries;
|
||||
|
||||
/// All people
|
||||
@JsonKey(defaultValue: _defaultPeopleList)
|
||||
List<DebtPerson> people;
|
||||
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()];
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:flutter/material.dart';
|
|||
import 'package:intl/intl.dart';
|
||||
import 'package:json_annotation/json_annotation.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/recurring_entry.dart';
|
||||
import 'package:prasule/api/wallet_entry.dart';
|
||||
|
@ -31,6 +32,7 @@ class Wallet {
|
|||
this.categories = const [],
|
||||
this.entries = const [],
|
||||
this.recurringEntries = const [],
|
||||
this.debts = const [],
|
||||
this.starterBalance = 0,
|
||||
});
|
||||
|
||||
|
@ -53,6 +55,10 @@ class Wallet {
|
|||
@JsonKey(defaultValue: [])
|
||||
final List<WalletSingleEntry> entries;
|
||||
|
||||
/// List of user's [DebtScenario]s
|
||||
@JsonKey(defaultValue: [])
|
||||
final List<DebtScenario> debts;
|
||||
|
||||
/// The starting balance of the wallet
|
||||
///
|
||||
/// Used to calculate current balance
|
||||
|
@ -88,6 +94,16 @@ class Wallet {
|
|||
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
|
||||
void recurEntries() {
|
||||
final n = DateTime.now();
|
||||
|
|
|
@ -25,6 +25,10 @@ Wallet _$WalletFromJson(Map<String, dynamic> json) => Wallet(
|
|||
RecurringWalletEntry.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
debts: (json['debts'] as List<dynamic>?)
|
||||
?.map((e) => DebtScenario.fromJson(e as Map<String, dynamic>))
|
||||
.toList() ??
|
||||
[],
|
||||
starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0,
|
||||
);
|
||||
|
||||
|
@ -33,6 +37,7 @@ Map<String, dynamic> _$WalletToJson(Wallet instance) => <String, dynamic>{
|
|||
'name': instance.name,
|
||||
'categories': instance.categories,
|
||||
'entries': instance.entries,
|
||||
'debts': instance.debts,
|
||||
'starterBalance': instance.starterBalance,
|
||||
'currency': instance.currency,
|
||||
};
|
||||
|
|
|
@ -16,7 +16,7 @@ import 'package:prasule/main.dart';
|
|||
/// Used for [Wallet]-managing operations
|
||||
class WalletManager {
|
||||
/// Currently selected wallet
|
||||
static Wallet? selectedWallet;
|
||||
static late Wallet selectedWallet;
|
||||
|
||||
/// Path to the directory with wallet files
|
||||
///
|
||||
|
|
|
@ -122,5 +122,14 @@
|
|||
"incomePerYearCategory":"Příjmy podle kategorie za rok {year}",
|
||||
"incomePerMonthCategory":"Příjmy podle kategorie za měsíc {monthYear}",
|
||||
"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"
|
||||
}
|
|
@ -313,5 +313,14 @@
|
|||
}
|
||||
},
|
||||
"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"
|
||||
}
|
|
@ -10,26 +10,26 @@ import 'package:flutter/services.dart';
|
|||
import 'package:prasule/pw/platformwidget.dart';
|
||||
|
||||
/// A [PlatformWidget] implementation of a text field
|
||||
class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
||||
const PlatformField({
|
||||
super.key,
|
||||
this.controller,
|
||||
this.enabled,
|
||||
this.labelText,
|
||||
this.obscureText = false,
|
||||
this.autocorrect = false,
|
||||
this.keyboardType,
|
||||
this.inputFormatters = const [],
|
||||
this.onChanged,
|
||||
this.autofillHints,
|
||||
this.textStyle,
|
||||
this.textAlign = TextAlign.start,
|
||||
this.maxLines = 1,
|
||||
this.focusNode,
|
||||
this.inputBorder = const OutlineInputBorder(),
|
||||
this.suffix,
|
||||
this.prefix,
|
||||
});
|
||||
class PlatformField extends PlatformWidget<TextFormField, CupertinoTextField> {
|
||||
const PlatformField(
|
||||
{super.key,
|
||||
this.controller,
|
||||
this.enabled,
|
||||
this.labelText,
|
||||
this.obscureText = false,
|
||||
this.autocorrect = false,
|
||||
this.keyboardType,
|
||||
this.inputFormatters = const [],
|
||||
this.onChanged,
|
||||
this.autofillHints,
|
||||
this.textStyle,
|
||||
this.textAlign = TextAlign.start,
|
||||
this.maxLines = 1,
|
||||
this.focusNode,
|
||||
this.inputBorder = const OutlineInputBorder(),
|
||||
this.suffix,
|
||||
this.prefix,
|
||||
this.validator});
|
||||
final TextEditingController? controller;
|
||||
final bool? enabled;
|
||||
final bool obscureText;
|
||||
|
@ -46,9 +46,10 @@ class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
|||
final FocusNode? focusNode;
|
||||
final Widget? suffix;
|
||||
final Widget? prefix;
|
||||
final String? Function(String?)? validator;
|
||||
|
||||
@override
|
||||
TextField createAndroidWidget(BuildContext context) => TextField(
|
||||
TextFormField createAndroidWidget(BuildContext context) => TextFormField(
|
||||
textAlign: textAlign,
|
||||
controller: controller,
|
||||
enabled: enabled,
|
||||
|
@ -67,6 +68,7 @@ class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
|
|||
onChanged: onChanged,
|
||||
autofillHints: autofillHints,
|
||||
maxLines: maxLines,
|
||||
validator: validator,
|
||||
);
|
||||
|
||||
@override
|
||||
|
|
|
@ -5,12 +5,13 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:prasule/pw/platformroute.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/recurring/recurring_view.dart';
|
||||
|
||||
/// 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(
|
||||
children: [
|
||||
const DrawerHeader(child: Text("Prašule")),
|
||||
|
@ -19,9 +20,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
|||
title: Text(
|
||||
AppLocalizations.of(context).home,
|
||||
),
|
||||
selected: page == 1,
|
||||
selected: page == Pages.home,
|
||||
onTap: () {
|
||||
if (page == 1) {
|
||||
if (page == Pages.home) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
@ -34,9 +35,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
|||
title: Text(
|
||||
AppLocalizations.of(context).graphs,
|
||||
),
|
||||
selected: page == 2,
|
||||
selected: page == Pages.graphs,
|
||||
onTap: () {
|
||||
if (page == 2) {
|
||||
if (page == Pages.graphs) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
@ -49,9 +50,9 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
|||
title: Text(
|
||||
AppLocalizations.of(context).recurringPayments,
|
||||
),
|
||||
selected: page == 3,
|
||||
selected: page == Pages.recurringEntries,
|
||||
onTap: () {
|
||||
if (page == 3) {
|
||||
if (page == Pages.recurringEntries) {
|
||||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
@ -60,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
|
||||
}
|
||||
|
|
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -31,7 +31,6 @@ class GraphView extends StatefulWidget {
|
|||
|
||||
class _GraphViewState extends State<GraphView> {
|
||||
var _selectedDate = DateTime.now();
|
||||
Wallet? selectedWallet;
|
||||
List<Wallet> wallets = [];
|
||||
String? locale;
|
||||
bool yearly = true;
|
||||
|
@ -48,9 +47,8 @@ class _GraphViewState extends State<GraphView> {
|
|||
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(
|
||||
final entriesForRange = WalletManager.selectedWallet.entries.where(
|
||||
(element) =>
|
||||
((!yearly)
|
||||
? element.date.month == _selectedDate.month &&
|
||||
|
@ -80,9 +78,9 @@ class _GraphViewState extends State<GraphView> {
|
|||
);
|
||||
return;
|
||||
}
|
||||
selectedWallet = wallets.first;
|
||||
WalletManager.selectedWallet = wallets.first;
|
||||
availableYears.clear();
|
||||
for (final entry in selectedWallet!.entries) {
|
||||
for (final entry in WalletManager.selectedWallet.entries) {
|
||||
if (!availableYears.any((element) => element.value == entry.date.year)) {
|
||||
availableYears.add(
|
||||
WheelChoice<int>(
|
||||
|
@ -200,11 +198,11 @@ class _GraphViewState extends State<GraphView> {
|
|||
],
|
||||
),
|
||||
title: DropdownButton<int>(
|
||||
value: (selectedWallet == null)
|
||||
? -1
|
||||
: wallets.indexOf(
|
||||
wallets.where((w) => w.name == selectedWallet!.name).first,
|
||||
),
|
||||
value: wallets.indexOf(
|
||||
wallets
|
||||
.where((w) => w.name == WalletManager.selectedWallet.name)
|
||||
.first,
|
||||
),
|
||||
items: [
|
||||
...wallets.map(
|
||||
(e) => DropdownMenuItem(
|
||||
|
@ -230,11 +228,11 @@ class _GraphViewState extends State<GraphView> {
|
|||
);
|
||||
wallets = WalletManager.listWallets();
|
||||
logger.i(wallets.length);
|
||||
selectedWallet = wallets.last;
|
||||
WalletManager.selectedWallet = wallets.last;
|
||||
setState(() {});
|
||||
return;
|
||||
}
|
||||
selectedWallet = wallets[v];
|
||||
WalletManager.selectedWallet = wallets[v];
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
|
@ -253,8 +251,8 @@ class _GraphViewState extends State<GraphView> {
|
|||
),
|
||||
)
|
||||
.then((value) async {
|
||||
selectedWallet =
|
||||
WalletManager.loadWallet(selectedWallet!.name);
|
||||
WalletManager.selectedWallet = WalletManager.loadWallet(
|
||||
WalletManager.selectedWallet.name);
|
||||
final s = await SharedPreferences.getInstance();
|
||||
chartType = s.getInt("monthlygraph") ?? 2;
|
||||
setState(() {});
|
||||
|
@ -266,453 +264,420 @@ class _GraphViewState extends State<GraphView> {
|
|||
),
|
||||
],
|
||||
),
|
||||
drawer: makeDrawer(context, 2),
|
||||
drawer: makeDrawer(context, Pages.graphs),
|
||||
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,
|
||||
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: [
|
||||
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);
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
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: (selectedWallet == null)
|
||||
? const CircularProgressIndicator(
|
||||
strokeWidth: 5,
|
||||
)
|
||||
: SizedBox(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context).size.height,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.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: [
|
||||
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);
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
setState(() {});
|
||||
},
|
||||
),
|
||||
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,
|
||||
),
|
||||
),
|
||||
],
|
||||
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
|
||||
],
|
||||
|
|
|
@ -94,8 +94,10 @@ class _HomeViewState extends State<HomeView> {
|
|||
setState(() {});
|
||||
},
|
||||
child: Scaffold(
|
||||
drawer: makeDrawer(context, 1),
|
||||
drawer: makeDrawer(context, Pages.home),
|
||||
floatingActionButton: SpeedDial(
|
||||
backgroundColor: Theme.of(context).colorScheme.primary,
|
||||
foregroundColor: Theme.of(context).colorScheme.onPrimary,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.all(Radius.circular(16)),
|
||||
),
|
||||
|
|
|
@ -31,6 +31,7 @@ class _InitializationScreenState extends State<InitializationScreen> {
|
|||
.pushReplacement(platformRoute((c) => const SetupView()));
|
||||
return;
|
||||
}
|
||||
WalletManager.selectedWallet = wallets.first;
|
||||
Navigator.of(context)
|
||||
.pushReplacement(platformRoute((c) => const HomeView()));
|
||||
});
|
||||
|
|
|
@ -64,7 +64,7 @@ class _RecurringEntriesViewState extends State<RecurringEntriesView> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
drawer: makeDrawer(context, 3),
|
||||
drawer: makeDrawer(context, Pages.recurringEntries),
|
||||
appBar: AppBar(
|
||||
title: DropdownButton<int>(
|
||||
value: (selectedWallet == null)
|
||||
|
|
|
@ -167,6 +167,9 @@ class _SetupViewState extends State<SetupView> {
|
|||
Navigator.of(context).pop();
|
||||
return;
|
||||
}
|
||||
|
||||
WalletManager.selectedWallet = wallet;
|
||||
|
||||
if (!context.mounted) return;
|
||||
unawaited(
|
||||
Navigator.of(context).pushReplacement(
|
||||
|
|
Loading…
Reference in a new issue