Code Cleanup
parent
5901774fc7
commit
3435b1917c
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
class ExpenseData {
|
||||
final String month;
|
||||
final double amount;
|
||||
|
||||
ExpenseData({required this.month, required this.amount});
|
||||
}
|
735
lib/main.dart
735
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<void> 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<HomePage> {
|
|||
super.initState();
|
||||
_themeManager.addListener(themeListener);
|
||||
loadAccounts();
|
||||
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_themeManager.removeListener(themeListener);
|
||||
super.dispose();
|
||||
}
|
||||
themeListener(){
|
||||
if(mounted){
|
||||
setState(() {
|
||||
|
||||
});
|
||||
themeListener() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> 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<void> saveAccounts() async {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -126,23 +135,20 @@ class HomePageState extends State<HomePage> {
|
|||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -168,7 +174,6 @@ class HomePageState extends State<HomePage> {
|
|||
deleteAccount(accounts[index]);
|
||||
},
|
||||
).show();
|
||||
|
||||
},
|
||||
child: Neumorphic(
|
||||
margin: const EdgeInsets.all(16),
|
||||
|
@ -177,11 +182,13 @@ class HomePageState extends State<HomePage> {
|
|||
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<HomePage> {
|
|||
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<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(
|
||||
child: const Icon(
|
||||
Icons.add,
|
||||
size: 40,
|
||||
size: 60,
|
||||
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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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});
|
||||
}
|
||||
|
|
@ -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<Settings> {
|
||||
|
||||
String _selectedLanguage = 'English';
|
||||
String _selectedCurrency = 'Euro';
|
||||
|
||||
final List<String> _languages = ['English', 'Deutsch'];
|
||||
final List<String> _currencies = ['Euro', 'Dollar', 'CHF'];
|
||||
|
||||
void checkForLanguage(BuildContext context){
|
||||
void checkForLanguage(BuildContext context) {
|
||||
String language = context.locale.toString();
|
||||
if (kDebugMode) {
|
||||
print(language);
|
||||
switch(language){
|
||||
case "en_US": _selectedLanguage = "English"; break;
|
||||
case "de_DE": _selectedLanguage = "Deutsch"; break;
|
||||
}
|
||||
switch (language) {
|
||||
case "en_US":
|
||||
_selectedLanguage = "English";
|
||||
break;
|
||||
case "de_DE":
|
||||
_selectedLanguage = "Deutsch";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -33,11 +39,10 @@ class SettingsState extends State<Settings> {
|
|||
_themeManager.removeListener(themeListener);
|
||||
super.dispose();
|
||||
}
|
||||
themeListener(){
|
||||
if(mounted){
|
||||
setState(() {
|
||||
|
||||
});
|
||||
themeListener() {
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,14 +64,14 @@ class SettingsState extends State<Settings> {
|
|||
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<Settings> {
|
|||
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<Settings> {
|
|||
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<Settings> {
|
|||
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<Settings> {
|
|||
// 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>(
|
||||
value: value,
|
||||
child: Text(value),
|
||||
|
|
|
@ -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<String> 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,10 +86,11 @@ 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'),
|
||||
),
|
||||
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
16
pubspec.lock
16
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:
|
||||
|
|
|
@ -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:
|
||||
|
|
Loading…
Reference in New Issue