diff --git a/lib/expenses.dart b/lib/expenses.dart deleted file mode 100644 index e69de29..0000000 diff --git a/lib/main.dart b/lib/main.dart index cb5e175..00cc6ed 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:expense_tracker/widgets/expenses.dart'; void main() { - runApp(MaterialApp(home: // Todo,),); - } + runApp( + MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Expenses(), + ), + ); +} diff --git a/lib/models/expense.dart b/lib/models/expense.dart new file mode 100644 index 0000000..b7741fa --- /dev/null +++ b/lib/models/expense.dart @@ -0,0 +1,35 @@ +import 'package:flutter/material.dart'; + +import 'package:uuid/uuid.dart'; +import 'package:intl/intl.dart'; + +final formatter = DateFormat('dd.MM.yyyy'); +const uuid = Uuid(); + +enum Category { foot, travel, leisure, work } + +const categoryIcons = { + Category.foot: Icons.lunch_dining, + Category.travel: Icons.flight_takeoff, + Category.leisure: Icons.movie, + Category.work: Icons.work, +}; + +class Expense { + Expense({ + required this.title, + required this.amount, + required this.date, + required this.category, + }) : id = uuid.v4(); + + final String id; + final String title; + final double amount; + final DateTime date; + final Category category; + + String get formattedDate { + return formatter.format(date); + } +} diff --git a/lib/widgets/expenses.dart b/lib/widgets/expenses.dart new file mode 100644 index 0000000..22ab843 --- /dev/null +++ b/lib/widgets/expenses.dart @@ -0,0 +1,58 @@ +import 'package:expense_tracker/widgets/expenses_list/expenses_list.dart'; +import 'package:expense_tracker/widgets/new_expense.dart'; +import 'package:flutter/material.dart'; +import 'package:expense_tracker/models/expense.dart'; + +class Expenses extends StatefulWidget { + const Expenses({super.key}); + + @override + State createState() { + return _ExpensesState(); + } +} + +class _ExpensesState extends State { + final List _registeredExpenses = [ + Expense( + title: 'Flutter Course', + amount: 19.99, + date: DateTime.now(), + category: Category.work, + ), + Expense( + title: 'Cinema', + amount: 15.69, + date: DateTime.now(), + category: Category.leisure, + ), + ]; + + void _openAddExpenseOverlay() { + showModalBottomSheet( + context: context, builder: (ctx) => const NewExpense()); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Flutter ExpenseTracker'), + actions: [ + IconButton( + onPressed: _openAddExpenseOverlay, + icon: const Icon(Icons.add), + ), + ], + ), + body: Column( + children: [ + const Text('The chart'), + Expanded( + child: ExpensesList(expenses: _registeredExpenses), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/expenses_list/expense_item.dart b/lib/widgets/expenses_list/expense_item.dart new file mode 100644 index 0000000..6b53c31 --- /dev/null +++ b/lib/widgets/expenses_list/expense_item.dart @@ -0,0 +1,39 @@ +import 'package:expense_tracker/models/expense.dart'; +import 'package:flutter/material.dart'; + +class ExpenseItem extends StatelessWidget { + const ExpenseItem(this.expense, {super.key}); + + final Expense expense; + + @override + Widget build(BuildContext context) { + return Card( + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), + child: Column( + children: [ + Text(expense.title), + const SizedBox(height: 4), + Row( + children: [ + Text('\$${expense.amount.toStringAsFixed(2)}'), + const Spacer(), + Row( + children: [ + Icon(categoryIcons[expense.category]), + const SizedBox(width: 8), + Text(expense.formattedDate), + ], + ), + ], + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/expenses_list/expenses_list.dart b/lib/widgets/expenses_list/expenses_list.dart new file mode 100644 index 0000000..eb346a0 --- /dev/null +++ b/lib/widgets/expenses_list/expenses_list.dart @@ -0,0 +1,20 @@ +import 'package:expense_tracker/widgets/expenses_list/expense_item.dart'; +import 'package:flutter/material.dart'; +import 'package:expense_tracker/models/expense.dart'; + +class ExpensesList extends StatelessWidget { + const ExpensesList({ + super.key, + required this.expenses, + }); + + final List expenses; + + @override + Widget build(BuildContext context) { + return ListView.builder( + itemCount: expenses.length, + itemBuilder: (ctx, index) => ExpenseItem(expenses[index]), + ); + } +} diff --git a/lib/widgets/new_expense.dart b/lib/widgets/new_expense.dart new file mode 100644 index 0000000..4a18f19 --- /dev/null +++ b/lib/widgets/new_expense.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +final formatter = DateFormat('dd.MM.yyyy'); + +class NewExpense extends StatefulWidget { + const NewExpense({super.key}); + + @override + State createState() { + return _NewExpenseState(); + } +} + +class _NewExpenseState extends State { + final _titleController = TextEditingController(); + final _amountController = TextEditingController(); + DateTime? _selectedDate; + + void _presentDatePicker() async { + final now = DateTime.now(); + final firstDate = DateTime(now.year - 1, now.month, now.day); + final pickedDate = await showDatePicker( + context: context, + initialDate: now, + firstDate: firstDate, + lastDate: now, + ); + setState(() { + _selectedDate = pickedDate; + }); + } + + @override + void dispose() { + _titleController.dispose(); + _amountController.dispose(); + super.dispose(); + } + + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(16), + child: Column( + children: [ + TextField( + controller: _titleController, + maxLength: 50, + decoration: const InputDecoration( + label: Text('Title'), + ), + ), + Row( + children: [ + Expanded( + child: TextField( + controller: _amountController, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + decoration: const InputDecoration( + prefix: Text('\$ '), + label: Text('Amount'), + ), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + _selectedDate == null + ? 'No date selected' + : formatter.format(_selectedDate!), + ), + IconButton( + onPressed: _presentDatePicker, + icon: Icon(Icons.calendar_month), + ), + ], + ), + ) + ], + ), + Row( + children: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel'), + ), + ElevatedButton( + onPressed: () { + print(_titleController.text); + print(_amountController.text); + }, + child: const Text('Save Expense'), + ), + ], + ), + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index e4a8596..b7cfda4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + crypto: + dependency: transitive + description: + name: crypto + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab + url: "https://pub.dev" + source: hosted + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -66,23 +82,31 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 + sha256: e2a421b7e59244faef694ba7b30562e489c2b489866e505074eb005cd7060db7 url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.0.1" flutter_test: dependency: "direct dev" description: flutter source: sdk version: "0.0.0" + intl: + dependency: "direct main" + description: + name: intl + sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + url: "https://pub.dev" + source: hosted + version: "0.19.0" lints: dependency: transitive description: name: lints - sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + sha256: cbf8d4b858bb0134ef3ef87841abdf8d63bfc255c266b7bf6b39daa1085c4290 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "3.0.0" matcher: dependency: transitive description: @@ -128,6 +152,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" stack_trace: dependency: transitive description: @@ -168,6 +200,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + typed_data: + dependency: transitive + description: + name: typed_data + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + url: "https://pub.dev" + source: hosted + version: "1.3.2" + uuid: + dependency: "direct main" + description: + name: uuid + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 + url: "https://pub.dev" + source: hosted + version: "4.3.3" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 86ea6b3..de9bc72 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -35,6 +35,8 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 + uuid: ^4.3.3 + intl: ^0.19.0 dev_dependencies: flutter_test: @@ -45,7 +47,7 @@ dev_dependencies: # 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 + flutter_lints: ^3.0.1 # For information on the generic Dart part of this file, see the # following page: https://dart.dev/tools/pub/pubspec