diff --git a/README.md b/README.md index 2944976..670ce11 100644 --- a/README.md +++ b/README.md @@ -45,11 +45,15 @@ flutter run ## 📱 Screenshots -

-Add screenshots 👀 +### Main Screen +![Main Screen](./lib/screenshots/main%20screen.png) +### Add Expense Screen +![Add Expense Screen](./lib/screenshots/add%20expense.png) + +### Splash Screen +![Splash Screen](./lib/screenshots/splash%20screen.png) -

## Custom App Icon 🎨 **-** Icons are located in: diff --git a/lib/models/expense.dart b/lib/models/expense.dart index ed62d24..e1f18c5 100644 --- a/lib/models/expense.dart +++ b/lib/models/expense.dart @@ -22,9 +22,7 @@ const categoryIcons = { class Expense { Expense( - { - // required this.id, - required this.title, + {required this.title, required this.amount, required this.date, required this.category}) @@ -39,6 +37,28 @@ class Expense { String get formattedDate { return formatter.format(date); } + + Map toMap() { + return { + 'id': id, + 'title': title, + 'amount': amount, + 'date': date.toIso8601String(), + 'category': category.toString(), + }; + } + + factory Expense.fromMap(Map map) { + return Expense( + title: map['title'], + amount: map['amount'], + date: DateTime.parse(map['date']), + category: Category.values.firstWhere( + (e) => e.toString() == map['category'], + orElse: () => Category.leisure, + ), + ); + } } class ExpenseBucket { @@ -48,7 +68,6 @@ class ExpenseBucket { : expenses = allExpenses .where((expense) => expense.category == category) .toList(); - // to filter all expenses according to category final Category category; final List expenses; @@ -62,4 +81,4 @@ class ExpenseBucket { return sum; } -} +} \ No newline at end of file diff --git a/lib/widgets/expenses.dart b/lib/widgets/expenses.dart index e884359..b26c232 100644 --- a/lib/widgets/expenses.dart +++ b/lib/widgets/expenses.dart @@ -2,7 +2,9 @@ import 'package:expense_tracker/widgets/new_expense.dart'; import 'package:flutter/material.dart'; import 'package:expense_tracker/models/expense.dart'; import 'package:expense_tracker/widgets/expenses_list/expenses_list.dart'; -import 'chart/chart.dart'; +import 'package:expense_tracker/widgets/chart/chart.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:convert'; class Expenses extends StatefulWidget { const Expenses({ @@ -16,28 +18,37 @@ class Expenses extends StatefulWidget { } class _ExpensesState extends State { - // Issue 1: Hardcoded initial expenses that don't reflect real usage - final List _registeredExpenses = [ - Expense( - title: 'Flutter course', - amount: 19.99, - date: DateTime.now(), - category: Category.work, - ), - Expense( - title: 'Movie night', - amount: 5, - date: DateTime.now(), - category: Category.leisure, - ), - ]; + final List _registeredExpenses = []; + + @override + void initState() { + super.initState(); + _loadExpenses(); + } + + Future _loadExpenses() async { + final prefs = await SharedPreferences.getInstance(); + final expenseList = prefs.getStringList('expenses') ?? []; + setState(() { + _registeredExpenses.clear(); + for (final expenseString in expenseList) { + final expenseMap = jsonDecode(expenseString) as Map; + _registeredExpenses.add(Expense.fromMap(expenseMap)); + } + }); + } + + Future _saveExpenses() async { + final prefs = await SharedPreferences.getInstance(); + final expenseList = _registeredExpenses.map((expense) => jsonEncode(expense.toMap())).toList(); + await prefs.setStringList('expenses', expenseList); + } - // Issue 2: No validation for duplicate expenses void _addExpense(Expense expense) { setState(() { _registeredExpenses.add(expense); }); - // Issue 3: Fixed duration for all notifications + _saveExpenses(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( duration: const Duration(seconds: 3), content: const Text('Expense Added!'), @@ -54,12 +65,12 @@ class _ExpensesState extends State { setState(() { _registeredExpenses.remove(expense); }); + _saveExpenses(); ScaffoldMessenger.of(context).clearSnackBars(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( duration: const Duration(seconds: 3), content: const Text('Expense deleted.'), - behavior: SnackBarBehavior - .floating, // makes it float instead of stick to bottom + behavior: SnackBarBehavior.floating, margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 10), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), @@ -70,9 +81,18 @@ class _ExpensesState extends State { setState(() { _registeredExpenses.insert(expenseIndex, expense); }); + _saveExpenses(); }))); } + void _updateExpense(Expense originalExpense, Expense newExpense) { + final expenseIndex = _registeredExpenses.indexOf(originalExpense); + setState(() { + _registeredExpenses[expenseIndex] = newExpense; + }); + _saveExpenses(); + } + void _openAddExpenseOverlay() { showModalBottomSheet( context: context, @@ -81,11 +101,23 @@ class _ExpensesState extends State { ); } + void _openEditExpenseOverlay(Expense expense) { + showModalBottomSheet( + context: context, + isScrollControlled: true, + builder: (ctx) => NewExpense( + onAddExpense: (newExpense) { + _updateExpense(expense, newExpense); + }, + expense: expense, + ), + ); + } + @override Widget build(BuildContext context) { final width = MediaQuery.of(context).size.width; - // Issue 4: Hardcoded breakpoint for responsive layout Widget mainContent = const Center(child: Text("No Expenses added. \n Add Something?")); @@ -93,21 +125,20 @@ class _ExpensesState extends State { mainContent = ExpensesList( expenses: _registeredExpenses, onRemoveExpense: _removeExpense, + onEditExpense: _openEditExpenseOverlay, ); } return Scaffold( appBar: AppBar( - // Issue 5: Hardcoded text styles in AppBar - title: const Text( + title: Text( 'Expenses Tracker', - style: TextStyle(fontSize: 23, fontWeight: FontWeight.bold), + style: Theme.of(context).textTheme.titleLarge, ), actions: [ IconButton( onPressed: _openAddExpenseOverlay, icon: const Icon(Icons.add)) ], ), - // Issue 6: Fixed width breakpoint for layout switching body: width < 600 ? Column( children: [ @@ -128,4 +159,4 @@ class _ExpensesState extends State { ], )); } -} +} \ No newline at end of file diff --git a/lib/widgets/expenses_list/expense_item.dart b/lib/widgets/expenses_list/expense_item.dart index 6c336da..21172ae 100644 --- a/lib/widgets/expenses_list/expense_item.dart +++ b/lib/widgets/expenses_list/expense_item.dart @@ -1,44 +1,50 @@ import 'package:flutter/material.dart'; import 'package:expense_tracker/models/expense.dart'; -// import 'expense_item.dart'; class ExpenseItem extends StatelessWidget { - const ExpenseItem(this.expense, {super.key}); + const ExpenseItem(this.expense, {super.key, this.onEdit}); final Expense expense; + final VoidCallback? onEdit; @override Widget build(BuildContext context) { return Card( child: Padding( - // Issue 1: Excessive padding making cards too large - padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 36), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 16, + ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Issue 2: Hardcoded text style instead of using theme - Text(expense.title, - style: - const TextStyle(fontSize: 20, fontWeight: FontWeight.bold) - // Theme.of(context).textTheme.titleLarge - ), - // Issue 3: Small spacing between elements + Text( + expense.title, + style: Theme.of(context).textTheme.titleLarge, + ), const SizedBox(height: 4), - Row(children: [ - // Issue 4: No currency symbol localization - Text('\$${expense.amount.toStringAsFixed(2)}'), - const Spacer(), - Row( - children: [ - Icon(categoryIcons[expense.category]), - // Issue 5: Large gap between icon and date - const SizedBox(width: 20), - Text(expense.formattedDate) - ], - ) - ]) + Row( + children: [ + Text( + '\$${expense.amount.toStringAsFixed(2)}', + ), + const Spacer(), + Row( + children: [ + Icon(categoryIcons[expense.category]), + const SizedBox(width: 8), + Text(expense.formattedDate), + ], + ), + if (onEdit != null) + IconButton( + icon: const Icon(Icons.edit), + onPressed: onEdit, + ), + ], + ), ], ), ), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/expenses_list/expenses_list.dart b/lib/widgets/expenses_list/expenses_list.dart index 0ebc17a..285dc9a 100644 --- a/lib/widgets/expenses_list/expenses_list.dart +++ b/lib/widgets/expenses_list/expenses_list.dart @@ -7,18 +7,18 @@ class ExpensesList extends StatelessWidget { super.key, required this.expenses, required this.onRemoveExpense, + required this.onEditExpense, }); final List expenses; final void Function(Expense expense) onRemoveExpense; + final void Function(Expense expense) onEditExpense; @override Widget build(BuildContext context) { - Future.delayed(const Duration(milliseconds: 800)); - final reversedExpenses = expenses.reversed.toList(); return ListView.builder( - itemCount: expenses.length > 5 ? 5 : expenses.length, + itemCount: expenses.length, itemBuilder: (context, index) => Dismissible( key: ValueKey(reversedExpenses[index]), background: Container( @@ -48,7 +48,10 @@ class ExpensesList extends StatelessWidget { onDismissed: (direction) { onRemoveExpense(reversedExpenses[index]); }, - child: ExpenseItem(reversedExpenses[index])), + child: ExpenseItem( + reversedExpenses[index], + onEdit: () => onEditExpense(reversedExpenses[index]), + )), ); } -} +} \ No newline at end of file diff --git a/lib/widgets/new_expense.dart b/lib/widgets/new_expense.dart index a569840..bd76bc0 100644 --- a/lib/widgets/new_expense.dart +++ b/lib/widgets/new_expense.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; import 'package:expense_tracker/models/expense.dart'; -import 'package:flutter/rendering.dart'; class NewExpense extends StatefulWidget { - const NewExpense({super.key, required this.onAddExpense}); + const NewExpense({super.key, required this.onAddExpense, this.expense}); final void Function(Expense expense) onAddExpense; + final Expense? expense; @override State createState() { @@ -19,6 +19,17 @@ class _NewExpenseState extends State { DateTime? _selectedDate; Category _selectedCategory = Category.leisure; + @override + void initState() { + super.initState(); + if (widget.expense != null) { + _titleController.text = widget.expense!.title; + _amountController.text = widget.expense!.amount.toString(); + _selectedDate = widget.expense!.date; + _selectedCategory = widget.expense!.category; + } + } + void _presentDatePicker() async { final now = DateTime.now(); final firstDate = DateTime(now.year - 25, now.month, now.day); @@ -46,7 +57,7 @@ class _NewExpenseState extends State { builder: (ctx) => AlertDialog( title: const Text('Invalid Input!!'), content: const Text( - 'Please enter valid title, amount, date and category!.'), //what is input is invalid? 👀 + 'Please enter valid title, amount, date and category!.'), actions: [ TextButton( onPressed: () { @@ -77,9 +88,6 @@ class _NewExpenseState extends State { @override Widget build(BuildContext context) { final keyboardSpace = MediaQuery.of(context).viewInsets.bottom; - // return LayoutBuilder(builder: (ctx, constraints){ - // return - // }); return SizedBox( height: double.infinity, @@ -132,7 +140,11 @@ class _NewExpenseState extends State { _selectedCategory = value; }); }), - TextButton(onPressed: () {}, child: const Text('Cancel')), + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel')), ElevatedButton( onPressed: _submitExpenseData, child: const Text('Save Expense')) @@ -142,4 +154,4 @@ class _NewExpenseState extends State { ), ); } -} +} \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index bc3bcd5..c90d161 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "2.0.3" cli_util: dependency: transitive description: @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -105,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + file: + dependency: transitive + description: + name: file + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 + url: "https://pub.dev" + source: hosted + version: "7.0.1" fixnum: dependency: transitive description: @@ -139,6 +147,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" image: dependency: transitive description: @@ -167,26 +180,26 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "11.0.2" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1" + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 url: "https://pub.dev" source: hosted - version: "3.0.10" + version: "3.0.9" leak_tracker_testing: dependency: transitive description: name: leak_tracker_testing - sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1" + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.1" lints: dependency: transitive description: @@ -227,14 +240,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "6.1.0" + platform: + dependency: transitive + description: + name: platform + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" + url: "https://pub.dev" + source: hosted + version: "3.1.6" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02" + url: "https://pub.dev" + source: hosted + version: "2.1.8" posix: dependency: transitive description: @@ -243,6 +296,62 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + url: "https://pub.dev" + source: hosted + version: "2.4.13" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + url: "https://pub.dev" + source: hosted + version: "2.5.4" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -300,10 +409,10 @@ packages: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.4" typed_data: dependency: transitive description: @@ -324,10 +433,10 @@ packages: dependency: transitive description: name: vector_math - sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.1.4" vm_service: dependency: transitive description: @@ -336,14 +445,30 @@ packages: url: "https://pub.dev" source: hosted version: "14.3.1" + web: + dependency: transitive + description: + name: web + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" + url: "https://pub.dev" + source: hosted + version: "1.1.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" xml: dependency: transitive description: name: xml - sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025" + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.5.0" yaml: dependency: transitive description: @@ -353,5 +478,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 12c5409..77ab4b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,57 +1,26 @@ name: expense_tracker 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 +publish_to: 'none' -# 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: ^3.5.3 + sdk: '>=3.0.6 <4.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.8 uuid: ^4.5.1 intl: ^0.20.2 + shared_preferences: ^2.2.3 dev_dependencies: flutter_test: sdk: flutter - flutter_launcher_icons: ^0.13.1 - - # 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_launcher_icons: "^0.13.1" flutter_lints: ^4.0.0 -# The following section is for app icon. flutter_launcher_icons: android: true ios: true @@ -59,45 +28,5 @@ flutter_launcher_icons: adaptive_icon_background: "#FFFFFF" adaptive_icon_foreground: "lib/app_icon/5501371.png" - -# 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/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # 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/to/font-from-package + uses-material-design: true \ No newline at end of file