Major design change to Neumorphic design
parent
7b8dec7b5a
commit
392a02b38d
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
520
lib/main.dart
520
lib/main.dart
|
@ -1,48 +1,65 @@
|
|||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
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:charts_flutter/flutter.dart' as charts;
|
||||
import 'package:syncfusion_flutter_charts/charts.dart';
|
||||
import 'package:tests/preferences.dart';
|
||||
import 'package:tests/theme/theme_constants.dart';
|
||||
import 'package:tests/theme/theme_manager.dart';
|
||||
|
||||
void main() {
|
||||
|
||||
runApp(FinancialPlannerApp());
|
||||
runApp(const FinancialPlannerApp());
|
||||
}
|
||||
ThemeManager _themeManager = ThemeManager();
|
||||
|
||||
class FinancialPlannerApp extends StatelessWidget {
|
||||
const FinancialPlannerApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
debugShowCheckedModeBanner: false,
|
||||
title: 'Financial Planner',
|
||||
theme: ThemeData(
|
||||
brightness: Brightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
darkTheme: ThemeData(
|
||||
brightness: Brightness.dark,
|
||||
primarySwatch: Colors.blue,
|
||||
),
|
||||
home: HomePage(),
|
||||
theme: lightTheme,
|
||||
darkTheme: darkTheme,
|
||||
themeMode: _themeManager.themeMode,
|
||||
home: const HomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
HomePageState createState() => HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
class HomePageState extends State<HomePage> {
|
||||
List<Account> accounts = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_themeManager.addListener(themeListener);
|
||||
loadAccounts();
|
||||
}
|
||||
|
||||
}
|
||||
@override
|
||||
void dispose() {
|
||||
_themeManager.removeListener(themeListener);
|
||||
super.dispose();
|
||||
}
|
||||
themeListener(){
|
||||
if(mounted){
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
Future<void> loadAccounts() async {
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final accountList = prefs.getStringList('accounts') ?? [];
|
||||
|
@ -82,58 +99,91 @@ class _HomePageState extends State<HomePage> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text('Financial Planner'),
|
||||
),
|
||||
body: ListView.builder(
|
||||
itemCount: accounts.length,
|
||||
itemBuilder: (context, index) {
|
||||
return ListTile(
|
||||
title: Text(accounts[index].name),
|
||||
subtitle: Text('Balance: \$${accounts[index].balance.toStringAsFixed(2)}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AccountDetailPage(
|
||||
account: accounts[index],
|
||||
updateAccountBalance: updateAccountBalance,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
trailing: IconButton(
|
||||
icon: Icon(Icons.delete),
|
||||
centerTitle: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: const Text(
|
||||
'Financial Planner',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0, top: 3, bottom: 7),
|
||||
child: NeumorphicButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Delete Account'),
|
||||
content: Text('Are you sure you want to delete this account?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
deleteAccount(accounts[index]);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Delete'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => Settings()),
|
||||
);
|
||||
},
|
||||
style: NeumorphicStyle(
|
||||
shape: NeumorphicShape.convex,
|
||||
boxShape: NeumorphicBoxShape.circle(),
|
||||
depth: 6,
|
||||
intensity: 0.9,
|
||||
color: Colors.grey.shade100,
|
||||
),
|
||||
child: Icon(Icons.settings, color: Colors.black38),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ListView.builder(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
itemCount: accounts.length,
|
||||
itemBuilder: (context, index) {
|
||||
return GestureDetector(
|
||||
onLongPress: () {
|
||||
AwesomeDialog(
|
||||
btnOkText: "Delete",
|
||||
btnOkColor: Colors.lightGreen,
|
||||
btnCancelColor: Colors.grey,
|
||||
context: context,
|
||||
animType: AnimType.bottomSlide,
|
||||
dialogType: DialogType.info,
|
||||
title: 'Delete Account',
|
||||
headerAnimationLoop: false,
|
||||
desc: 'Are you sure you want to delete this account?',
|
||||
btnCancelOnPress: () {},
|
||||
btnOkOnPress: () {
|
||||
deleteAccount(accounts[index]);
|
||||
},
|
||||
).show();
|
||||
|
||||
},
|
||||
child: Neumorphic(
|
||||
margin: const EdgeInsets.all(16),
|
||||
style: NeumorphicStyle(
|
||||
depth: 7,
|
||||
intensity: 1,
|
||||
shadowDarkColor: Colors.grey.shade300,
|
||||
color: Colors.grey.shade100,
|
||||
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)),
|
||||
),
|
||||
child: ListTile(
|
||||
title: Text(accounts[index].name),
|
||||
subtitle: Text('Balance: \$${accounts[index].balance.toStringAsFixed(2)}'),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => AccountDetailPage(
|
||||
account: accounts[index],
|
||||
updateAccountBalance: updateAccountBalance,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
floatingActionButton: NeumorphicButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
|
@ -144,7 +194,14 @@ class _HomePageState extends State<HomePage> {
|
|||
},
|
||||
);
|
||||
},
|
||||
child: Icon(Icons.add),
|
||||
style: NeumorphicStyle(
|
||||
depth: 8,
|
||||
intensity: 1,
|
||||
shadowDarkColor: Colors.grey.shade400,
|
||||
color: Colors.grey.shade100,
|
||||
boxShape: const NeumorphicBoxShape.circle(),
|
||||
),
|
||||
child: const Icon(Icons.add,size:60,color: Colors.black12,),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -178,13 +235,13 @@ class Account {
|
|||
class AddAccountDialog extends StatefulWidget {
|
||||
final Function addAccount;
|
||||
|
||||
AddAccountDialog({required this.addAccount});
|
||||
const AddAccountDialog({super.key, required this.addAccount});
|
||||
|
||||
@override
|
||||
_AddAccountDialogState createState() => _AddAccountDialogState();
|
||||
AddAccountDialogState createState() => AddAccountDialogState();
|
||||
}
|
||||
|
||||
class _AddAccountDialogState extends State<AddAccountDialog> {
|
||||
class AddAccountDialogState extends State<AddAccountDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _nameController = TextEditingController();
|
||||
final _balanceController = TextEditingController();
|
||||
|
@ -214,52 +271,123 @@ class _AddAccountDialogState extends State<AddAccountDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Add Account'),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
title: const Text(
|
||||
'Add Account',
|
||||
),
|
||||
titleTextStyle: TextStyle(
|
||||
color: Colors.black54,
|
||||
fontSize: 20,
|
||||
),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
controller: _nameController,
|
||||
decoration: InputDecoration(labelText: 'Name'),
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter a name';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
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 'Please enter a name';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
TextFormField(
|
||||
controller: _balanceController,
|
||||
decoration: InputDecoration(labelText: 'Balance'),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter a balance';
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return 'Please enter a valid number';
|
||||
}
|
||||
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',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.fromLTRB(12, 16, 12, 16),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter a balance';
|
||||
}
|
||||
if (double.tryParse(value) == null) {
|
||||
return 'Please enter a valid number';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
NeumorphicButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Cancel'),
|
||||
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',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
NeumorphicButton(
|
||||
onPressed: _submitForm,
|
||||
child: Text('Add'),
|
||||
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',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,13 +396,13 @@ class AccountDetailPage extends StatefulWidget {
|
|||
final Account account;
|
||||
final Function(Account) updateAccountBalance;
|
||||
|
||||
AccountDetailPage({required this.account, required this.updateAccountBalance});
|
||||
const AccountDetailPage({super.key, required this.account, required this.updateAccountBalance});
|
||||
|
||||
@override
|
||||
_AccountDetailPageState createState() => _AccountDetailPageState();
|
||||
AccountDetailPageState createState() => AccountDetailPageState();
|
||||
}
|
||||
|
||||
class _AccountDetailPageState extends State<AccountDetailPage> with SingleTickerProviderStateMixin {
|
||||
class AccountDetailPageState extends State<AccountDetailPage> with SingleTickerProviderStateMixin {
|
||||
late TabController _tabController;
|
||||
List<Transaction> transactions = [];
|
||||
List<Transaction> incomeTransactions = [];
|
||||
|
@ -363,14 +491,49 @@ class _AccountDetailPageState extends State<AccountDetailPage> with SingleTicker
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(widget.account.name),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
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: [
|
||||
tabs: const [
|
||||
Tab(text: 'Einnahmen'),
|
||||
Tab(text: 'Ausgaben'),
|
||||
],
|
||||
|
@ -391,12 +554,24 @@ class _AccountDetailPageState extends State<AccountDetailPage> with SingleTicker
|
|||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
floatingActionButton: NeumorphicButton(
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (_) => AddTransactionDialog(addTransaction: addTransaction),
|
||||
),
|
||||
child: Icon(Icons.add),
|
||||
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,
|
||||
size: 40,
|
||||
color: Colors.black12,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -425,11 +600,11 @@ class _AccountDetailPageState extends State<AccountDetailPage> with SingleTicker
|
|||
|
||||
Widget _buildExpenseChart() {
|
||||
return Padding(
|
||||
padding: EdgeInsets.all(8.0),
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Card(
|
||||
elevation: 4.0,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: MonthlyExpensesChart(data: expenseData),
|
||||
),
|
||||
),
|
||||
|
@ -484,13 +659,13 @@ class Transaction {
|
|||
class AddTransactionDialog extends StatefulWidget {
|
||||
final Function addTransaction;
|
||||
|
||||
AddTransactionDialog({required this.addTransaction});
|
||||
const AddTransactionDialog({super.key, required this.addTransaction});
|
||||
|
||||
@override
|
||||
_AddTransactionDialogState createState() => _AddTransactionDialogState();
|
||||
AddTransactionDialogState createState() => AddTransactionDialogState();
|
||||
}
|
||||
|
||||
class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
class AddTransactionDialogState extends State<AddTransactionDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final _titleController = TextEditingController();
|
||||
final _amountController = TextEditingController();
|
||||
|
@ -523,15 +698,38 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: Text('Add Transaction'),
|
||||
content: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextFormField(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(15),
|
||||
),
|
||||
title: Text(
|
||||
'Add Transaction',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
color: Colors.black54,
|
||||
),
|
||||
),
|
||||
content: 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'),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Title',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
return 'Please enter a title';
|
||||
|
@ -539,9 +737,27 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
|||
return null;
|
||||
},
|
||||
),
|
||||
TextFormField(
|
||||
),
|
||||
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'),
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Amount',
|
||||
border: InputBorder.none,
|
||||
contentPadding: EdgeInsets.symmetric(
|
||||
vertical: 12,
|
||||
horizontal: 16,
|
||||
),
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
validator: (value) {
|
||||
if (value!.isEmpty) {
|
||||
|
@ -553,35 +769,71 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
|||
return null;
|
||||
},
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Checkbox(
|
||||
value: _isExpense,
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_isExpense = value!;
|
||||
});
|
||||
},
|
||||
),
|
||||
Text('Expense'),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
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', style: TextStyle(color: Colors.black87),),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
NeumorphicButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text('Cancel'),
|
||||
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',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
NeumorphicButton(
|
||||
onPressed: _submitForm,
|
||||
child: Text('Add'),
|
||||
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',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -589,11 +841,11 @@ class _AddTransactionDialogState extends State<AddTransactionDialog> {
|
|||
class MonthlyExpensesChart extends StatelessWidget {
|
||||
final List<ExpenseData> data;
|
||||
|
||||
MonthlyExpensesChart({required this.data});
|
||||
const MonthlyExpensesChart({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
return SizedBox(
|
||||
height: 300,
|
||||
child: SfCartesianChart(
|
||||
primaryXAxis: CategoryAxis(),
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:charts_flutter/flutter.dart' as charts;
|
||||
|
||||
import 'main.dart';
|
||||
|
||||
class MonthlyExpensesChart extends StatelessWidget {
|
||||
final List<ExpenseData> data;
|
||||
|
||||
MonthlyExpensesChart({required this.data});
|
||||
const MonthlyExpensesChart({super.key, required this.data});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -22,18 +21,18 @@ class MonthlyExpensesChart extends StatelessWidget {
|
|||
|
||||
return Container(
|
||||
height: 200,
|
||||
padding: EdgeInsets.all(16),
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: charts.BarChart(
|
||||
series,
|
||||
animate: true,
|
||||
domainAxis: charts.OrdinalAxisSpec(
|
||||
domainAxis: const charts.OrdinalAxisSpec(
|
||||
renderSpec: charts.SmallTickRendererSpec(
|
||||
labelStyle: charts.TextStyleSpec(
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
primaryMeasureAxis: charts.NumericAxisSpec(
|
||||
primaryMeasureAxis: const charts.NumericAxisSpec(
|
||||
renderSpec: charts.GridlineRendererSpec(
|
||||
labelStyle: charts.TextStyleSpec(
|
||||
fontSize: 12,
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
import 'package:flutter_neumorphic/flutter_neumorphic.dart';
|
||||
import 'package:tests/theme/theme_manager.dart';
|
||||
|
||||
ThemeManager _themeManager = ThemeManager();
|
||||
|
||||
class Settings extends StatefulWidget {
|
||||
const Settings({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
SettingsState createState() => SettingsState();
|
||||
}
|
||||
|
||||
class SettingsState extends State<Settings> {
|
||||
|
||||
String _selectedLanguage = 'English';
|
||||
String _selectedCurrency = 'Euro';
|
||||
|
||||
final List<String> _languages = ['English', 'Deutsch'];
|
||||
final List<String> _currencies = ['Euro', 'Dollar', 'CHF'];
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_themeManager.removeListener(themeListener);
|
||||
super.dispose();
|
||||
}
|
||||
themeListener(){
|
||||
if(mounted){
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_themeManager.addListener(themeListener);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
toolbarHeight: 80,
|
||||
title: Text(
|
||||
'Settings',
|
||||
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: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
children: [
|
||||
Neumorphic(
|
||||
style: NeumorphicStyle(
|
||||
depth: 7,
|
||||
intensity: 1,
|
||||
shadowDarkColor: Colors.grey.shade300,
|
||||
color: Colors.grey.shade100,
|
||||
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)),
|
||||
),
|
||||
child: ListTile(
|
||||
title: const Text('Dark Mode'),
|
||||
trailing: NeumorphicSwitch(
|
||||
value: _themeManager.themeMode == ThemeMode.dark,
|
||||
onChanged: (bool value) {
|
||||
setState(() {
|
||||
_themeManager.toggleTheme(value);
|
||||
});
|
||||
},
|
||||
style: NeumorphicSwitchStyle(
|
||||
lightSource: LightSource.topLeft,
|
||||
thumbShape: NeumorphicShape.concave,
|
||||
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
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Neumorphic(
|
||||
style: NeumorphicStyle(
|
||||
depth: 7,
|
||||
intensity: 1,
|
||||
shadowDarkColor: Colors.grey.shade300,
|
||||
color: Colors.grey.shade100,
|
||||
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)),
|
||||
),
|
||||
child: ListTile(
|
||||
title: const Text('Language'),
|
||||
trailing: DropdownButton<String>(
|
||||
value: _selectedLanguage,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedLanguage = newValue!;
|
||||
// Hier kannst du die Spracheinstellung entsprechend anpassen
|
||||
// z.B. mit einer Funktion, die die App-Sprache ändert.
|
||||
});
|
||||
},
|
||||
items: _languages.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Neumorphic(
|
||||
style: NeumorphicStyle(
|
||||
depth: 7,
|
||||
intensity: 1,
|
||||
shadowDarkColor: Colors.grey.shade300,
|
||||
color: Colors.grey.shade100,
|
||||
boxShape: NeumorphicBoxShape.roundRect(BorderRadius.circular(15)),
|
||||
),
|
||||
child: ListTile(
|
||||
title: const Text('Currency'),
|
||||
trailing: DropdownButton<String>(
|
||||
value: _selectedCurrency,
|
||||
onChanged: (String? newValue) {
|
||||
setState(() {
|
||||
_selectedCurrency = newValue!;
|
||||
// Hier kannst du die Währungseinstellung entsprechend anpassen
|
||||
// z.B. mit einer Funktion, die die Währung ändert.
|
||||
});
|
||||
},
|
||||
items: _currencies.map<DropdownMenuItem<String>>((String value) {
|
||||
return DropdownMenuItem<String>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const COLOR_PRIMARY = Colors.deepOrangeAccent;
|
||||
|
||||
ThemeData lightTheme = ThemeData(
|
||||
brightness: Brightness.light,
|
||||
primaryColor: COLOR_PRIMARY,
|
||||
fontFamily: "Montserrat"
|
||||
|
||||
);
|
||||
|
||||
ThemeData darkTheme = ThemeData(
|
||||
fontFamily: "Montserrat",
|
||||
brightness: Brightness.dark,
|
||||
switchTheme: SwitchThemeData(
|
||||
trackColor: MaterialStateProperty.all<Color>(Colors.grey),
|
||||
thumbColor: MaterialStateProperty.all<Color>(Colors.white),
|
||||
),
|
||||
);
|
|
@ -0,0 +1,14 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class ThemeManager with ChangeNotifier{
|
||||
|
||||
ThemeMode _themeMode = ThemeMode.light;
|
||||
|
||||
get themeMode => _themeMode;
|
||||
|
||||
toggleTheme(bool isDark){
|
||||
_themeMode = isDark?ThemeMode.dark:ThemeMode.light;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
}
|
66
pubspec.lock
66
pubspec.lock
|
@ -9,6 +9,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
awesome_dialog:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: awesome_dialog
|
||||
sha256: "7da175ea284fa5da0a4d0cbdfe835c5b71d30c7b38c1770c0f27f48272ff5a08"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -102,6 +110,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
flutter_neumorphic:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_neumorphic
|
||||
sha256: "02606d937a3ceaa497b8a7c25f3efa95188bf93d77ebf0bd6552e432db4c2ec6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
|
@ -112,8 +128,32 @@ packages:
|
|||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
intl:
|
||||
graphs:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: graphs
|
||||
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
http:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: http_parser
|
||||
sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
intl:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
|
@ -224,6 +264,22 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.2.4"
|
||||
rive:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rive
|
||||
sha256: "55e1f8bf249444545a7c832830d2bbb9adae759193fb879294bc6018b9f0eedd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.3"
|
||||
rive_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: rive_common
|
||||
sha256: "7e17937b790bb2f631767b3d505da8c298309c0a6ab08cd317fa6fe081ed5b63"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.10"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -349,6 +405,14 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.2"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
52
pubspec.yaml
52
pubspec.yaml
|
@ -30,14 +30,15 @@ environment:
|
|||
dependencies:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
intl: ^0.17.0
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.2
|
||||
shared_preferences: ^2.1.2
|
||||
charts_flutter: ^0.12.0
|
||||
syncfusion_flutter_charts: ^21.2.4
|
||||
flutter_neumorphic: ^3.2.0
|
||||
awesome_dialog: ^3.1.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -61,33 +62,24 @@ flutter:
|
|||
# the material Icons class.
|
||||
uses-material-design: true
|
||||
|
||||
# To add assets to your application, add an assets section, like this:
|
||||
# assets:
|
||||
# - images/a_dot_burr.jpeg
|
||||
# - images/a_dot_ham.jpeg
|
||||
|
||||
# An image asset can refer to one or more resolution-specific "variants", see
|
||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/assets-and-images/#from-packages
|
||||
|
||||
# To add custom fonts to your application, add a fonts section here,
|
||||
# in this "flutter" section. Each entry in this list should have a
|
||||
# "family" key with the font family name, and a "fonts" key with a
|
||||
# list giving the asset and other descriptors for the font. For
|
||||
# example:
|
||||
# fonts:
|
||||
# - family: Schyler
|
||||
# fonts:
|
||||
# - asset: fonts/Schyler-Regular.ttf
|
||||
# - asset: fonts/Schyler-Italic.ttf
|
||||
# style: italic
|
||||
# - family: Trajan Pro
|
||||
# fonts:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
#
|
||||
fonts:
|
||||
- family: Montserrat
|
||||
fonts:
|
||||
- asset: lib/fonts/Montserrat-Regular.ttf
|
||||
weight: 400
|
||||
- asset: lib/fonts/Montserrat-Bold.ttf
|
||||
weight: 700
|
||||
- asset: lib/fonts/Montserrat-ExtraBold.ttf
|
||||
weight: 800
|
||||
- asset: lib/fonts/Montserrat-Light.ttf
|
||||
weight: 300
|
||||
- asset: lib/fonts/Montserrat-ExtraLight.ttf
|
||||
weight: 200
|
||||
- asset: lib/fonts/Montserrat-Medium.ttf
|
||||
weight: 500
|
||||
- asset: lib/fonts/Montserrat-SemiBold.ttf
|
||||
weight: 600
|
||||
- asset: lib/fonts/Montserrat-Thin.ttf
|
||||
weight: 100
|
||||
# For details regarding fonts from package dependencies,
|
||||
# see https://flutter.dev/custom-fonts/#from-packages
|
||||
|
|
Loading…
Reference in New Issue