Code Cleanup

main
Arlind 2023-06-15 00:55:02 +02:00
parent 5901774fc7
commit 3435b1917c
16 changed files with 964 additions and 831 deletions

View File

@ -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<String, dynamic> map = jsonDecode(json);
return Account(
name: map['name'],
balance: map['balance'],
);
}
Map<String, dynamic> toJson() {
return {
'name': name,
'balance': balance,
};
}
}

View File

@ -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<AccountDetailPage>
with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Transaction> transactions = [];
List<Transaction> incomeTransactions = [];
List<Transaction> expenseTransactions = [];
List<ExpenseData> 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<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;
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<double> _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<Transaction> 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),
),
),
);
}
}

View File

@ -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<AddAccountDialog> {
final _formKey = GlobalKey<FormState>();
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,
),
),
),
],
);
}
}

View File

@ -1,7 +1,7 @@
{ {
"title" : "Finanzplaner", "title" : "Finanzplaner",
"delete" : "Löschen", "delete" : "Löschen",
"deleteaccount" : "Account Löschen", "deleteaccount" : "Account löschen",
"sure" : "Sind Sie sicher, dass Sie dieses Konto löschen möchten?", "sure" : "Sind Sie sicher, dass Sie dieses Konto löschen möchten?",
"balance" : "Bilanz", "balance" : "Bilanz",
"addaccount" : "Konto hinzufügen", "addaccount" : "Konto hinzufügen",
@ -18,7 +18,9 @@
"enteramount": "Bitte geben Sie einen Betrag ein", "enteramount": "Bitte geben Sie einen Betrag ein",
"expense": "Ausgabe", "expense": "Ausgabe",
"settings": "Einstellungen", "settings": "Einstellungen",
"darkmode": "Dunkel Modus", "darkmode": "Dark Mode",
"language": "Sprache", "language": "Sprache",
"currency": "Währung" "currency": "Währung",
"budget": "Budgetcheck",
"progress": "von deinem Budget ausgegeben"
} }

View File

@ -1,18 +1,18 @@
{ {
"title" : "Financial Planner", "title" : "Financial Planner",
"delete" : "Delete", "delete" : "Delete",
"deleteaccount" : "Delete Account", "deleteaccount" : "Delete account",
"sure" : "Are you sure you want to delete this account?", "sure" : "Are you sure you want to delete this account?",
"balance" : "Balance", "balance" : "Balance",
"addaccount" : "Add Account", "addaccount" : "Add account",
"entername" : "Please enter a name", "entername" : "Please enter a name",
"enterbalance" : "Please enter a balance", "enterbalance" : "Please enter a balance",
"entervalidnumber" : "Please enter a valid number", "entervalidnumber" : "Please enter a valid number",
"cancel" : "Cancel", "cancel" : "Cancel",
"add" : "Add", "add" : "Add",
"income": "Income", "income": "Income",
"expenditures": "Expenditures", "expenditures": "Expenses",
"addtrans": "Add Transaction", "addtrans": "Add transaction",
"entertitle": "Please enter a title", "entertitle": "Please enter a title",
"amount": "Amount", "amount": "Amount",
"enteramount": "Please enter an amount", "enteramount": "Please enter an amount",
@ -20,5 +20,7 @@
"settings": "Settings", "settings": "Settings",
"darkmode": "Dark Mode", "darkmode": "Dark Mode",
"language": "Language", "language": "Language",
"currency": "Currency" "currency": "Currency",
"budget": "Budget check",
"progress": "of your budget spent"
} }

View File

@ -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<ExpenseData> data;
const MonthlyExpensesChart({super.key, required this.data});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
child: SfCartesianChart(
primaryXAxis: CategoryAxis(),
series: <ChartSeries>[
ColumnSeries<ExpenseData, String>(
dataSource: data,
xValueMapper: (ExpenseData expense, _) => expense.month,
yValueMapper: (ExpenseData expense, _) => expense.amount,
color: Colors.blue,
),
],
),
);
}
}

View File

@ -0,0 +1,6 @@
class ExpenseData {
final String month;
final double amount;
ExpenseData({required this.month, required this.amount});
}

View File

