Mztj67_#M}W?l>kYSliK<%xAp;0j{!}J0!o7b
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
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..0467bf12aa4d28f374bb26596605a46dcbb3e7c8
GIT binary patch
literal 1418
zcmV;51$Fv~P)q
zKfU)WzW*n(@|xWGCA9ScMt*e9`2kdxPQ&&>|-UCa7_51w+
zLUsW@ZzZSW0y$)Hp~e9%PvP|a03ks1`~K?q{u;6NC8*{AOqIUq{CL&;p56Lf$oQGq
z^={4hPQv)y=I|4n+?>7Fim=dxt1
z2H+Dm+1+fh+IF>G0SjJMkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJT
zkdTm&kdTm&kdTm&kdP`esgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>Tmf`
zm1{eLgw!b{bXkjWbF%dTkTZEJWyWOb##Lfw4EK2}<0d6%>AGS{po>WCOy&f$Tay_>
z?NBlkpo@s-O;0V%Y_Xa-G#_O08q5LR*~F%&)}{}r&L%Sbs8AS4t7Y0NEx*{soY=0MZExqA5XHQkqi#4gW3
zqODM^iyZl;dvf)-bOXtOru(s)Uc7~BFx{w-FK;2{`VA?(g&@3z&bfLFyctOH!cVsF
z7IL=fo-qBndRUm;kAdXR4e6>k-z|21AaN%ubeVrHl*<|s&Ax@W-t?LR(P-24A5=>a
z*R9#QvjzF8n%@1Nw@?CG@6(%>+-0ASK~jEmCV|&a*7-GKT72W<(TbSjf)&Eme6nGE
z>Gkj4Sq&2e+-G%|+NM8OOm5zVl9{Z8Dd8A5z3y8mZ=4Bv4%>as_{9cN#bm~;h>62(
zdqY93Zy}v&c4n($Vv!UybR8ocs7#zbfX1IY-*w~)p}XyZ-SFC~4w>BvMVr`dFbelV{lLL0bx7@*ZZdebr3`sP;?
zVImji)kG)(6Juv0lz@q`F!k1FE;CQ(D0iG$wchPbKZQELlsZ#~rt8#90Y_Xh&3U-<
z{s<&cCV_1`^TD^ia9!*mQDq&
zn2{r`j};V|uV%_wsP!zB?m%;FeaRe+X47K0e+KE!8C{gAWF8)lCd1u1%~|M!XNRvw
zvtqy3iz0WSpWdhn6$hP8PaRBmp)q`#PCA`Vd#Tc$@f1tAcM>f_I@bC)hkI9|o(Iqv
zo}Piadq!j76}004RBio<`)70k^`K1NK)q>w?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF
z34$0Z;QO!JOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUC
YUoZo%k(ykuW&i*H07*qoM6N<$f+CH{y8r+H
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
new file mode 100644
index 0000000..0bedcf2
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json
@@ -0,0 +1,23 @@
+{
+ "images" : [
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage.png",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@2x.png",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "filename" : "LaunchImage@3x.png",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "version" : 1,
+ "author" : "xcode"
+ }
+}
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
new file mode 100644
index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838
GIT binary patch
literal 68
zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J
Q1PU{Fy85}Sb4q9e0B4a5jsO4v
literal 0
HcmV?d00001
diff --git a/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
new file mode 100644
index 0000000..89c2725
--- /dev/null
+++ b/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md
@@ -0,0 +1,5 @@
+# Launch Screen Assets
+
+You can customize the launch screen with your own desired assets by replacing the image files in this directory.
+
+You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
diff --git a/ios/Runner/Base.lproj/LaunchScreen.storyboard b/ios/Runner/Base.lproj/LaunchScreen.storyboard
new file mode 100644
index 0000000..f2e259c
--- /dev/null
+++ b/ios/Runner/Base.lproj/LaunchScreen.storyboard
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Base.lproj/Main.storyboard b/ios/Runner/Base.lproj/Main.storyboard
new file mode 100644
index 0000000..f3c2851
--- /dev/null
+++ b/ios/Runner/Base.lproj/Main.storyboard
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
new file mode 100644
index 0000000..970ce66
--- /dev/null
+++ b/ios/Runner/Info.plist
@@ -0,0 +1,51 @@
+
+
+
+
+ CFBundleDevelopmentRegion
+ $(DEVELOPMENT_LANGUAGE)
+ CFBundleDisplayName
+ Tests
+ CFBundleExecutable
+ $(EXECUTABLE_NAME)
+ CFBundleIdentifier
+ $(PRODUCT_BUNDLE_IDENTIFIER)
+ CFBundleInfoDictionaryVersion
+ 6.0
+ CFBundleName
+ tests
+ CFBundlePackageType
+ APPL
+ CFBundleShortVersionString
+ $(FLUTTER_BUILD_NAME)
+ CFBundleSignature
+ ????
+ CFBundleVersion
+ $(FLUTTER_BUILD_NUMBER)
+ LSRequiresIPhoneOS
+
+ UILaunchStoryboardName
+ LaunchScreen
+ UIMainStoryboardFile
+ Main
+ UISupportedInterfaceOrientations
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UISupportedInterfaceOrientations~ipad
+
+ UIInterfaceOrientationPortrait
+ UIInterfaceOrientationPortraitUpsideDown
+ UIInterfaceOrientationLandscapeLeft
+ UIInterfaceOrientationLandscapeRight
+
+ UIViewControllerBasedStatusBarAppearance
+
+ CADisableMinimumFrameDurationOnPhone
+
+ UIApplicationSupportsIndirectInputEvents
+
+
+
diff --git a/ios/Runner/Runner-Bridging-Header.h b/ios/Runner/Runner-Bridging-Header.h
new file mode 100644
index 0000000..308a2a5
--- /dev/null
+++ b/ios/Runner/Runner-Bridging-Header.h
@@ -0,0 +1 @@
+#import "GeneratedPluginRegistrant.h"
diff --git a/lib/main.dart b/lib/main.dart
new file mode 100644
index 0000000..83fa217
--- /dev/null
+++ b/lib/main.dart
@@ -0,0 +1,614 @@
+import 'dart:convert';
+
+import 'package:flutter/material.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';
+
+void main() {
+
+ runApp(FinancialPlannerApp());
+}
+
+class FinancialPlannerApp extends StatelessWidget {
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'Financial Planner',
+ theme: ThemeData(
+ brightness: Brightness.light,
+ primarySwatch: Colors.blue,
+ ),
+ darkTheme: ThemeData(
+ brightness: Brightness.dark,
+ primarySwatch: Colors.blue,
+ ),
+ home: HomePage(),
+ );
+ }
+}
+
+class HomePage extends StatefulWidget {
+ @override
+ _HomePageState createState() => _HomePageState();
+}
+
+class _HomePageState extends State {
+ List accounts = [];
+
+ @override
+ void initState() {
+ super.initState();
+ loadAccounts();
+ }
+
+ Future loadAccounts() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ final accountList = prefs.getStringList('accounts') ?? [];
+
+ setState(() {
+ accounts = accountList.map((accountJson) => Account.fromJson(accountJson)).toList();
+ });
+ }
+
+ Future saveAccounts() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ List accountJsonList = accounts.map((account) => json.encode(account.toJson())).toList();
+ await prefs.setStringList('accounts', accountJsonList);
+ }
+
+ void addAccount(Account account) {
+ setState(() {
+ accounts.add(account);
+ saveAccounts();
+ });
+ }
+
+ void deleteAccount(Account account) {
+ setState(() {
+ accounts.remove(account);
+ saveAccounts();
+ });
+ }
+
+ void updateAccountBalance(Account account) {
+ setState(() {
+ saveAccounts();
+ });
+ }
+
+ @override
+ 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),
+ 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'),
+ ),
+ ],
+ );
+ },
+ );
+ },
+ ),
+ );
+ },
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () {
+ showDialog(
+ context: context,
+ builder: (BuildContext context) {
+ return AddAccountDialog(
+ addAccount: addAccount,
+ );
+ },
+ );
+ },
+ child: Icon(Icons.add),
+ ),
+ );
+ }
+}
+
+class Account {
+ String name;
+ double balance;
+
+ Account({
+ required this.name,
+ required this.balance,
+ });
+
+ factory Account.fromJson(String json) {
+ final Map map = jsonDecode(json);
+ return Account(
+ name: map['name'],
+ balance: map['balance'],
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'name': name,
+ 'balance': balance,
+ };
+ }
+}
+
+class AddAccountDialog extends StatefulWidget {
+ final Function addAccount;
+
+ AddAccountDialog({required this.addAccount});
+
+ @override
+ _AddAccountDialogState createState() => _AddAccountDialogState();
+}
+
+class _AddAccountDialogState extends State {
+ final _formKey = GlobalKey();
+ final _nameController = TextEditingController();
+ final _balanceController = TextEditingController();
+
+ @override
+ void dispose() {
+ _nameController.dispose();
+ _balanceController.dispose();
+ super.dispose();
+ }
+
+ void _submitForm() {
+ if (_formKey.currentState!.validate()) {
+ String name = _nameController.text.trim();
+ double balance = double.parse(_balanceController.text.trim());
+
+ Account account = Account(
+ name: name,
+ balance: balance,
+ );
+ widget.addAccount(account);
+
+ Navigator.of(context).pop();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text('Add Account'),
+ 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;
+ },
+ ),
+ 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;
+ },
+ ),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: _submitForm,
+ child: Text('Add'),
+ ),
+ ],
+ );
+ }
+}
+
+
+class AccountDetailPage extends StatefulWidget {
+ final Account account;
+ final Function(Account) updateAccountBalance;
+
+ AccountDetailPage({required this.account, required this.updateAccountBalance});
+
+ @override
+ _AccountDetailPageState createState() => _AccountDetailPageState();
+}
+
+class _AccountDetailPageState extends State with SingleTickerProviderStateMixin {
+ late TabController _tabController;
+ List transactions = [];
+ List incomeTransactions = [];
+ List expenseTransactions = [];
+ List expenseData = [];
+
+ @override
+ void initState() {
+ super.initState();
+ _tabController = TabController(length: 2, vsync: this);
+ loadTransactions();
+ }
+
+ @override
+ void dispose() {
+ _tabController.dispose();
+ super.dispose();
+ }
+
+ void loadTransactions() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ String? transactionsJson = prefs.getString(widget.account.name);
+ if (transactionsJson != null) {
+ List decodedJson = jsonDecode(transactionsJson);
+ setState(() {
+ transactions = decodedJson.map((json) => Transaction.fromJson(json)).toList();
+ incomeTransactions = transactions.where((transaction) => !transaction.isExpense).toList();
+ expenseTransactions = transactions.where((transaction) => transaction.isExpense).toList();
+ expenseData = calculateMonthlyExpenses();
+ });
+ }
+ }
+
+ void saveTransactions() async {
+ SharedPreferences prefs = await SharedPreferences.getInstance();
+ List transactionsJsonList = transactions.map((transaction) => json.encode(transaction.toJson())).toList();
+ prefs.setString(widget.account.name, jsonEncode(transactionsJsonList));
+ }
+
+ void addTransaction(Transaction transaction) {
+ setState(() {
+ transactions.add(transaction);
+ if (transaction.isExpense) {
+ widget.account.balance -= transaction.amount;
+ expenseTransactions.add(transaction);
+ } else {
+ widget.account.balance += transaction.amount;
+ incomeTransactions.add(transaction);
+ }
+ saveTransactions();
+ widget.updateAccountBalance(widget.account);
+ expenseData = calculateMonthlyExpenses();
+ });
+ }
+
+ void deleteTransaction(Transaction transaction) {
+ setState(() {
+ transactions.remove(transaction);
+ if (transaction.isExpense) {
+ widget.account.balance += transaction.amount;
+ expenseTransactions.remove(transaction);
+ } else {
+ widget.account.balance -= transaction.amount;
+ incomeTransactions.remove(transaction);
+ }
+ saveTransactions();
+ widget.updateAccountBalance(widget.account);
+ expenseData = calculateMonthlyExpenses();
+ });
+ }
+
+ List calculateMonthlyExpenses() {
+ Map monthlyExpenses = {};
+ for (var transaction in expenseTransactions) {
+ String month = DateFormat('yyyy-MM').format(transaction.date);
+ monthlyExpenses[month] = (monthlyExpenses[month] ?? 0) + transaction.amount;
+ }
+ List expenseData = [];
+ monthlyExpenses.forEach((month, amount) {
+ expenseData.add(ExpenseData(month: month, amount: amount));
+ });
+ return expenseData;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(
+ title: Text(widget.account.name),
+ ),
+ body: Column(
+ children: [
+ TabBar(
+ controller: _tabController,
+ labelColor: Colors.black,
+ tabs: [
+ Tab(text: 'Einnahmen'),
+ Tab(text: 'Ausgaben'),
+ ],
+ ),
+ Expanded(
+ child: TabBarView(
+ controller: _tabController,
+ children: [
+ _buildTransactionsList(incomeTransactions),
+ Column(
+ children: [
+ Expanded(child: _buildTransactionsList(expenseTransactions)),
+ if (expenseTransactions.isNotEmpty) _buildExpenseChart(),
+ ],
+ ),
+ ],
+ ),
+ ),
+ ],
+ ),
+ floatingActionButton: FloatingActionButton(
+ onPressed: () => showDialog(
+ context: context,
+ builder: (_) => AddTransactionDialog(addTransaction: addTransaction),
+ ),
+ child: Icon(Icons.add),
+ ),
+ );
+ }
+
+ Widget _buildTransactionsList(List transactionsList) {
+ return ListView.builder(
+ itemCount: transactionsList.length,
+ itemBuilder: (context, index) {
+ return ListTile(
+ title: Text(transactionsList[index].title),
+ subtitle: Text(transactionsList[index].title),
+ trailing: Text(
+ transactionsList[index].isExpense
+ ? '-\$${transactionsList[index].amount.toStringAsFixed(2)}'
+ : '+\$${transactionsList[index].amount.toStringAsFixed(2)}',
+ style: TextStyle(
+ color: transactionsList[index].isExpense ? Colors.red : Colors.green,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ onLongPress: () => deleteTransaction(transactionsList[index]),
+ );
+ },
+ );
+ }
+
+ Widget _buildExpenseChart() {
+ return Padding(
+ padding: EdgeInsets.all(8.0),
+ child: Card(
+ elevation: 4.0,
+ child: Padding(
+ padding: EdgeInsets.all(16.0),
+ child: MonthlyExpensesChart(data: expenseData),
+ ),
+ ),
+ );
+ }
+}
+
+
+class ExpenseData {
+ final String month;
+ final double amount;
+
+ ExpenseData({required this.month, required this.amount});
+}
+
+
+
+
+class Transaction {
+ String title;
+ double amount;
+ bool isExpense;
+ DateTime date;
+
+ Transaction({
+ required this.title,
+ required this.amount,
+ required this.isExpense,
+ required this.date,
+ });
+
+ factory Transaction.fromJson(String json) {
+ final Map map = jsonDecode(json);
+ return Transaction(
+ title: map['title'],
+ amount: map['amount'],
+ isExpense: map['isExpense'],
+ date: DateTime.parse(map['date']),
+ );
+ }
+
+ Map toJson() {
+ return {
+ 'title': title,
+ 'amount': amount,
+ 'isExpense': isExpense,
+ 'date': date.toString(),
+ };
+ }
+}
+
+class AddTransactionDialog extends StatefulWidget {
+ final Function addTransaction;
+
+ AddTransactionDialog({required this.addTransaction});
+
+ @override
+ _AddTransactionDialogState createState() => _AddTransactionDialogState();
+}
+
+class _AddTransactionDialogState extends State {
+ final _formKey = GlobalKey();
+ final _titleController = TextEditingController();
+ final _amountController = TextEditingController();
+ bool _isExpense = true;
+
+ @override
+ void dispose() {
+ _titleController.dispose();
+ _amountController.dispose();
+ super.dispose();
+ }
+
+ void _submitForm() {
+ if (_formKey.currentState!.validate()) {
+ String title = _titleController.text.trim();
+ double amount = double.parse(_amountController.text.trim());
+
+ Transaction transaction = Transaction(
+ title: title,
+ amount: amount,
+ isExpense: _isExpense,
+ date: DateTime.now(),
+ );
+ widget.addTransaction(transaction);
+
+ Navigator.of(context).pop();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return AlertDialog(
+ title: Text('Add Transaction'),
+ content: Form(
+ key: _formKey,
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ TextFormField(
+ controller: _titleController,
+ decoration: InputDecoration(labelText: 'Title'),
+ validator: (value) {
+ if (value!.isEmpty) {
+ return 'Please enter a title';
+ }
+ return null;
+ },
+ ),
+ TextFormField(
+ controller: _amountController,
+ decoration: InputDecoration(labelText: 'Amount'),
+ keyboardType: TextInputType.number,
+ validator: (value) {
+ if (value!.isEmpty) {
+ return 'Please enter an amount';
+ }
+ if (double.tryParse(value) == null) {
+ return 'Please enter a valid number';
+ }
+ return null;
+ },
+ ),
+ Row(
+ children: [
+ Checkbox(
+ value: _isExpense,
+ onChanged: (value) {
+ setState(() {
+ _isExpense = value!;
+ });
+ },
+ ),
+ Text('Expense'),
+ ],
+ ),
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () {
+ Navigator.of(context).pop();
+ },
+ child: Text('Cancel'),
+ ),
+ TextButton(
+ onPressed: _submitForm,
+ child: Text('Add'),
+ ),
+ ],
+ );
+ }
+}
+
+
+class MonthlyExpensesChart extends StatelessWidget {
+ final List data;
+
+ MonthlyExpensesChart({required this.data});
+
+ @override
+ Widget build(BuildContext context) {
+ return Container(
+ height: 300,
+ child: SfCartesianChart(
+ primaryXAxis: CategoryAxis(),
+ series: [
+ ColumnSeries(
+ dataSource: data,
+ xValueMapper: (ExpenseData expense, _) => expense.month,
+ yValueMapper: (ExpenseData expense, _) => expense.amount,
+ color: Colors.blue,
+ ),
+ ],
+ ),
+ );
+ }
+}
+
+
+
diff --git a/lib/monthly_expenses.dart b/lib/monthly_expenses.dart
new file mode 100644
index 0000000..48da208
--- /dev/null
+++ b/lib/monthly_expenses.dart
@@ -0,0 +1,54 @@
+import 'package:flutter/material.dart';
+import 'package:charts_flutter/flutter.dart' as charts;
+
+import 'main.dart';
+
+class MonthlyExpensesChart extends StatelessWidget {
+ final List data;
+
+ MonthlyExpensesChart({required this.data});
+
+ @override
+ Widget build(BuildContext context) {
+ List> series = [
+ charts.Series(
+ id: 'Expenses',
+ data: data,
+ domainFn: (ExpenseData expense, _) => expense.month,
+ measureFn: (ExpenseData expense, _) => expense.amount,
+ colorFn: (_, __) => charts.MaterialPalette.blue.shadeDefault,
+ ),
+ ];
+
+ return Container(
+ height: 200,
+ padding: EdgeInsets.all(16),
+ child: charts.BarChart(
+ series,
+ animate: true,
+ domainAxis: charts.OrdinalAxisSpec(
+ renderSpec: charts.SmallTickRendererSpec(
+ labelStyle: charts.TextStyleSpec(
+ fontSize: 12,
+ ),
+ ),
+ ),
+ primaryMeasureAxis: charts.NumericAxisSpec(
+ renderSpec: charts.GridlineRendererSpec(
+ labelStyle: charts.TextStyleSpec(
+ fontSize: 12,
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class ExpenseData {
+ final String month;
+ final double amount;
+
+ ExpenseData({required this.month, required this.amount});
+}
+
diff --git a/pubspec.lock b/pubspec.lock
new file mode 100644
index 0000000..d0c6fe0
--- /dev/null
+++ b/pubspec.lock
@@ -0,0 +1,378 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.10.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ charts_common:
+ dependency: transitive
+ description:
+ name: charts_common
+ sha256: "7b8922f9b0d9b134122756a787dab1c3946ae4f3fc5022ff323ba0014998ea02"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.0"
+ charts_flutter:
+ dependency: "direct main"
+ description:
+ name: charts_flutter
+ sha256: "4172c3f4b85322fdffe1896ffbed79ae4689ae72cb6fe6690dcaaea620a9c558"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.0"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.17.0"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: e35129dc44c9118cee2a5603506d823bab99c68393879edb440e0090d07586be
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.5"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.1"
+ ffi:
+ dependency: transitive
+ description:
+ name: ffi
+ sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.2"
+ file:
+ dependency: transitive
+ description:
+ name: file
+ sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d"
+ url: "https://pub.dev"
+ source: hosted
+ version: "6.1.4"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_web_plugins:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ intl:
+ dependency: transitive
+ description:
+ name: intl
+ sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.17.0"
+ js:
+ dependency: transitive
+ description:
+ name: js
+ sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.5"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.1"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.12.13"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.2.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.8.2"
+ path_provider_linux:
+ dependency: transitive
+ description:
+ name: path_provider_linux
+ sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.11"
+ path_provider_platform_interface:
+ dependency: transitive
+ description:
+ name: path_provider_platform_interface
+ sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.6"
+ path_provider_windows:
+ dependency: transitive
+ description:
+ name: path_provider_windows
+ sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.7"
+ platform:
+ dependency: transitive
+ description:
+ name: platform
+ sha256: "4a451831508d7d6ca779f7ac6e212b4023dd5a7d08a27a63da33756410e32b76"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.1.0"
+ plugin_platform_interface:
+ dependency: transitive
+ description:
+ name: plugin_platform_interface
+ sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ process:
+ dependency: transitive
+ description:
+ name: process
+ sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.2.4"
+ shared_preferences:
+ dependency: "direct main"
+ description:
+ name: shared_preferences
+ sha256: "396f85b8afc6865182610c0a2fc470853d56499f75f7499e2a73a9f0539d23d0"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.2"
+ shared_preferences_android:
+ dependency: transitive
+ description:
+ name: shared_preferences_android
+ sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ shared_preferences_foundation:
+ dependency: transitive
+ description:
+ name: shared_preferences_foundation
+ sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.2"
+ shared_preferences_linux:
+ dependency: transitive
+ description:
+ name: shared_preferences_linux
+ sha256: "9d387433ca65717bbf1be88f4d5bb18f10508917a8fa2fb02e0fd0d7479a9afa"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ shared_preferences_platform_interface:
+ dependency: transitive
+ description:
+ name: shared_preferences_platform_interface
+ sha256: fb5cf25c0235df2d0640ac1b1174f6466bd311f621574997ac59018a6664548d
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ shared_preferences_web:
+ dependency: transitive
+ description:
+ name: shared_preferences_web
+ sha256: "74083203a8eae241e0de4a0d597dbedab3b8fef5563f33cf3c12d7e93c655ca5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.0"
+ shared_preferences_windows:
+ dependency: transitive
+ description:
+ name: shared_preferences_windows
+ sha256: "5e588e2efef56916a3b229c3bfe81e6a525665a454519ca51dbcc4236a274173"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.2.0"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.9.1"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: c3c7d8edb15bee7f0f74debd4b9c5f3c2ea86766fe4178eb2a18eb30a0bdaed5
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.11.0"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: "83615bee9045c1d322bbbd1ba209b7a749c2cbcdcb3fdd1df8eb488b3279c1c8"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.1"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.0"
+ syncfusion_flutter_charts:
+ dependency: "direct main"
+ description:
+ name: syncfusion_flutter_charts
+ sha256: bdb7cc5814ceb187793cea587f4a5946afcffd96726b219cee79df8460f44b7b
+ url: "https://pub.dev"
+ source: hosted
+ version: "21.2.4"
+ syncfusion_flutter_core:
+ dependency: transitive
+ description:
+ name: syncfusion_flutter_core
+ sha256: "4aa69e3541d38810533b1455aa38997a89755f9928e16ef813b62b2e8bcc398c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "21.2.9"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.4.16"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.1.4"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
+ url: "https://pub.dev"
+ source: hosted
+ version: "4.1.4"
+ xdg_directories:
+ dependency: transitive
+ description:
+ name: xdg_directories
+ sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.0"
+sdks:
+ dart: ">=2.19.6 <3.0.0"
+ flutter: ">=3.3.0"
diff --git a/pubspec.yaml b/pubspec.yaml
new file mode 100644
index 0000000..1ac76d9
--- /dev/null
+++ b/pubspec.yaml
@@ -0,0 +1,93 @@
+name: tests
+description: A new Flutter project.
+# The following line prevents the package from being accidentally published to
+# pub.dev using `flutter pub publish`. This is preferred for private packages.
+publish_to: 'none' # Remove this line if you wish to publish to pub.dev
+
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# In Android, build-name is used as versionName while build-number used as versionCode.
+# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
+# Read more about iOS versioning at
+# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
+version: 1.0.0+1
+
+environment:
+ sdk: '>=2.19.6 <3.0.0'
+
+# Dependencies specify other packages that your package needs in order to work.
+# To automatically upgrade your package dependencies to the latest versions
+# consider running `flutter pub upgrade --major-versions`. Alternatively,
+# dependencies can be manually updated by changing the version numbers below to
+# the latest version available on pub.dev. To see which dependencies have newer
+# versions available, run `flutter pub outdated`.
+dependencies:
+ flutter:
+ sdk: flutter
+
+
+ # 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
+
+dev_dependencies:
+ flutter_test:
+ sdk: flutter
+
+ # The "flutter_lints" package below contains a set of recommended lints to
+ # encourage good coding practices. The lint set provided by the package is
+ # activated in the `analysis_options.yaml` file located at the root of your
+ # package. See that file for information about deactivating specific lint
+ # rules and activating additional ones.
+ flutter_lints: ^2.0.0
+
+# For information on the generic Dart part of this file, see the
+# following page: https://dart.dev/tools/pub/pubspec
+
+# The following section is specific to Flutter packages.
+flutter:
+
+ # The following line ensures that the Material Icons font is
+ # included with your application, so that you can use the icons in
+ # 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
+ #
+ # For details regarding fonts from package dependencies,
+ # see https://flutter.dev/custom-fonts/#from-packages
diff --git a/test/widget_test.dart b/test/widget_test.dart
new file mode 100644
index 0000000..86b6bdf
--- /dev/null
+++ b/test/widget_test.dart
@@ -0,0 +1,30 @@
+// This is a basic Flutter widget test.
+//
+// To perform an interaction with a widget in your test, use the WidgetTester
+// utility in the flutter_test package. For example, you can send tap and scroll
+// gestures. You can also use WidgetTester to find child widgets in the widget
+// tree, read text, and verify that the values of widget properties are correct.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+import 'package:tests/main.dart';
+
+void main() {
+ testWidgets('Counter increments smoke test', (WidgetTester tester) async {
+ // Build our app and trigger a frame.
+ await tester.pumpWidget(const MyApp());
+
+ // Verify that our counter starts at 0.
+ expect(find.text('0'), findsOneWidget);
+ expect(find.text('1'), findsNothing);
+
+ // Tap the '+' icon and trigger a frame.
+ await tester.tap(find.byIcon(Icons.add));
+ await tester.pump();
+
+ // Verify that our counter has incremented.
+ expect(find.text('0'), findsNothing);
+ expect(find.text('1'), findsOneWidget);
+ });
+}