cpd_app_gruppe_finanzplaner/lib/account/account_detail.dart

576 lines
20 KiB
Dart
Raw Normal View History

2023-06-15 00:55:02 +02:00
import 'dart:convert';
2023-06-18 03:00:17 +02:00
import 'package:awesome_dialog/awesome_dialog.dart';
2023-06-15 00:55:02 +02:00
import 'package:circular_seek_bar/circular_seek_bar.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter_neumorphic/flutter_neumorphic.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tests/saving_tips.dart';
2023-06-17 03:09:31 +02:00
import 'package:tests/theme/theme_constants.dart';
2023-06-15 00:55:02 +02:00
import 'package:tests/transaction/transaction_dialog.dart';
import 'package:tests/transaction/transaction.dart';
2023-06-16 04:27:34 +02:00
import '../main.dart';
2023-06-15 00:55:02 +02:00
import 'account.dart';
import '../chart/expense_chart.dart';
import '../chart/expense_data.dart';
import 'package:tab_indicator_styler/tab_indicator_styler.dart';
class AccountDetailPage extends StatefulWidget {
final Account account;
final Function(Account) updateAccountBalance;
const AccountDetailPage(
{super.key, required this.account, required this.updateAccountBalance});
@override
AccountDetailPageState createState() => AccountDetailPageState();
}
class AccountDetailPageState extends State<AccountDetailPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Transaction> transactions = [];
List<Transaction> incomeTransactions = [];
List<Transaction> expenseTransactions = [];
List<ExpenseData> expenseData = [];
2023-06-17 03:09:31 +02:00
String _selectedCurrency = "";
2023-06-15 00:55:02 +02:00
2023-06-16 04:27:34 +02:00
Future<String> getCurrencyFromSharedPreferences(String key) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
2023-06-17 03:09:31 +02:00
if (prefs.getString(key) == "Euro") {
_selectedCurrency = "";
2023-06-16 04:27:34 +02:00
}
2023-06-17 03:09:31 +02:00
if (prefs.getString(key) == "Dollar") {
_selectedCurrency = r"$";
2023-06-16 04:27:34 +02:00
}
2023-06-17 03:09:31 +02:00
if (prefs.getString(key) == "CHF") {
_selectedCurrency = "CHF";
2023-06-16 04:27:34 +02:00
}
return prefs.getString(key) ?? 'Euro';
}
2023-06-17 03:09:31 +02:00
2023-06-15 00:55:02 +02:00
@override
void initState() {
super.initState();
_tabController = TabController(length: 3, vsync: this);
2023-06-16 04:27:34 +02:00
getCurrencyFromSharedPreferences("currency").then((value) {
2023-06-17 03:09:31 +02:00
setState(() {});
2023-06-16 04:27:34 +02:00
});
2023-06-17 03:09:31 +02:00
loadMaxProgress();
2023-06-15 00:55:02 +02:00
loadTransactions();
}
2023-06-17 03:09:31 +02:00
void loadMaxProgress() async {
double storedMaxProgress = await getMaxProgress();
setState(() {
maxProgress = storedMaxProgress;
});
}
2023-06-15 00:55:02 +02:00
@override
void dispose() {
_tabController.dispose();
_budgetController.dispose();
super.dispose();
}
void loadTransactions() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String? transactionsJson = prefs.getString(widget.account.name);
if (transactionsJson != null) {
List<dynamic> decodedJson = jsonDecode(transactionsJson);
setState(() {
transactions =
decodedJson.map((json) => Transaction.fromJson(json)).toList();
incomeTransactions = transactions
.where((transaction) => !transaction.isExpense)
.toList();
expenseTransactions =
transactions.where((transaction) => transaction.isExpense).toList();
expenseData = calculateMonthlyExpenses();
});
}
}
void saveTransactions() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> transactionsJsonList = transactions
.map((transaction) => json.encode(transaction.toJson()))
.toList();
prefs.setString(widget.account.name, jsonEncode(transactionsJsonList));
}
void addTransaction(Transaction transaction) {
setState(() {
transactions.add(transaction);
if (transaction.isExpense) {
widget.account.balance -= transaction.amount;
expenseTransactions.add(transaction);
} else {
widget.account.balance += transaction.amount;
incomeTransactions.add(transaction);
}
saveTransactions();
widget.updateAccountBalance(widget.account);
expenseData = calculateMonthlyExpenses();
});
}
void deleteTransaction(Transaction transaction) {
setState(() {
transactions.remove(transaction);
if (transaction.isExpense) {
widget.account.balance += transaction.amount;
expenseTransactions.remove(transaction);
} else {
widget.account.balance -= transaction.amount;
incomeTransactions.remove(transaction);
}
saveTransactions();
widget.updateAccountBalance(widget.account);
expenseData = calculateMonthlyExpenses();
});
}
List<ExpenseData> calculateMonthlyExpenses() {
Map<String, double> monthlyExpenses = {};
for (var transaction in expenseTransactions) {
String month = DateFormat('yyyy-MM').format(transaction.date);
monthlyExpenses[month] =
(monthlyExpenses[month] ?? 0) + transaction.amount;
}
List<ExpenseData> expenseData = [];
monthlyExpenses.forEach((month, amount) {
expenseData.add(ExpenseData(month: month, amount: amount));
});
return expenseData;
}
final _budgetController = TextEditingController();
double progress = 0;
2023-06-17 03:09:31 +02:00
double submitBudget() {
if (_budgetController.text.isNotEmpty) {
double budgetValue = double.parse(_budgetController.text);
SharedPreferences.getInstance().then((prefs) {
prefs.setDouble("maxProgress", budgetValue);
});
setState(() {
maxProgress = budgetValue;
});
return budgetValue;
}
return 0;
}
Future<double> getMaxProgress() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
double maxProgress = prefs.getDouble('maxProgress') ?? 1000;
return maxProgress;
2023-06-15 00:55:02 +02:00
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
actions: [
Padding(
padding: const EdgeInsets.only(right: 10.0),
child: IconButton(
2023-06-17 03:09:31 +02:00
icon: Icon(
2023-06-15 00:55:02 +02:00
Icons.info,
2023-06-17 03:09:31 +02:00
color: Theme.of(context).unselectedWidgetColor,
2023-06-15 00:55:02 +02:00
),
onPressed: () {
showDialog(
context: context,
builder: (BuildContext context) => SavingsTipsDialog(),
);
},
),
),
],
toolbarHeight: 80,
title: Text(
widget.account.name,
2023-06-17 03:09:31 +02:00
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context).brightness == Brightness.dark
? Colors.white70
: Colors.black87),
2023-06-15 00:55:02 +02:00
),
centerTitle: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15),
),
),
leading: Padding(
padding: const EdgeInsets.only(left: 16.0),
child: NeumorphicButton(
onPressed: () {
2023-06-17 03:09:31 +02:00
Navigator.pop(context);
Navigator.pushReplacement(
2023-06-16 04:27:34 +02:00
context,
MaterialPageRoute(builder: (context) => const HomePage()),
);
2023-06-15 00:55:02 +02:00
},
style: NeumorphicStyle(
shape: NeumorphicShape.flat,
boxShape: const NeumorphicBoxShape.circle(),
depth: 6,
2023-06-17 03:09:31 +02:00
shadowLightColor: Theme.of(context).brightness == Brightness.light
? const NeumorphicStyle().shadowLightColor
: Theme.of(context).shadowColor,
shadowDarkColor: Theme.of(context).brightness == Brightness.dark
? const NeumorphicStyle().shadowDarkColor
: grey400,
color: Theme.of(context).brightness == Brightness.light
? grey200
: grey800,
2023-06-15 00:55:02 +02:00
intensity: 0.9,
),
2023-06-17 03:09:31 +02:00
padding: const EdgeInsets.all(8),
child: Icon(Icons.arrow_back,
color: Theme.of(context).unselectedWidgetColor),
2023-06-15 00:55:02 +02:00
),
),
),
body: Column(
children: [
TabBar(
controller: _tabController,
2023-06-17 03:09:31 +02:00
labelColor: Theme.of(context).cardColor,
2023-06-15 02:23:13 +02:00
labelStyle: const TextStyle(fontSize: 14),
2023-06-17 03:09:31 +02:00
unselectedLabelColor: Theme.of(context).unselectedWidgetColor,
2023-06-15 00:55:02 +02:00
indicator: MaterialIndicator(
height: 4,
2023-06-17 03:09:31 +02:00
color: Theme.of(context).unselectedWidgetColor,
2023-06-15 00:55:02 +02:00
topLeftRadius: 8,
topRightRadius: 8,
horizontalPadding: 45,
tabPosition: TabPosition.bottom,
),
tabs: [
Tab(text: 'income'.tr()),
Tab(text: 'expenditures'.tr()),
Tab(text: 'budget'.tr())
],
),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
_buildTransactionsList(incomeTransactions),
Column(
children: [
Expanded(
child: _buildTransactionsList(expenseTransactions)),
if (expenseTransactions.isNotEmpty) _buildExpenseChart(),
],
),
Column(
children: [
const SizedBox(
height: 15,
),
monthlybudgetplanner(),
Neumorphic(
margin: const EdgeInsets.all(14),
style: NeumorphicStyle(
2023-06-17 03:09:31 +02:00
shadowLightColor:
Theme.of(context).brightness == Brightness.light
? const NeumorphicStyle().shadowLightColor
: Theme.of(context).shadowColor,
shadowDarkColor:
Theme.of(context).brightness == Brightness.dark
? const NeumorphicStyle().shadowDarkColor
: grey400,
color: Theme.of(context).brightness == Brightness.light
? grey200
: grey800,
2023-06-15 00:55:02 +02:00
boxShape: NeumorphicBoxShape.roundRect(
BorderRadius.circular(15)),
depth: -5,
intensity: 0.8,
),
child: TextFormField(
2023-06-17 03:09:31 +02:00
controller: _budgetController,
2023-06-15 00:55:02 +02:00
keyboardType: TextInputType.number,
decoration: InputDecoration(
2023-06-17 03:09:31 +02:00
labelText: 'enterbudget'.tr(),
2023-06-15 00:55:02 +02:00
contentPadding: const EdgeInsets.only(
left: 16, bottom: 8, top: 8),
border: InputBorder.none,
),
),
),
const SizedBox(height: 16),
NeumorphicButton(
onPressed: () {
2023-06-17 03:09:31 +02:00
setState(() {
submitBudget();
});
2023-06-15 00:55:02 +02:00
},
style: NeumorphicStyle(
boxShape: NeumorphicBoxShape.roundRect(
BorderRadius.circular(12),
),
2023-06-17 03:09:31 +02:00
shadowLightColor:
Theme.of(context).brightness == Brightness.light
? const NeumorphicStyle().shadowLightColor
: Theme.of(context).shadowColor,
shadowDarkColor:
Theme.of(context).brightness == Brightness.dark
? const NeumorphicStyle().shadowDarkColor
: grey400,
color: Theme.of(context).brightness == Brightness.light
? grey200
: grey800,
2023-06-15 00:55:02 +02:00
depth: 8,
intensity: 0.9,
),
child: Text('add'.tr()),
),
],
)
],
),
),
],
),
floatingActionButton: NeumorphicButton(
onPressed: () => showDialog(
context: context,
builder: (_) => AddTransactionDialog(addTransaction: addTransaction),
),
style: NeumorphicStyle(
depth: 8,
intensity: 1,
2023-06-17 03:09:31 +02:00
shadowLightColor: Theme.of(context).brightness == Brightness.light
? const NeumorphicStyle().shadowLightColor
: Theme.of(context).shadowColor,
shadowDarkColor: Theme.of(context).brightness == Brightness.dark
? const NeumorphicStyle().shadowDarkColor
: grey400,
color: Theme.of(context).brightness == Brightness.light
? grey200
: grey800,
2023-06-15 00:55:02 +02:00
boxShape: const NeumorphicBoxShape.circle(),
),
2023-06-17 03:09:31 +02:00
child: Icon(
2023-06-15 00:55:02 +02:00
Icons.add,
2023-06-17 03:09:31 +02:00
size: 60,
color: Theme.of(context).brightness == Brightness.light
? Colors.black12
: Colors.white12,
2023-06-15 00:55:02 +02:00
),
),
);
}
final ValueNotifier<double> _valueNotifier = ValueNotifier(0);
2023-06-17 03:09:31 +02:00
double maxProgress = 500;
2023-06-15 00:55:02 +02:00
Widget monthlybudgetplanner() {
return CircularSeekBar(
width: double.infinity,
height: 300,
2023-06-17 03:09:31 +02:00
trackColor: Theme.of(context).brightness == Brightness.light
? Colors.black12
: Colors.white12,
progress: calculateMonthlyExpensesTotal(),
2023-06-15 00:55:02 +02:00
minProgress: 0,
2023-06-17 03:09:31 +02:00
maxProgress: calculateMonthlyExpensesTotal() > maxProgress
? calculateMonthlyExpensesTotal()
: maxProgress,
2023-06-15 00:55:02 +02:00
barWidth: 17,
startAngle: 45,
sweepAngle: 270,
strokeCap: StrokeCap.butt,
progressGradientColors: const [
Colors.lightGreenAccent,
Colors.lightGreen,
Colors.green,
Colors.yellowAccent,
Colors.yellow,
Colors.orangeAccent,
Colors.orange,
Colors.deepOrangeAccent,
Colors.deepOrange,
Colors.redAccent,
Colors.red
],
innerThumbRadius: 0,
innerThumbStrokeWidth: 12,
2023-06-17 03:09:31 +02:00
innerThumbColor: Theme.of(context).cardColor,
2023-06-15 00:55:02 +02:00
outerThumbRadius: 0,
outerThumbStrokeWidth: 15,
dashWidth: 1.5,
dashGap: 1.9,
animation: true,
animDurationMillis: 2200,
curves: Curves.fastOutSlowIn,
valueNotifier: _valueNotifier,
child: Center(
child: ValueListenableBuilder(
2023-06-17 03:09:31 +02:00
valueListenable: _valueNotifier,
builder: (_, double value, __) {
final isMaxProgressReached = value >= maxProgress;
final textStyle = TextStyle(
fontSize: 15,
fontWeight:
isMaxProgressReached ? FontWeight.w600 : FontWeight.w300,
);
return Column(
mainAxisSize: MainAxisSize.min,
children: [
if (isMaxProgressReached)
Text(
"budgetmax".tr(),
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Theme.of(context).cardColor),
)
else
Text(
'${value.round()}$_selectedCurrency',
style: TextStyle(
fontSize: 24, color: Theme.of(context).cardColor),
),
const SizedBox(
height: 10,
),
isMaxProgressReached
? Text('hint'.tr(),
textAlign: TextAlign.center,
style: TextStyle(color: Theme.of(context).cardColor))
: Text('progress'.tr(),
style: TextStyle(color: Theme.of(context).cardColor)),
const SizedBox(height: 10),
isMaxProgressReached
? Container()
: Text(
'Budget: ${maxProgress.round()}$_selectedCurrency',
2023-06-17 03:09:31 +02:00
style: textStyle,
),
],
);
},
),
2023-06-15 00:55:02 +02:00
),
);
}
Widget _buildTransactionsList(List<Transaction> transactionsList) {
2023-06-18 03:00:17 +02:00
return ListView.separated(
2023-06-17 03:09:31 +02:00
physics: const BouncingScrollPhysics(),
2023-06-15 00:55:02 +02:00
itemCount: transactionsList.length,
itemBuilder: (context, index) {
2023-06-21 21:35:27 +02:00
final reversedIndex = transactionsList.length -
1 -
index; // Berechnung des umgekehrten Index
final revtrans = transactionsList[reversedIndex];
final formattedDate =
DateFormat.yMMMMd().add_Hm().format(revtrans.date);
2023-06-18 03:00:17 +02:00
return GestureDetector(
onLongPress: () =>
_showDeleteConfirmationDialog(transactionsList[reversedIndex]),
2023-06-18 03:00:17 +02:00
child: ListTile(
title: Text(
transactionsList[reversedIndex].title,
2023-06-18 03:00:17 +02:00
style: TextStyle(
color: Theme.of(context).brightness == Brightness.dark
? Colors.white54
: Colors.black87),
),
2023-06-21 21:35:27 +02:00
subtitle: Text(
2023-06-18 03:00:17 +02:00
formattedDate,
style: TextStyle(
fontSize: 12,
2023-06-21 21:35:27 +02:00
color: Theme.of(context).brightness == Brightness.dark
? Colors.grey[600]
: Colors.grey[400],
2023-06-18 03:00:17 +02:00
),
),
trailing: Text(
transactionsList[reversedIndex].isExpense
? '-$_selectedCurrency${transactionsList[reversedIndex].amount.toStringAsFixed(2)}'
: '+$_selectedCurrency${transactionsList[reversedIndex].amount.toStringAsFixed(2)}',
2023-06-18 03:00:17 +02:00
style: TextStyle(
color: transactionsList[reversedIndex].isExpense
2023-06-18 03:00:17 +02:00
? Colors.red
: Colors.green,
fontWeight: FontWeight.bold,
),
2023-06-15 00:55:02 +02:00
),
),
);
},
2023-06-18 03:00:17 +02:00
separatorBuilder: (context, index) => Divider(
indent: MediaQuery.of(context).size.width * 0.03,
endIndent: MediaQuery.of(context).size.width * 0.03,
color: Colors.grey,
height: 0.05,
),
2023-06-15 00:55:02 +02:00
);
}
2023-06-18 03:00:17 +02:00
Future _showDeleteConfirmationDialog(Transaction transaction) {
return AwesomeDialog(
dialogBackgroundColor: Theme.of(context).brightness == Brightness.dark
? Colors.grey[800]
: Colors.grey[200],
btnOkText: "Delete".tr(),
btnOkColor: Colors.red,
btnCancelColor: Theme.of(context).brightness == Brightness.dark
? Colors.grey[500]
: Colors.grey[500],
context: context,
animType: AnimType.bottomSlide,
dialogType: DialogType.info,
title: 'deletetransaction'.tr(),
headerAnimationLoop: false,
desc: 'suretransaction'.tr(),
btnCancelOnPress: () {},
btnOkOnPress: () {
deleteTransaction(transaction);
},
).show();
}
2023-06-17 03:09:31 +02:00
double calculateMonthlyExpensesTotal() {
double total = 0;
for (var transaction in expenseTransactions) {
String month = DateFormat('yyyy-MM').format(transaction.date);
if (month == DateFormat('yyyy-MM').format(DateTime.now())) {
total += transaction.amount;
}
}
return total;
}
2023-06-15 00:55:02 +02:00
Widget _buildExpenseChart() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Card(
2023-06-17 03:09:31 +02:00
color: Theme.of(context).brightness == Brightness.light
? grey400
: grey800,
elevation: 6.0,
2023-06-15 00:55:02 +02:00
child: Padding(
padding: const EdgeInsets.all(16.0),
child: MonthlyExpensesChart(data: expenseData),
),
),
);
}
}