@ -2,25 +2,31 @@ import 'dart:convert';
import 'package:awesome_dialog/awesome_dialog.dart'; import 'package:awesome_dialog/awesome_dialog.dart';
import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart';
import 'package:intl/intl.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:syncfusion_flutter_charts/charts.dart';
import 'package:tests/preferences.dart'; import 'package:tests/preferences.dart';
import 'package:tests/saving_tips.dart';
import 'package:tests/theme/theme_constants.dart'; import 'package:tests/theme/theme_constants.dart';
import 'package:tests/theme/theme_manager.dart'; import 'package:tests/theme/theme_manager.dart';
import "package:easy_localization/easy_localization.dart"; import "package:easy_localization/easy_localization.dart";
import 'account/account_dialog.dart';
import 'account/account.dart';
import 'account/account_detail.dart';
Future<void> main() async { Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized(); await EasyLocalization.ensureInitialized();
runApp(EasyLocalization( runApp(
supportedLocales: [Locale('en', 'US'), Locale('de', 'DE')], EasyLocalization(
path: 'lib/assets/translations', // <-- change the path of the translation files supportedLocales: const [Locale('en', 'US'), Locale('de', 'DE')],
fallbackLocale: Locale('en', 'US'), path: 'lib/assets/translations',
child: const FinancialPlannerApp() // <-- change the path of the translation files
),); fallbackLocale: const Locale('en', 'US'),
child: const FinancialPlannerApp()),
);
} }
ThemeManager _themeManager = ThemeManager(); ThemeManager _themeManager = ThemeManager();
class FinancialPlannerApp extends StatelessWidget { class FinancialPlannerApp extends StatelessWidget {
@ -57,32 +63,35 @@ class HomePageState extends State<HomePage> {
super.initState(); super.initState();
_themeManager.addListener(themeListener); _themeManager.addListener(themeListener);
loadAccounts(); loadAccounts();
} }
@override @override
void dispose() { void dispose() {
_themeManager.removeListener(themeListener); _themeManager.removeListener(themeListener);
super.dispose(); super.dispose();
} }
themeListener() { themeListener() {
if (mounted) { if (mounted) {
setState(() { setState(() {});
}
}
});
}
}
Future<void> loadAccounts() async { Future<void> loadAccounts() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
final accountList = prefs.getStringList('accounts') ?? []; final accountList = prefs.getStringList('accounts') ?? [];
setState(() { setState(() {
accounts = accountList.map((accountJson) => Account.fromJson(accountJson)).toList(); accounts = accountList
.map((accountJson) => Account.fromJson(accountJson))
.toList();
}); });
} }
Future<void> saveAccounts() async { Future<void> saveAccounts() async {
SharedPreferences prefs = await SharedPreferences.getInstance(); SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> accountJsonList = accounts.map((account) => json.encode(account.toJson())).toList(); List<String> accountJsonList =
accounts.map((account) => json.encode(account.toJson())).toList();
await prefs.setStringList('accounts', accountJsonList); await prefs.setStringList('accounts', accountJsonList);
} }
@ -126,23 +135,20 @@ class HomePageState extends State<HomePage> {
padding: const EdgeInsets.only(right: 16.0, top: 3, bottom: 7), padding: const EdgeInsets.only(right: 16.0, top: 3, bottom: 7),
child: NeumorphicButton( child: NeumorphicButton(
onPressed: () async { onPressed: () async {
await Navigator.push(
String value = await Navigator.push(
context, context,
MaterialPageRoute(builder: (context) => Settings()), MaterialPageRoute(builder: (context) => const Settings()),
); );
setState(() { setState(() {});
});
}, },
style: NeumorphicStyle( style: NeumorphicStyle(
shape: NeumorphicShape.convex, shape: NeumorphicShape.convex,
boxShape: NeumorphicBoxShape.circle(), boxShape: const NeumorphicBoxShape.circle(),
depth: 6, depth: 6,
intensity: 0.9, intensity: 0.9,
color: Colors.grey.shade100, color: Colors.grey.shade100,
), ),
child: Icon(Icons.settings, color: Colors.black38), child: const Icon(Icons.settings, color: Colors.black38),
), ),
), ),
], ],
@ -168,7 +174,6 @@ class HomePageState extends State<HomePage> {
deleteAccount(accounts[index]); deleteAccount(accounts[index]);
}, },
).show(); ).show();
}, },
child: Neumorphic( child: Neumorphic(
margin: const EdgeInsets.all(16), margin: const EdgeInsets.all(16),
@ -177,11 +182,13 @@ class HomePageState extends State<HomePage> {
intensity: 1, intensity: 1,
shadowDarkColor: Colors.grey.shade300, shadowDarkColor: Colors.grey.shade300,
color: Colors.grey.shade100, color: Colors.grey.shade100,
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)), boxShape:
NeumorphicBoxShape.roundRect(BorderRadius.circular(15)),
), ),
child: ListTile( child: ListTile(
title: Text(accounts[index].name), title: Text(accounts[index].name),
subtitle: Text('${'balance'.tr()}: \$${accounts[index].balance.toStringAsFixed(2)}'), subtitle: Text(
'${'balance'.tr()}: \$${accounts[index].balance.toStringAsFixed(2)}'),
onTap: () { onTap: () {
Navigator.push( Navigator.push(
context, context,
@ -216,682 +223,12 @@ class HomePageState extends State<HomePage> {
color: Colors.grey.shade100, color: Colors.grey.shade100,
boxShape: const NeumorphicBoxShape.circle(), boxShape: const NeumorphicBoxShape.circle(),
), ),
child: const Icon(Icons.add,size:60,color: Colors.black12,), child: const Icon(
),
);
}
}
class Account {
String name;
double balance;
Account({
required this.name,
required this.balance,
});
factory Account.fromJson(String json) {
final Map<String, dynamic> map = jsonDecode(json);
return Account(
name: map['name'],
balance: map['balance'],
);
}
Map<String, dynamic> 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<AddAccountDialog> {
final _formKey = GlobalKey<FormState>();
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<AccountDetailPage> with SingleTickerProviderStateMixin {
late TabController _tabController;
List<Transaction> transactions = [];
List<Transaction> incomeTransactions = [];
List<Transaction> expenseTransactions = [];
List<ExpenseData> 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<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;
}
@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(
Icons.add, Icons.add,
size: 40, size: 60,
color: Colors.black12, color: Colors.black12,
), ),
), ),
); );
} }
Widget _buildTransactionsList(List<Transaction> 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<String, dynamic> map = jsonDecode(json);
return Transaction(
title: map['title'],
amount: map['amount'],
isExpense: map['isExpense'],
date: DateTime.parse(map['date']),
);
}
Map<String, dynamic> 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<AddTransactionDialog> {
final _formKey = GlobalKey<FormState>();
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<ExpenseData> data;
const MonthlyExpensesChart({super.key, required this.data});
@override
Widget build(BuildContext context) {
return SizedBox(
height: 300,
child: SfCartesianChart(
primaryXAxis: CategoryAxis(),
series: <ChartSeries>[
ColumnSeries<ExpenseData, String>(
dataSource: data,
xValueMapper: (ExpenseData expense, _) => expense.month,
yValueMapper: (ExpenseData expense, _) => expense.amount,
color: Colors.blue,
),
],
),
);
}
}

View File

@ -1,53 +0,0 @@
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class MonthlyExpensesChart extends StatelessWidget {
final List<ExpenseData> data;
const MonthlyExpensesChart({super.key, required this.data});
@override
Widget build(BuildContext context) {
List<charts.Series<ExpenseData, String>> 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});
}

View File

@ -1,4 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart';
import 'package:tests/theme/theme_manager.dart'; import 'package:tests/theme/theme_manager.dart';
@ -12,7 +13,6 @@ class Settings extends StatefulWidget {
} }
class SettingsState extends State<Settings> { class SettingsState extends State<Settings> {
String _selectedLanguage = 'English'; String _selectedLanguage = 'English';
String _selectedCurrency = 'Euro'; String _selectedCurrency = 'Euro';
@ -21,10 +21,16 @@ class SettingsState extends State<Settings> {
void checkForLanguage(BuildContext context) { void checkForLanguage(BuildContext context) {
String language = context.locale.toString(); String language = context.locale.toString();
if (kDebugMode) {
print(language); print(language);
}
switch (language) { switch (language) {
case "en_US": _selectedLanguage = "English"; break; case "en_US":
case "de_DE": _selectedLanguage = "Deutsch"; break; _selectedLanguage = "English";
break;
case "de_DE":
_selectedLanguage = "Deutsch";
break;
} }
} }
@ -33,11 +39,10 @@ class SettingsState extends State<Settings> {
_themeManager.removeListener(themeListener); _themeManager.removeListener(themeListener);
super.dispose(); super.dispose();
} }
themeListener() { themeListener() {
if (mounted) { if (mounted) {
setState(() { setState(() {});
});
} }
} }
@ -59,14 +64,14 @@ class SettingsState extends State<Settings> {
toolbarHeight: 80, toolbarHeight: 80,
title: Text( title: Text(
'settings'.tr(), 'settings'.tr(),
style: TextStyle( style: const TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
color: Colors.black54, color: Colors.black54,
), ),
), ),
centerTitle: true, centerTitle: true,
shape: RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(15), bottomLeft: Radius.circular(15),
bottomRight: Radius.circular(15), bottomRight: Radius.circular(15),
@ -78,17 +83,16 @@ class SettingsState extends State<Settings> {
child: NeumorphicButton( child: NeumorphicButton(
onPressed: () { onPressed: () {
Navigator.pop(context, "Change"); Navigator.pop(context, "Change");
}, },
style: NeumorphicStyle( style: NeumorphicStyle(
shape: NeumorphicShape.flat, shape: NeumorphicShape.flat,
boxShape: NeumorphicBoxShape.circle(), boxShape: const NeumorphicBoxShape.circle(),
depth: 6, depth: 6,
intensity: 0.9, intensity: 0.9,
color: Colors.grey.shade100, color: Colors.grey.shade100,
), ),
padding: EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Icon(Icons.arrow_back, color: Colors.black38), child: const Icon(Icons.arrow_back, color: Colors.black38),
), ),
), ),
), ),
@ -118,10 +122,14 @@ class SettingsState extends State<Settings> {
trackDepth: 5, trackDepth: 5,
// Weitere Style-Eigenschaften hier anpassen // Weitere Style-Eigenschaften hier anpassen
// Um den Switch heller zu machen, kannst du die Farben anpassen // Um den Switch heller zu machen, kannst du die Farben anpassen
activeTrackColor: Colors.lightGreen, // Farbe für den aktiven Track activeTrackColor: Colors.lightGreen,
inactiveTrackColor: Colors.grey.shade300, // Farbe für den inaktiven Track // Farbe für den aktiven Track
activeThumbColor: Colors.grey.shade100, // Farbe für den aktiven Thumb inactiveTrackColor: Colors.grey.shade300,
inactiveThumbColor: Colors.grey.shade200, // Farbe für den inaktiven Thumb // 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
), ),
), ),
), ),
@ -143,8 +151,12 @@ class SettingsState extends State<Settings> {
setState(() { setState(() {
_selectedLanguage = newValue!; _selectedLanguage = newValue!;
switch (_selectedLanguage) { switch (_selectedLanguage) {
case "English": context.setLocale(Locale('en', 'US')); break; case "English":
case "Deutsch": context.setLocale(Locale('de', 'DE')); break; context.setLocale(const Locale('en', 'US'));
break;
case "Deutsch":
context.setLocale(const Locale('de', 'DE'));
break;
} }
}); });
}, },
@ -177,7 +189,8 @@ class SettingsState extends State<Settings> {
// z.B. mit einer Funktion, die die Währung ändert. // z.B. mit einer Funktion, die die Währung ändert.
}); });
}, },
items: _currencies.map<DropdownMenuItem<String>>((String value) { items:
_currencies.map<DropdownMenuItem<String>>((String value) {
return DropdownMenuItem<String>( return DropdownMenuItem<String>(
value: value, value: value,
child: Text(value), child: Text(value),

View File

@ -1,9 +1,5 @@
import 'package:flutter/material.dart';
import 'package:carousel_slider/carousel_slider.dart'; import 'package:carousel_slider/carousel_slider.dart';
import 'package:flutter_neumorphic/flutter_neumorphic.dart'; import 'package:flutter_neumorphic/flutter_neumorphic.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter/cupertino.dart';
class SavingsTipsDialog extends StatelessWidget { class SavingsTipsDialog extends StatelessWidget {
final List<String> savingsTips = [ final List<String> savingsTips = [
@ -24,6 +20,8 @@ class SavingsTipsDialog extends StatelessWidget {
'Nutze kostenlose Online-Ressourcen für Weiterbildung und Hobbys.', 'Nutze kostenlose Online-Ressourcen für Weiterbildung und Hobbys.',
]; ];
SavingsTipsDialog({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Dialog( return Dialog(
@ -38,49 +36,48 @@ class SavingsTipsDialog extends StatelessWidget {
CarouselSlider.builder( CarouselSlider.builder(
itemCount: savingsTips.length, itemCount: savingsTips.length,
itemBuilder: (BuildContext context, int index, int realIndex) { itemBuilder: (BuildContext context, int index, int realIndex) {
return Padding( return Neumorphic(
padding: const EdgeInsets.symmetric(horizontal: 8.0), margin: const EdgeInsets.only(bottom: 5),
child: Neumorphic(
style: NeumorphicStyle( style: NeumorphicStyle(
boxShape: NeumorphicBoxShape.roundRect(
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(13.0)), BorderRadius.circular(13.0)),
), ),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10.0), 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), padding: const EdgeInsets.all(14.0),
child: Column( mainAxisAlignment: MainAxisAlignment.center, child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
savingsTips[index], savingsTips[index],
style: TextStyle(fontSize: 18.0), style: const TextStyle(fontSize: 18.0),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
), ),
), ),
),
); );
}, },
options: CarouselOptions( options: CarouselOptions(
height: 250.0, height: 250.0,
enlargeCenterPage: true, enlargeCenterPage: true,
viewportFraction: 0.85, viewportFraction: 0.76,
initialPage: 0, initialPage: 0,
enableInfiniteScroll: true, enableInfiniteScroll: true,
autoPlay: true, autoPlay: true,
autoPlayInterval: Duration(seconds: 4), autoPlayInterval: const Duration(seconds: 4),
autoPlayAnimationDuration: Duration(milliseconds: 700), autoPlayAnimationDuration: const Duration(milliseconds: 700),
autoPlayCurve: Curves.fastOutSlowIn, autoPlayCurve: Curves.fastOutSlowIn,
pauseAutoPlayOnTouch: true, pauseAutoPlayOnTouch: true,
), ),
), ),
SizedBox(height: 16.0), const SizedBox(height: 16.0),
NeumorphicButton( NeumorphicButton(
child: Text('Close'),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -89,10 +86,11 @@ class SavingsTipsDialog extends StatelessWidget {
depth: 10, depth: 10,
intensity: 0.9, intensity: 0.9,
shape: NeumorphicShape.flat, shape: NeumorphicShape.flat,
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15.0)), boxShape:
NeumorphicBoxShape.roundRect(BorderRadius.circular(15.0)),
), ),
child: const Text('Close'),
), ),
], ],
), ),
), ),

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
const COLOR_PRIMARY = Colors.deepOrangeAccent; const COLOR_PRIMARY = Colors.deepOrangeAccent;
@ -6,9 +5,7 @@ const COLOR_PRIMARY = Colors.deepOrangeAccent;
ThemeData lightTheme = ThemeData( ThemeData lightTheme = ThemeData(
brightness: Brightness.light, brightness: Brightness.light,
primaryColor: COLOR_PRIMARY, primaryColor: COLOR_PRIMARY,
fontFamily: "Montserrat" fontFamily: "Montserrat");
);
ThemeData darkTheme = ThemeData( ThemeData darkTheme = ThemeData(
fontFamily: "Montserrat", fontFamily: "Montserrat",

View File

@ -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<String, dynamic> map = jsonDecode(json);
return Transaction(
title: map['title'],
amount: map['amount'],
isExpense: map['isExpense'],
date: DateTime.parse(map['date']),
);
}
Map<String, dynamic> toJson() {
return {
'title': title,
'amount': amount,
'isExpense': isExpense,
'date': date.toString(),
};
}
}

View File

@ -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<AddTransactionDialog> {
final _formKey = GlobalKey<FormState>();
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,
),
),
),
],
);
}
}

View File

@ -65,6 +65,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.0" 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: clock:
dependency: transitive dependency: transitive
description: description:
@ -450,6 +458,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "21.2.10" 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: term_glyph:
dependency: transitive dependency: transitive
description: description:

View File

@ -42,7 +42,8 @@ dependencies:
easy_localization: ^3.0.2 easy_localization: ^3.0.2
carousel_slider: ^4.2.1 carousel_slider: ^4.2.1
flutter_svg: ^2.0.0 flutter_svg: ^2.0.0
circular_seek_bar: ^1.1.0
tab_indicator_styler: ^2.0.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: