diff --git a/lib/account/account.dart b/lib/account/account.dart new file mode 100644 index 0000000..6570c16 --- /dev/null +++ b/lib/account/account.dart @@ -0,0 +1,26 @@ +import 'dart:convert'; + +class Account { + String name; + double balance; + + Account({ + required this.name, + required this.balance, + }); + + factory Account.fromJson(String json) { + final Map map = jsonDecode(json); + return Account( + name: map['name'], + balance: map['balance'], + ); + } + + Map toJson() { + return { + 'name': name, + 'balance': balance, + }; + } +} \ No newline at end of file diff --git a/lib/account/account_detail.dart b/lib/account/account_detail.dart new file mode 100644 index 0000000..cc837ee --- /dev/null +++ b/lib/account/account_detail.dart @@ -0,0 +1,377 @@ +import 'dart:convert'; +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'; +import 'package:tests/transaction/transaction_dialog.dart'; +import 'package:tests/transaction/transaction.dart'; +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 + with SingleTickerProviderStateMixin { + late TabController _tabController; + List transactions = []; + List incomeTransactions = []; + List expenseTransactions = []; + List expenseData = []; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 3, vsync: this); + loadTransactions(); + } + + @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 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 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 calculateMonthlyExpenses() { + Map monthlyExpenses = {}; + for (var transaction in expenseTransactions) { + String month = DateFormat('yyyy-MM').format(transaction.date); + monthlyExpenses[month] = + (monthlyExpenses[month] ?? 0) + transaction.amount; + } + List expenseData = []; + monthlyExpenses.forEach((month, amount) { + expenseData.add(ExpenseData(month: month, amount: amount)); + }); + return expenseData; + } + + final _budgetController = TextEditingController(); + double progress = 0; + + double submitbudget() { + return progress = double.parse(_budgetController.text.trim()); + } + + @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( + icon: const Icon( + Icons.info, + color: Colors.grey, + ), + onPressed: () { + showDialog( + context: context, + builder: (BuildContext context) => SavingsTipsDialog(), + ); + }, + ), + ), + ], + toolbarHeight: 80, + title: Text( + widget.account.name, + style: const TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Colors.black54, + ), + ), + centerTitle: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + bottomLeft: Radius.circular(15), + bottomRight: Radius.circular(15), + ), + ), + shadowColor: Colors.grey.shade300, + leading: Padding( + padding: const EdgeInsets.only(left: 16.0), + child: NeumorphicButton( + onPressed: () { + Navigator.pop(context); + }, + style: NeumorphicStyle( + shape: NeumorphicShape.flat, + boxShape: const NeumorphicBoxShape.circle(), + depth: 6, + intensity: 0.9, + color: Colors.grey.shade100, + ), + padding: const EdgeInsets.all(10), + child: const Icon(Icons.arrow_back, color: Colors.black38), + ), + ), + ), + body: Column( + children: [ + TabBar( + controller: _tabController, + labelColor: Colors.black, + unselectedLabelColor: Colors.black54, + indicator: MaterialIndicator( + height: 4, + color: Colors.black54, + 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( + color: Colors.grey.shade100, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(15)), + depth: -5, + intensity: 0.8, + ), + child: TextFormField( + keyboardType: TextInputType.number, + decoration: InputDecoration( + labelText: 'enteramount'.tr(), + contentPadding: const EdgeInsets.only( + left: 16, bottom: 8, top: 8), + border: InputBorder.none, + ), + ), + ), + const SizedBox(height: 16), + NeumorphicButton( + onPressed: () { + progress = submitbudget(); + }, + style: NeumorphicStyle( + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + color: Colors.grey.shade100, + 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, + shadowDarkColor: Colors.grey.shade400, + color: Colors.grey.shade100, + boxShape: const NeumorphicBoxShape.circle(), + ), + padding: const EdgeInsets.all(16), + child: const Icon( + Icons.add, + size: 40, + color: Colors.black12, + ), + ), + ); + } + + final ValueNotifier _valueNotifier = ValueNotifier(0); + + Widget monthlybudgetplanner() { + return CircularSeekBar( + width: double.infinity, + height: 300, + trackColor: Colors.black12, + progress: 500, + minProgress: 0, + maxProgress: 800, + 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, + innerThumbColor: Colors.white, + outerThumbRadius: 0, + outerThumbStrokeWidth: 15, + outerThumbColor: Colors.blueAccent, + dashWidth: 1.5, + dashGap: 1.9, + animation: true, + animDurationMillis: 2200, + curves: Curves.fastOutSlowIn, + valueNotifier: _valueNotifier, + child: Center( + child: ValueListenableBuilder( + valueListenable: _valueNotifier, + builder: (_, double value, __) => Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${value.round()}' '€', + ), + Text( + 'progress'.tr(), + ), + ], + )), + ), + ); + } + + Widget _buildTransactionsList(List transactionsList) { + return ListView.builder( + itemCount: transactionsList.length, + itemBuilder: (context, index) { + return ListTile( + title: Text(transactionsList[index].title), + subtitle: Text(transactionsList[index].title), + trailing: Text( + transactionsList[index].isExpense + ? '-\$${transactionsList[index].amount.toStringAsFixed(2)}' + : '+\$${transactionsList[index].amount.toStringAsFixed(2)}', + style: TextStyle( + color: + transactionsList[index].isExpense ? Colors.red : Colors.green, + fontWeight: FontWeight.bold, + ), + ), + onLongPress: () => deleteTransaction(transactionsList[index]), + ); + }, + ); + } + + Widget _buildExpenseChart() { + return Padding( + padding: const EdgeInsets.all(8.0), + child: Card( + elevation: 4.0, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: MonthlyExpensesChart(data: expenseData), + ), + ), + ); + } +} diff --git a/lib/account/account_dialog.dart b/lib/account/account_dialog.dart new file mode 100644 index 0000000..c1415bd --- /dev/null +++ b/lib/account/account_dialog.dart @@ -0,0 +1,161 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_neumorphic/flutter_neumorphic.dart'; +import 'account.dart'; + +class AddAccountDialog extends StatefulWidget { + final Function addAccount; + + const AddAccountDialog({super.key, required this.addAccount}); + + @override + AddAccountDialogState createState() => AddAccountDialogState(); +} + +class AddAccountDialogState extends State { + final _formKey = GlobalKey(); + final _nameController = TextEditingController(); + final _balanceController = TextEditingController(); + + @override + void dispose() { + _nameController.dispose(); + _balanceController.dispose(); + super.dispose(); + } + + void _submitForm() { + if (_formKey.currentState!.validate()) { + String name = _nameController.text.trim(); + double balance = double.parse(_balanceController.text.trim()); + + Account account = Account( + name: name, + balance: balance, + ); + widget.addAccount(account); + + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + title: Text( + 'addaccount'.tr(), + ), + titleTextStyle: const TextStyle( + color: Colors.black54, + fontSize: 20, + ), + content: Form( + key: _formKey, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Neumorphic( + style: NeumorphicStyle( + depth: -5, + intensity: 0.8, + color: Colors.grey.shade100, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + ), + child: TextFormField( + controller: _nameController, + decoration: const InputDecoration( + labelText: 'Name', + border: InputBorder.none, + contentPadding: EdgeInsets.fromLTRB(12, 16, 12, 16), + ), + validator: (value) { + if (value!.isEmpty) { + return 'entername'.tr(); + } + return null; + }, + ), + ), + const SizedBox(height: 16), + Neumorphic( + style: NeumorphicStyle( + depth: -5, + intensity: 0.8, + color: Colors.grey.shade100, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + ), + child: TextFormField( + controller: _balanceController, + decoration: InputDecoration( + labelText: 'balance'.tr(), + border: InputBorder.none, + contentPadding: const EdgeInsets.fromLTRB(12, 16, 12, 16), + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value!.isEmpty) { + return 'enterbalance'.tr(); + } + if (double.tryParse(value) == null) { + return 'entervalidnumber'.tr(); + } + return null; + }, + ), + ), + ], + ), + ), + actions: [ + NeumorphicButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: NeumorphicStyle( + shape: NeumorphicShape.concave, + intensity: 0.9, + depth: 9, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + color: Colors.grey.shade100, + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Text( + 'cancel'.tr(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + NeumorphicButton( + onPressed: _submitForm, + style: NeumorphicStyle( + shape: NeumorphicShape.concave, + intensity: 0.8, + depth: 9, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + color: Colors.grey.shade100, + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Text( + 'add'.tr(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/lib/assets/translations/de-DE.json b/lib/assets/translations/de-DE.json index 57e05ce..5118589 100644 --- a/lib/assets/translations/de-DE.json +++ b/lib/assets/translations/de-DE.json @@ -1,7 +1,7 @@ { "title" : "Finanzplaner", "delete" : "Löschen", - "deleteaccount" : "Account Löschen", + "deleteaccount" : "Account löschen", "sure" : "Sind Sie sicher, dass Sie dieses Konto löschen möchten?", "balance" : "Bilanz", "addaccount" : "Konto hinzufügen", @@ -18,7 +18,9 @@ "enteramount": "Bitte geben Sie einen Betrag ein", "expense": "Ausgabe", "settings": "Einstellungen", - "darkmode": "Dunkel Modus", + "darkmode": "Dark Mode", "language": "Sprache", - "currency": "Währung" + "currency": "Währung", + "budget": "Budgetcheck", + "progress": "von deinem Budget ausgegeben" } \ No newline at end of file diff --git a/lib/assets/translations/en-US.json b/lib/assets/translations/en-US.json index 3453905..af9fb86 100644 --- a/lib/assets/translations/en-US.json +++ b/lib/assets/translations/en-US.json @@ -1,18 +1,18 @@ { "title" : "Financial Planner", "delete" : "Delete", - "deleteaccount" : "Delete Account", + "deleteaccount" : "Delete account", "sure" : "Are you sure you want to delete this account?", "balance" : "Balance", - "addaccount" : "Add Account", + "addaccount" : "Add account", "entername" : "Please enter a name", "enterbalance" : "Please enter a balance", "entervalidnumber" : "Please enter a valid number", "cancel" : "Cancel", "add" : "Add", "income": "Income", - "expenditures": "Expenditures", - "addtrans": "Add Transaction", + "expenditures": "Expenses", + "addtrans": "Add transaction", "entertitle": "Please enter a title", "amount": "Amount", "enteramount": "Please enter an amount", @@ -20,5 +20,7 @@ "settings": "Settings", "darkmode": "Dark Mode", "language": "Language", - "currency": "Currency" + "currency": "Currency", + "budget": "Budget check", + "progress": "of your budget spent" } \ No newline at end of file diff --git a/lib/chart/expense_chart.dart b/lib/chart/expense_chart.dart new file mode 100644 index 0000000..5e66e62 --- /dev/null +++ b/lib/chart/expense_chart.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:syncfusion_flutter_charts/charts.dart'; +import 'expense_data.dart'; + +class MonthlyExpensesChart extends StatelessWidget { + final List data; + + const MonthlyExpensesChart({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 300, + child: SfCartesianChart( + primaryXAxis: CategoryAxis(), + series: [ + ColumnSeries( + dataSource: data, + xValueMapper: (ExpenseData expense, _) => expense.month, + yValueMapper: (ExpenseData expense, _) => expense.amount, + color: Colors.blue, + ), + ], + ), + ); + } +} diff --git a/lib/chart/expense_data.dart b/lib/chart/expense_data.dart new file mode 100644 index 0000000..2268101 --- /dev/null +++ b/lib/chart/expense_data.dart @@ -0,0 +1,6 @@ +class ExpenseData { + final String month; + final double amount; + + ExpenseData({required this.month, required this.amount}); +} diff --git a/lib/main.dart b/lib/main.dart index 69dfe38..20b47a9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,25 +2,31 @@ import 'dart:convert'; import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; -import 'package:intl/intl.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:syncfusion_flutter_charts/charts.dart'; + import 'package:tests/preferences.dart'; -import 'package:tests/saving_tips.dart'; + import 'package:tests/theme/theme_constants.dart'; import 'package:tests/theme/theme_manager.dart'; import "package:easy_localization/easy_localization.dart"; +import 'account/account_dialog.dart'; +import 'account/account.dart'; +import 'account/account_detail.dart'; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - runApp(EasyLocalization( - supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')], - path: 'lib/assets/translations', // <-- change the path of the translation files - fallbackLocale: Locale('en', 'US'), - child: const FinancialPlannerApp() - ),); + runApp( + EasyLocalization( + supportedLocales: const [Locale('en', 'US'), Locale('de', 'DE')], + path: 'lib/assets/translations', + // <-- change the path of the translation files + fallbackLocale: const Locale('en', 'US'), + child: const FinancialPlannerApp()), + ); } + ThemeManager _themeManager = ThemeManager(); class FinancialPlannerApp extends StatelessWidget { @@ -57,32 +63,35 @@ class HomePageState extends State { super.initState(); _themeManager.addListener(themeListener); loadAccounts(); - } + @override void dispose() { _themeManager.removeListener(themeListener); super.dispose(); } - themeListener(){ - if(mounted){ - setState(() { - }); + themeListener() { + if (mounted) { + setState(() {}); } } + Future loadAccounts() async { SharedPreferences prefs = await SharedPreferences.getInstance(); final accountList = prefs.getStringList('accounts') ?? []; setState(() { - accounts = accountList.map((accountJson) => Account.fromJson(accountJson)).toList(); + accounts = accountList + .map((accountJson) => Account.fromJson(accountJson)) + .toList(); }); } Future saveAccounts() async { SharedPreferences prefs = await SharedPreferences.getInstance(); - List accountJsonList = accounts.map((account) => json.encode(account.toJson())).toList(); + List accountJsonList = + accounts.map((account) => json.encode(account.toJson())).toList(); await prefs.setStringList('accounts', accountJsonList); } @@ -126,23 +135,20 @@ class HomePageState extends State { padding: const EdgeInsets.only(right: 16.0, top: 3, bottom: 7), child: NeumorphicButton( onPressed: () async { - - String value = await Navigator.push( + await Navigator.push( context, - MaterialPageRoute(builder: (context) => Settings()), + MaterialPageRoute(builder: (context) => const Settings()), ); - setState(() { - - }); + setState(() {}); }, style: NeumorphicStyle( shape: NeumorphicShape.convex, - boxShape: NeumorphicBoxShape.circle(), + boxShape: const NeumorphicBoxShape.circle(), depth: 6, intensity: 0.9, color: Colors.grey.shade100, ), - child: Icon(Icons.settings, color: Colors.black38), + child: const Icon(Icons.settings, color: Colors.black38), ), ), ], @@ -155,20 +161,19 @@ class HomePageState extends State { onLongPress: () { AwesomeDialog( btnOkText: "Delete".tr(), - btnOkColor: Colors.lightGreen, - btnCancelColor: Colors.grey, - context: context, - animType: AnimType.bottomSlide, - dialogType: DialogType.info, - title: 'deleteaccount'.tr(), - headerAnimationLoop: false, - desc: 'sure'.tr(), - btnCancelOnPress: () {}, - btnOkOnPress: () { - deleteAccount(accounts[index]); - }, - ).show(); - + btnOkColor: Colors.lightGreen, + btnCancelColor: Colors.grey, + context: context, + animType: AnimType.bottomSlide, + dialogType: DialogType.info, + title: 'deleteaccount'.tr(), + headerAnimationLoop: false, + desc: 'sure'.tr(), + btnCancelOnPress: () {}, + btnOkOnPress: () { + deleteAccount(accounts[index]); + }, + ).show(); }, child: Neumorphic( margin: const EdgeInsets.all(16), @@ -177,11 +182,13 @@ class HomePageState extends State { intensity: 1, shadowDarkColor: Colors.grey.shade300, color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)), + boxShape: + NeumorphicBoxShape.roundRect(BorderRadius.circular(15)), ), child: ListTile( title: Text(accounts[index].name), - subtitle: Text('${'balance'.tr()}: \$${accounts[index].balance.toStringAsFixed(2)}'), + subtitle: Text( + '${'balance'.tr()}: \$${accounts[index].balance.toStringAsFixed(2)}'), onTap: () { Navigator.push( context, @@ -216,682 +223,12 @@ class HomePageState extends State { color: Colors.grey.shade100, boxShape: const NeumorphicBoxShape.circle(), ), - child: const Icon(Icons.add,size:60,color: Colors.black12,), - ), - ); - } -} - -class Account { - String name; - double balance; - - Account({ - required this.name, - required this.balance, - }); - - factory Account.fromJson(String json) { - final Map map = jsonDecode(json); - return Account( - name: map['name'], - balance: map['balance'], - ); - } - - Map toJson() { - return { - 'name': name, - 'balance': balance, - }; - } -} - -class AddAccountDialog extends StatefulWidget { - final Function addAccount; - - const AddAccountDialog({super.key, required this.addAccount}); - - @override - AddAccountDialogState createState() => AddAccountDialogState(); -} - -class AddAccountDialogState extends State { - final _formKey = GlobalKey(); - final _nameController = TextEditingController(); - final _balanceController = TextEditingController(); - - @override - void dispose() { - _nameController.dispose(); - _balanceController.dispose(); - super.dispose(); - } - - void _submitForm() { - if (_formKey.currentState!.validate()) { - String name = _nameController.text.trim(); - double balance = double.parse(_balanceController.text.trim()); - - Account account = Account( - name: name, - balance: balance, - ); - widget.addAccount(account); - - Navigator.of(context).pop(); - } - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - title: Text( - 'addaccount'.tr(), - ), - titleTextStyle: TextStyle( - color: Colors.black54, - fontSize: 20, - ), - content: Form( - key: _formKey, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Neumorphic( - style: NeumorphicStyle( - depth: -5, - intensity: 0.8, - color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - ), - child: TextFormField( - controller: _nameController, - decoration: InputDecoration( - labelText: 'Name', - border: InputBorder.none, - contentPadding: EdgeInsets.fromLTRB(12, 16, 12, 16), - ), - validator: (value) { - if (value!.isEmpty) { - return 'entername'.tr(); - } - return null; - }, - ), - ), - SizedBox(height: 16), - Neumorphic( - style: NeumorphicStyle( - depth: -5, - intensity: 0.8, - color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - ), - child: TextFormField( - controller: _balanceController, - decoration: InputDecoration( - labelText: 'balance'.tr(), - border: InputBorder.none, - contentPadding: EdgeInsets.fromLTRB(12, 16, 12, 16), - ), - keyboardType: TextInputType.number, - validator: (value) { - if (value!.isEmpty) { - return 'enterbalance'.tr(); - } - if (double.tryParse(value) == null) { - return 'entervalidnumber'.tr(); - } - return null; - }, - ), - ), - ], - ), - ), - actions: [ - NeumorphicButton( - onPressed: () { - Navigator.of(context).pop(); - }, - style: NeumorphicStyle( - shape: NeumorphicShape.concave, - intensity: 0.9, - depth: 9, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - color: Colors.grey.shade100, - ), - padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Text( - 'cancel'.tr(), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - NeumorphicButton( - onPressed: _submitForm, - style: NeumorphicStyle( - shape: NeumorphicShape.concave, - intensity: 0.8, - depth: 9, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - color: Colors.grey.shade100, - ), - padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Text( - 'add'.tr(), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ); - - } -} - - -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 with SingleTickerProviderStateMixin { - late TabController _tabController; - List transactions = []; - List incomeTransactions = []; - List expenseTransactions = []; - List expenseData = []; - - @override - void initState() { - super.initState(); - _tabController = TabController(length: 2, vsync: this); - loadTransactions(); - } - - @override - void dispose() { - _tabController.dispose(); - super.dispose(); - } - - void loadTransactions() async { - SharedPreferences prefs = await SharedPreferences.getInstance(); - String? transactionsJson = prefs.getString(widget.account.name); - if (transactionsJson != null) { - List 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 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 calculateMonthlyExpenses() { - Map monthlyExpenses = {}; - for (var transaction in expenseTransactions) { - String month = DateFormat('yyyy-MM').format(transaction.date); - monthlyExpenses[month] = (monthlyExpenses[month] ?? 0) + transaction.amount; - } - List expenseData = []; - monthlyExpenses.forEach((month, amount) { - expenseData.add(ExpenseData(month: month, amount: amount)); - }); - return expenseData; - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Colors.transparent, - elevation: 0, - actions: [ - Padding( - padding: EdgeInsets.only(right: 10.0), - child: IconButton( - icon: Icon(Icons.info,color: Colors.grey,), - onPressed: () { - showDialog( - context: context, - builder: (BuildContext context) => SavingsTipsDialog(), - ); - }, - ), - ), - ], - toolbarHeight: 80, - title: Text( - widget.account.name, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Colors.black54, - ), - ), - centerTitle: true, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - bottomLeft: Radius.circular(15), - bottomRight: Radius.circular(15), - ), - ), - shadowColor: Colors.grey.shade300, - leading: Padding( - padding: const EdgeInsets.only(left: 16.0), - child: NeumorphicButton( - onPressed: () { - Navigator.pop(context); - }, - style: NeumorphicStyle( - shape: NeumorphicShape.flat, - boxShape: NeumorphicBoxShape.circle(), - depth: 6, - intensity: 0.9, - color: Colors.grey.shade100, - ), - padding: EdgeInsets.all(10), - child: Icon(Icons.arrow_back, color: Colors.black38), - ), - ), - ), - body: Column( - children: [ - TabBar( - controller: _tabController, - labelColor: Colors.black, - tabs: [ - Tab(text: 'income'.tr()), - Tab(text: 'expenditures'.tr()), - ], - ), - Expanded( - child: TabBarView( - controller: _tabController, - children: [ - _buildTransactionsList(incomeTransactions), - Column( - children: [ - Expanded(child: _buildTransactionsList(expenseTransactions)), - if (expenseTransactions.isNotEmpty) _buildExpenseChart(), - ], - ), - ], - ), - ), - ], - ), - floatingActionButton: NeumorphicButton( - onPressed: () => showDialog( - context: context, - builder: (_) => AddTransactionDialog(addTransaction: addTransaction), - ), - style: NeumorphicStyle( - depth: 8, - intensity: 1, - shadowDarkColor: Colors.grey.shade400, - color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.circle(), - ), - padding: EdgeInsets.all(16), - child: Icon( + child: const Icon( Icons.add, - size: 40, + size: 60, color: Colors.black12, ), ), ); } - - Widget _buildTransactionsList(List transactionsList) { - return ListView.builder( - itemCount: transactionsList.length, - itemBuilder: (context, index) { - return ListTile( - title: Text(transactionsList[index].title), - subtitle: Text(transactionsList[index].title), - trailing: Text( - transactionsList[index].isExpense - ? '-\$${transactionsList[index].amount.toStringAsFixed(2)}' - : '+\$${transactionsList[index].amount.toStringAsFixed(2)}', - style: TextStyle( - color: transactionsList[index].isExpense ? Colors.red : Colors.green, - fontWeight: FontWeight.bold, - ), - ), - onLongPress: () => deleteTransaction(transactionsList[index]), - ); - }, - ); - } - - Widget _buildExpenseChart() { - return Padding( - padding: const EdgeInsets.all(8.0), - child: Card( - elevation: 4.0, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: MonthlyExpensesChart(data: expenseData), - ), - ), - ); - } } - - -class ExpenseData { - final String month; - final double amount; - - ExpenseData({required this.month, required this.amount}); -} - - - - -class Transaction { - String title; - double amount; - bool isExpense; - DateTime date; - - Transaction({ - required this.title, - required this.amount, - required this.isExpense, - required this.date, - }); - - factory Transaction.fromJson(String json) { - final Map map = jsonDecode(json); - return Transaction( - title: map['title'], - amount: map['amount'], - isExpense: map['isExpense'], - date: DateTime.parse(map['date']), - ); - } - - Map toJson() { - return { - 'title': title, - 'amount': amount, - 'isExpense': isExpense, - 'date': date.toString(), - }; - } -} - -class AddTransactionDialog extends StatefulWidget { - final Function addTransaction; - - const AddTransactionDialog({super.key, required this.addTransaction}); - - @override - AddTransactionDialogState createState() => AddTransactionDialogState(); -} - -class AddTransactionDialogState extends State { - final _formKey = GlobalKey(); - final _titleController = TextEditingController(); - final _amountController = TextEditingController(); - bool _isExpense = true; - - @override - void dispose() { - _titleController.dispose(); - _amountController.dispose(); - super.dispose(); - } - - void _submitForm() { - if (_formKey.currentState!.validate()) { - String title = _titleController.text.trim(); - double amount = double.parse(_amountController.text.trim()); - - Transaction transaction = Transaction( - title: title, - amount: amount, - isExpense: _isExpense, - date: DateTime.now(), - ); - widget.addTransaction(transaction); - - Navigator.of(context).pop(); - } - } - - @override - Widget build(BuildContext context) { - return AlertDialog( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - ), - title: Text( - 'addtrans'.tr(), - style: TextStyle( - fontSize: 20, - color: Colors.black54, - ), - ), - content: Form( - key: _formKey, // Hier wird das _formKey dem Form-Widget zugewiesen - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Neumorphic( - style: NeumorphicStyle( - depth: -5, - intensity: 0.8, - color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - ), - child: TextFormField( - controller: _titleController, - decoration: InputDecoration( - labelText: 'Title', - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - vertical: 12, - horizontal: 16, - ), - ), - validator: (value) { - if (value!.isEmpty) { - return 'entertitle'.tr(); - } - return null; - }, - ), - ), - SizedBox(height: 16), - Neumorphic( - style: NeumorphicStyle( - depth: -5, - intensity: 0.8, - color: Colors.grey.shade100, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - ), - child: TextFormField( - controller: _amountController, - decoration: InputDecoration( - labelText: 'amount'.tr(), - border: InputBorder.none, - contentPadding: EdgeInsets.symmetric( - vertical: 12, - horizontal: 16, - ), - ), - keyboardType: TextInputType.number, - validator: (value) { - if (value!.isEmpty) { - return 'enteramount'.tr(); - } - if (double.tryParse(value) == null) { - return 'entervalidnumber'.tr(); - } - return null; - }, - ), - ), - SizedBox(height: 16), - Row( - children: [ - NeumorphicCheckbox( - style: NeumorphicCheckboxStyle(selectedColor: Colors.lightGreen, disabledColor: Colors.grey.shade200,selectedDepth: -10, unselectedDepth: 8), - value: _isExpense, - onChanged: (value) { - setState(() { - _isExpense = value!; - }); - }, - ), - SizedBox(width: 8), - Text('expense'.tr(), style: TextStyle(color: Colors.black87),), - ], - ), - ], - )), - actions: [ - NeumorphicButton( - onPressed: () { - Navigator.of(context).pop(); - }, - style: NeumorphicStyle( - shape: NeumorphicShape.concave, - intensity: 0.8, - depth: 9, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - color: Colors.grey.shade100, - ), - padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Text( - 'cancel'.tr(), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - NeumorphicButton( - onPressed: _submitForm, - style: NeumorphicStyle( - shape: NeumorphicShape.concave, - intensity: 0.8, - depth: 9, - boxShape: NeumorphicBoxShape.roundRect( - BorderRadius.circular(12), - ), - color: Colors.grey.shade100, - ), - padding: EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: Text( - 'add'.tr(), - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ); - - } -} - - -class MonthlyExpensesChart extends StatelessWidget { - final List data; - - const MonthlyExpensesChart({super.key, required this.data}); - - @override - Widget build(BuildContext context) { - return SizedBox( - height: 300, - child: SfCartesianChart( - primaryXAxis: CategoryAxis(), - series: [ - ColumnSeries( - dataSource: data, - xValueMapper: (ExpenseData expense, _) => expense.month, - yValueMapper: (ExpenseData expense, _) => expense.amount, - color: Colors.blue, - ), - ], - ), - ); - } -} - - - diff --git a/lib/monthly_expenses.dart b/lib/monthly_expenses.dart deleted file mode 100644 index 3e191e0..0000000 --- a/lib/monthly_expenses.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:charts_flutter/flutter.dart' as charts; - - -class MonthlyExpensesChart extends StatelessWidget { - final List data; - - const MonthlyExpensesChart({super.key, required this.data}); - - @override - Widget build(BuildContext context) { - List> series = [ - charts.Series( - id: 'Expenses', - data: data, - domainFn: (ExpenseData expense, _) => expense.month, - measureFn: (ExpenseData expense, _) => expense.amount, - colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault, - ), - ]; - - return Container( - height: 200, - padding: const EdgeInsets.all(16), - child: charts.BarChart( - series, - animate: true, - domainAxis: const charts.OrdinalAxisSpec( - renderSpec: charts.SmallTickRendererSpec( - labelStyle: charts.TextStyleSpec( - fontSize: 12, - ), - ), - ), - primaryMeasureAxis: const charts.NumericAxisSpec( - renderSpec: charts.GridlineRendererSpec( - labelStyle: charts.TextStyleSpec( - fontSize: 12, - ), - ), - ), - ), - ); - } -} - -class ExpenseData { - final String month; - final double amount; - - ExpenseData({required this.month, required this.amount}); -} - diff --git a/lib/preferences.dart b/lib/preferences.dart index 4658d08..6891c38 100644 --- a/lib/preferences.dart +++ b/lib/preferences.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:tests/theme/theme_manager.dart'; @@ -12,19 +13,24 @@ class Settings extends StatefulWidget { } class SettingsState extends State { - String _selectedLanguage = 'English'; String _selectedCurrency = 'Euro'; final List _languages = ['English', 'Deutsch']; final List _currencies = ['Euro', 'Dollar', 'CHF']; - void checkForLanguage(BuildContext context){ + void checkForLanguage(BuildContext context) { String language = context.locale.toString(); - print(language); - switch(language){ - case "en_US": _selectedLanguage = "English"; break; - case "de_DE": _selectedLanguage = "Deutsch"; break; + if (kDebugMode) { + print(language); + } + switch (language) { + case "en_US": + _selectedLanguage = "English"; + break; + case "de_DE": + _selectedLanguage = "Deutsch"; + break; } } @@ -33,20 +39,19 @@ class SettingsState extends State { _themeManager.removeListener(themeListener); super.dispose(); } - themeListener(){ - if(mounted){ - setState(() { - }); - } + themeListener() { + if (mounted) { + setState(() {}); } + } - @override - void initState() { - super.initState(); + @override + void initState() { + super.initState(); - _themeManager.addListener(themeListener); - } + _themeManager.addListener(themeListener); + } @override Widget build(BuildContext context) { @@ -59,14 +64,14 @@ class SettingsState extends State { toolbarHeight: 80, title: Text( 'settings'.tr(), - style: TextStyle( + style: const TextStyle( fontSize: 18, fontWeight: FontWeight.bold, color: Colors.black54, ), ), centerTitle: true, - shape: RoundedRectangleBorder( + shape: const RoundedRectangleBorder( borderRadius: BorderRadius.only( bottomLeft: Radius.circular(15), bottomRight: Radius.circular(15), @@ -78,17 +83,16 @@ class SettingsState extends State { child: NeumorphicButton( onPressed: () { Navigator.pop(context, "Change"); - }, style: NeumorphicStyle( shape: NeumorphicShape.flat, - boxShape: NeumorphicBoxShape.circle(), + boxShape: const NeumorphicBoxShape.circle(), depth: 6, intensity: 0.9, color: Colors.grey.shade100, ), - padding: EdgeInsets.all(10), - child: Icon(Icons.arrow_back, color: Colors.black38), + padding: const EdgeInsets.all(10), + child: const Icon(Icons.arrow_back, color: Colors.black38), ), ), ), @@ -118,10 +122,14 @@ class SettingsState extends State { trackDepth: 5, // Weitere Style-Eigenschaften hier anpassen // Um den Switch heller zu machen, kannst du die Farben anpassen - activeTrackColor: Colors.lightGreen, // Farbe für den aktiven Track - inactiveTrackColor: Colors.grey.shade300, // Farbe für den inaktiven Track - activeThumbColor: Colors.grey.shade100, // Farbe für den aktiven Thumb - inactiveThumbColor: Colors.grey.shade200, // Farbe für den inaktiven Thumb + activeTrackColor: Colors.lightGreen, + // Farbe für den aktiven Track + inactiveTrackColor: Colors.grey.shade300, + // Farbe für den inaktiven Track + activeThumbColor: Colors.grey.shade100, + // Farbe für den aktiven Thumb + inactiveThumbColor: + Colors.grey.shade200, // Farbe für den inaktiven Thumb ), ), ), @@ -142,9 +150,13 @@ class SettingsState extends State { onChanged: (String? newValue) { setState(() { _selectedLanguage = newValue!; - switch(_selectedLanguage){ - case "English": context.setLocale(Locale('en', 'US')); break; - case "Deutsch": context.setLocale(Locale('de', 'DE')); break; + switch (_selectedLanguage) { + case "English": + context.setLocale(const Locale('en', 'US')); + break; + case "Deutsch": + context.setLocale(const Locale('de', 'DE')); + break; } }); }, @@ -177,7 +189,8 @@ class SettingsState extends State { // z.B. mit einer Funktion, die die Währung ändert. }); }, - items: _currencies.map>((String value) { + items: + _currencies.map>((String value) { return DropdownMenuItem( value: value, child: Text(value), diff --git a/lib/saving_tips.dart b/lib/saving_tips.dart index 2e7c5f7..dbb083a 100644 --- a/lib/saving_tips.dart +++ b/lib/saving_tips.dart @@ -1,9 +1,5 @@ -import 'package:flutter/material.dart'; import 'package:carousel_slider/carousel_slider.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart'; -import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter/cupertino.dart'; - class SavingsTipsDialog extends StatelessWidget { final List savingsTips = [ @@ -24,6 +20,8 @@ class SavingsTipsDialog extends StatelessWidget { 'Nutze kostenlose Online-Ressourcen für Weiterbildung und Hobbys.', ]; + SavingsTipsDialog({super.key}); + @override Widget build(BuildContext context) { return Dialog( @@ -38,49 +36,48 @@ class SavingsTipsDialog extends StatelessWidget { CarouselSlider.builder( itemCount: savingsTips.length, itemBuilder: (BuildContext context, int index, int realIndex) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: Neumorphic( + return Neumorphic( + margin: const EdgeInsets.only(bottom: 5), style: NeumorphicStyle( - - boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(13.0)), + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(13.0)), ), child: Container( decoration: BoxDecoration( borderRadius: BorderRadius.circular(10.0), - color: Colors.grey.shade100, // Setze die Hintergrundfarbe auf rot + color: Colors.grey + .shade100, // Setze die Hintergrundfarbe auf rot ), - padding: const EdgeInsets.all(16.0), - child: Column( mainAxisAlignment: MainAxisAlignment.center, + padding: const EdgeInsets.all(14.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Text( savingsTips[index], - style: TextStyle(fontSize: 18.0), + style: const TextStyle(fontSize: 18.0), textAlign: TextAlign.center, ), ], ), ), - ), + ); }, options: CarouselOptions( height: 250.0, enlargeCenterPage: true, - viewportFraction: 0.85, + viewportFraction: 0.76, initialPage: 0, enableInfiniteScroll: true, autoPlay: true, - autoPlayInterval: Duration(seconds: 4), - autoPlayAnimationDuration: Duration(milliseconds: 700), + autoPlayInterval: const Duration(seconds: 4), + autoPlayAnimationDuration: const Duration(milliseconds: 700), autoPlayCurve: Curves.fastOutSlowIn, pauseAutoPlayOnTouch: true, ), ), - SizedBox(height: 16.0), + const SizedBox(height: 16.0), NeumorphicButton( - child: Text('Close'), onPressed: () { Navigator.of(context).pop(); }, @@ -89,13 +86,14 @@ class SavingsTipsDialog extends StatelessWidget { depth: 10, intensity: 0.9, shape: NeumorphicShape.flat, - boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15.0)), + boxShape: + NeumorphicBoxShape.roundRect(BorderRadius.circular(15.0)), ), + child: const Text('Close'), ), - ], ), ), ); } -} \ No newline at end of file +} diff --git a/lib/theme/theme_constants.dart b/lib/theme/theme_constants.dart index ca5fdc7..c81fb40 100644 --- a/lib/theme/theme_constants.dart +++ b/lib/theme/theme_constants.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; const COLOR_PRIMARY = Colors.deepOrangeAccent; @@ -6,9 +5,7 @@ const COLOR_PRIMARY = Colors.deepOrangeAccent; ThemeData lightTheme = ThemeData( brightness: Brightness.light, primaryColor: COLOR_PRIMARY, - fontFamily: "Montserrat" - -); + fontFamily: "Montserrat"); ThemeData darkTheme = ThemeData( fontFamily: "Montserrat", diff --git a/lib/transaction/transaction.dart b/lib/transaction/transaction.dart new file mode 100644 index 0000000..c0b5ef2 --- /dev/null +++ b/lib/transaction/transaction.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +class Transaction { + String title; + double amount; + bool isExpense; + DateTime date; + + Transaction({ + required this.title, + required this.amount, + required this.isExpense, + required this.date, + }); + + factory Transaction.fromJson(String json) { + final Map map = jsonDecode(json); + return Transaction( + title: map['title'], + amount: map['amount'], + isExpense: map['isExpense'], + date: DateTime.parse(map['date']), + ); + } + + Map toJson() { + return { + 'title': title, + 'amount': amount, + 'isExpense': isExpense, + 'date': date.toString(), + }; + } +} diff --git a/lib/transaction/transaction_dialog.dart b/lib/transaction/transaction_dialog.dart new file mode 100644 index 0000000..e9ff2c5 --- /dev/null +++ b/lib/transaction/transaction_dialog.dart @@ -0,0 +1,189 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter_neumorphic/flutter_neumorphic.dart'; +import 'package:tests/transaction/transaction.dart'; + +class AddTransactionDialog extends StatefulWidget { + final Function addTransaction; + const AddTransactionDialog({super.key, required this.addTransaction}); + + @override + AddTransactionDialogState createState() => AddTransactionDialogState(); +} + +class AddTransactionDialogState extends State { + final _formKey = GlobalKey(); + final _titleController = TextEditingController(); + final _amountController = TextEditingController(); + bool _isExpense = true; + + @override + void dispose() { + _titleController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + void _submitForm() { + if (_formKey.currentState!.validate()) { + String title = _titleController.text.trim(); + double amount = double.parse(_amountController.text.trim()); + Transaction transaction = Transaction( + title: title, + amount: amount, + isExpense: _isExpense, + date: DateTime.now(), + ); + widget.addTransaction(transaction); + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + return AlertDialog( + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15), + ), + title: Text( + 'addtrans'.tr(), + style: const TextStyle( + fontSize: 20, + color: Colors.black54, + ), + ), + content: Form( + key: _formKey, // Hier wird das _formKey dem Form-Widget zugewiesen + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Neumorphic( + style: NeumorphicStyle( + depth: -5, + intensity: 0.8, + color: Colors.grey.shade100, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + ), + child: TextFormField( + controller: _titleController, + decoration: const InputDecoration( + labelText: 'Title', + border: InputBorder.none, + contentPadding: EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + ), + validator: (value) { + if (value!.isEmpty) { + return 'entertitle'.tr(); + } + return null; + }, + ), + ), + const SizedBox(height: 16), + Neumorphic( + style: NeumorphicStyle( + depth: -5, + intensity: 0.8, + color: Colors.grey.shade100, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + ), + child: TextFormField( + controller: _amountController, + decoration: InputDecoration( + labelText: 'amount'.tr(), + border: InputBorder.none, + contentPadding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + ), + keyboardType: TextInputType.number, + validator: (value) { + if (value!.isEmpty) { + return 'enteramount'.tr(); + } + if (double.tryParse(value) == null) { + return 'entervalidnumber'.tr(); + } + return null; + }, + ), + ), + const SizedBox(height: 16), + Row( + children: [ + NeumorphicCheckbox( + style: NeumorphicCheckboxStyle( + selectedColor: Colors.lightGreen, + disabledColor: Colors.grey.shade200, + selectedDepth: -10, + unselectedDepth: 8), + value: _isExpense, + onChanged: (value) { + setState(() { + _isExpense = value!; + }); + }, + ), + const SizedBox(width: 8), + Text( + 'expense'.tr(), + style: const TextStyle(color: Colors.black87), + ), + ], + ), + ], + )), + actions: [ + NeumorphicButton( + onPressed: () { + Navigator.of(context).pop(); + }, + style: NeumorphicStyle( + shape: NeumorphicShape.concave, + intensity: 0.8, + depth: 9, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + color: Colors.grey.shade100, + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Text( + 'cancel'.tr(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + NeumorphicButton( + onPressed: _submitForm, + style: NeumorphicStyle( + shape: NeumorphicShape.concave, + intensity: 0.8, + depth: 9, + boxShape: NeumorphicBoxShape.roundRect( + BorderRadius.circular(12), + ), + color: Colors.grey.shade100, + ), + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), + child: Text( + 'add'.tr(), + style: const TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 33044db..562463c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.12.0" + circular_seek_bar: + dependency: "direct main" + description: + name: circular_seek_bar + sha256: "78ff5e54b6012523f3ce19b33c870ab75433ea96977f44606f1641a8ac5c1873" + url: "https://pub.dev" + source: hosted + version: "1.1.0+2" clock: dependency: transitive description: @@ -450,6 +458,14 @@ packages: url: "https://pub.dev" source: hosted version: "21.2.10" + tab_indicator_styler: + dependency: "direct main" + description: + name: tab_indicator_styler + sha256: "9e7e90367e20f71f3882fc6578fdcced35ab1c66ab20fcb623cdcc20d2796c76" + url: "https://pub.dev" + source: hosted + version: "2.0.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 1c15353..9e362c8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,7 +42,8 @@ dependencies: easy_localization: ^3.0.2 carousel_slider: ^4.2.1 flutter_svg: ^2.0.0 - + circular_seek_bar: ^1.1.0 + tab_indicator_styler: ^2.0.0 dev_dependencies: flutter_test: