diff --git a/.flutter b/.flutter index adc7dfe..0b591f2 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit adc7dfe87ebc5ff6cb6ceb786efaa83058fdc1a1 +Subproject commit 0b591f2c82e9f59276ed68c7d4cbd63196f7c865 diff --git a/android/app/build.gradle b/android/app/build.gradle index 840b587..7551c3f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -30,7 +30,7 @@ if (keystorePropertiesFile.exists()) { android { namespace "cafe.caras.prasule" - compileSdkVersion 33 + compileSdkVersion 34 ndkVersion flutter.ndkVersion compileOptions { diff --git a/lib/api/wallet.dart b/lib/api/wallet.dart index 6d88efd..05e2d0a 100644 --- a/lib/api/wallet.dart +++ b/lib/api/wallet.dart @@ -12,7 +12,7 @@ class Wallet { final String name; final List categories; final List entries; - double availableAmount; + double starterBalance; @JsonKey(fromJson: _currencyFromJson) final Currency currency; @@ -21,7 +21,7 @@ class Wallet { required this.currency, this.categories = const [], this.entries = const [], - this.availableAmount = 0}); + this.starterBalance = 0}); /// Connect the generated [_$WalletEntry] function to the `fromJson` /// factory. @@ -38,4 +38,23 @@ class Wallet { } return id; } + + static final Wallet empty = Wallet( + name: "Empty", + currency: Currency.from( + json: { + "code": "USD", + "name": "United States Dollar", + "symbol": "\$", + "flag": "USD", + "decimal_digits": 2, + "number": 840, + "name_plural": "US dollars", + "thousands_separator": ",", + "decimal_separator": ".", + "space_between_amount_and_symbol": false, + "symbol_on_left": true, + }, + ), + ); } diff --git a/lib/api/wallet.g.dart b/lib/api/wallet.g.dart index f3f7243..d2affe5 100644 --- a/lib/api/wallet.g.dart +++ b/lib/api/wallet.g.dart @@ -18,13 +18,13 @@ Wallet _$WalletFromJson(Map json) => Wallet( (e) => WalletSingleEntry.fromJson(e as Map)) .toList() ?? const [], - availableAmount: (json['availableAmount'] as num?)?.toDouble() ?? 0, + starterBalance: (json['starterBalance'] as num?)?.toDouble() ?? 0, ); Map _$WalletToJson(Wallet instance) => { 'name': instance.name, 'categories': instance.categories, 'entries': instance.entries, - 'availableAmount': instance.availableAmount, + 'starterBalance': instance.starterBalance, 'currency': instance.currency, }; diff --git a/lib/api/walletmanager.dart b/lib/api/walletmanager.dart index d4432fc..c8f2823 100644 --- a/lib/api/walletmanager.dart +++ b/lib/api/walletmanager.dart @@ -13,7 +13,11 @@ class WalletManager { } var wallets = []; for (var w in path.listSync().map((e) => e.path.split("/").last).toList()) { - wallets.add(await loadWallet(w)); + try { + wallets.add(await loadWallet(w)); + } catch (e) { + // TODO: do something with unreadable wallets + } } return wallets; } @@ -38,7 +42,7 @@ class WalletManager { if (!path.existsSync()) { path.createSync(); } - if (wallet.existsSync()) return false; + // if (!wallet.existsSync()) return false; wallet.writeAsStringSync(jsonEncode(w.toJson())); return true; } diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index b078760..3f32913 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -10,7 +10,7 @@ "welcome": "Vítejte!", "welcomeAboutPrasule": "Prašule je správce výdajů navržený pro lidi, kteří nechtějí vyplňovat každý malý detail.", "welcomeInstruction": "Na této obrazovce si nastavíte svoji 'peněženku', ve které budou zaznamenány vaše výdaje uspořádané do kategorií, které si nastavíte hned potom.", - "setupWalletNameCurrency": "Nastavte si název a měnu peněženky", + "setupWalletNameCurrency": "Nastavte si název a výchozí měnu peněženky", "setupNamePlaceholder": "Moje super peněženka", "setupCurrency": "Měna: {currency}", "setupCategoriesHeading": "Vytvořit kategorie", @@ -55,6 +55,12 @@ "license":"©️ 2023 Matyáš Caras\nVydáno pod licencí GNU AGPL license verze 3", "description":"Popis", "newWallet":"Přidat novou peněženku", - "walletExists":"Peněženka s tímto názvem již existuje!" + "walletExists":"Peněženka s tímto názvem již existuje!", + "setupStartingBalance":"Počáteční zůstatek", + "graphs":"Grafy", + "createTestData":"Vytvořit vzorková data", + "spendingStats":"Statistiky utrácení", + "yearly":"Roční", + "monthly":"Měsíční" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index d131587..3fd1003 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -10,7 +10,7 @@ "welcome": "Welcome!", "welcomeAboutPrasule": "Prašule is an expense tracker tool designed for people, who don't want to spend too much time filling in all the little details.", "welcomeInstruction": "On this screen you will set up your 'wallet', in which you will track your expenses categorized under categories, which you can later set in the settings menu.", - "setupWalletNameCurrency": "Set your wallet's name and currency", + "setupWalletNameCurrency": "Set your wallet's name and default currency", "setupNamePlaceholder": "Your awesome name here...", "setupCurrency": "Currency: {currency}", "@setupCurrency": { @@ -91,5 +91,11 @@ "license":"©️ 2023 Matyáš Caras\nReleased under the GNU AGPL license version 3", "description":"Description", "newWallet":"Add new wallet", - "walletExists":"A wallet with this name already exists!" + "walletExists":"A wallet with this name already exists!", + "setupStartingBalance":"Starting balance", + "graphs":"Graphs", + "createTestData":"Create test data", + "spendingStats":"Spending statistics", + "yearly":"Yearly", + "monthly":"Monthly" } \ No newline at end of file diff --git a/lib/util/drawer.dart b/lib/util/drawer.dart new file mode 100644 index 0000000..fbc3a20 --- /dev/null +++ b/lib/util/drawer.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:prasule/pw/platformroute.dart'; +import 'package:prasule/views/graph_view.dart'; +import 'package:prasule/views/home.dart'; + +/// Makes the drawer because I won't enter the same code in every view +Drawer makeDrawer(BuildContext context, int page) => Drawer( + child: ListView( + children: [ + const DrawerHeader(child: Text("Prašule")), + ListTile( + leading: const Icon(Icons.home), + title: Text( + AppLocalizations.of(context)!.home, + ), + selected: page == 1, + onTap: () { + if (page == 1) { + Navigator.of(context).pop(); + return; + } + Navigator.of(context) + .pushReplacement(platformRoute((p0) => const HomeView())); + }, + ), + ListTile( + leading: const Icon(Icons.bar_chart), + title: Text( + AppLocalizations.of(context)!.graphs, + ), + selected: page == 2, + onTap: () { + if (page == 2) { + Navigator.of(context).pop(); + return; + } + Navigator.of(context) + .pushReplacement(platformRoute((p0) => const GraphView())); + }, + ), + ], + ), + ); diff --git a/lib/util/graphs.dart b/lib/util/graphs.dart new file mode 100644 index 0000000..c91fa06 --- /dev/null +++ b/lib/util/graphs.dart @@ -0,0 +1,74 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +/// Monthly/Yearly expenses [LineChart] +class ExpensesChart extends StatelessWidget { + const ExpensesChart( + {super.key, + required this.date, + required this.locale, + this.data = const [], + this.yearly = false}); + final bool yearly; + final DateTime date; + final String locale; + final List data; + List get dataSorted { + var list = List.from(data); + list.sort((a, b) => a.compareTo(b)); + return list; + } + + @override + Widget build(BuildContext context) { + return LineChart( + LineChartData( + maxX: (yearly) ? 12 : DateTime(date.year, date.month, 0).day.toDouble(), + maxY: dataSorted.last, + minX: 1, + minY: 0, + backgroundColor: Theme.of(context).colorScheme.background, + lineBarsData: [ + LineChartBarData( + isCurved: true, + barWidth: 8, + isStrokeCapRound: true, + dotData: const FlDotData(show: false), + belowBarData: BarAreaData(show: false), + color: Theme.of(context).colorScheme.primary, + spots: List.generate( + (yearly) ? 12 : DateTime(date.year, date.month, 0).day, + (index) => FlSpot(index.toDouble() + 1, data[index]), + ), + ), + ], // actual data + titlesData: FlTitlesData( + rightTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + topTitles: const AxisTitles( + sideTitles: SideTitles(showTitles: false), + ), + bottomTitles: AxisTitles( + sideTitles: SideTitles( + showTitles: true, + getTitlesWidget: (value, meta) { + String text; + if (yearly) { + text = DateFormat.MMM(locale).format( + DateTime(date.year, value.toInt(), 1), + ); + } else { + text = value.toInt().toString(); + } + return SideTitleWidget( + axisSide: meta.axisSide, child: Text(text)); + }, + ), + ), + ), // axis descriptions + ), + ); + } +} diff --git a/lib/views/graph_view.dart b/lib/views/graph_view.dart new file mode 100644 index 0000000..e03c7c6 --- /dev/null +++ b/lib/views/graph_view.dart @@ -0,0 +1,228 @@ +import 'package:fl_chart/fl_chart.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:prasule/api/wallet.dart'; +import 'package:prasule/api/walletmanager.dart'; +import 'package:prasule/main.dart'; +import 'package:prasule/pw/platformbutton.dart'; +import 'package:prasule/pw/platformroute.dart'; +import 'package:prasule/util/drawer.dart'; +import 'package:prasule/util/graphs.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:prasule/views/settings/settings.dart'; +import 'package:prasule/views/setup.dart'; + +class GraphView extends StatefulWidget { + const GraphView({super.key}); + + @override + State createState() => _GraphViewState(); +} + +class _GraphViewState extends State { + var _selectedDate = DateTime.now(); + Wallet? selectedWallet; + List wallets = []; + String? locale; + var yearlyBtnSet = {"monthly"}; + bool get yearly => yearlyBtnSet.contains("yearly"); + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + locale ??= Localizations.localeOf(context).languageCode; + } + + List generateChartData() { + if (selectedWallet == null) return [0]; + var data = List.filled( + (yearly) + ? 12 + : DateTime(_selectedDate.year, _selectedDate.month, 0).day, + 0.0); + for (var i = 0; i < data.length; i++) { + var entriesForRange = selectedWallet!.entries.where((element) => (!yearly) + ? element.date.month == _selectedDate.month && + element.date.year == _selectedDate.year && + element.date.day == i + 1 + : element.date.month == i + 1 && + element.date.year == _selectedDate.year); + var sum = 0.0; + for (var e in entriesForRange) { + sum += e.data.amount; + } + data[i] = sum; + } + return data; + } + + void loadWallet() async { + wallets = await WalletManager.listWallets(); + if (wallets.isEmpty && mounted) { + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (c) => const SetupView())); + return; + } + selectedWallet = wallets.first; + setState(() {}); + } + + @override + void initState() { + super.initState(); + loadWallet(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: DropdownButton( + value: + (selectedWallet == null) ? -1 : wallets.indexOf(selectedWallet!), + items: [ + ...wallets.map( + (e) => DropdownMenuItem( + value: wallets.indexOf( + e, + ), + child: Text(e.name), + ), + ), + DropdownMenuItem( + value: -1, + child: Text(AppLocalizations.of(context)!.newWallet), + ) + ], + onChanged: (v) async { + if (v == null || v == -1) { + await Navigator.of(context).push( + platformRoute( + (c) => const SetupView( + newWallet: true, + ), + ), + ); + wallets = await WalletManager.listWallets(); + logger.i(wallets.length); + selectedWallet = wallets.last; + setState(() {}); + return; + } + selectedWallet = wallets[v]; + setState(() {}); + }, + ), + actions: [ + PopupMenuButton( + itemBuilder: (context) => [ + AppLocalizations.of(context)!.settings, + AppLocalizations.of(context)!.about + ].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(), + onSelected: (value) { + if (value == AppLocalizations.of(context)!.settings) { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => const SettingsView(), + ), + ); + } else if (value == AppLocalizations.of(context)!.about) { + showAboutDialog( + context: context, + applicationLegalese: AppLocalizations.of(context)!.license, + applicationName: "Prašule"); + } + }, + ) + ], + ), + drawer: makeDrawer(context, 2), + body: SingleChildScrollView( + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + height: MediaQuery.of(context).size.height, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SegmentedButton( + segments: [ + ButtonSegment( + value: "spending", + label: Text(AppLocalizations.of(context)!.spendingStats), + ) + ], + selected: const {"spending"}, + // TODO: onSelectionChanged + ), + const SizedBox( + height: 5, + ), + SegmentedButton( + segments: [ + ButtonSegment( + value: "yearly", + label: Text(AppLocalizations.of(context)!.yearly), + ), + ButtonSegment( + value: "monthly", + label: Text(AppLocalizations.of(context)!.monthly), + ), + ], + selected: yearlyBtnSet, + onSelectionChanged: (selection) { + yearlyBtnSet = selection; + setState(() {}); + }, + ), + const SizedBox(height: 5), + PlatformButton( + text: (yearly) + ? DateFormat.y(locale).format(_selectedDate) + : DateFormat.yMMMM(locale).format(_selectedDate), + onPressed: () async { + var firstDate = (selectedWallet!.entries + ..sort((a, b) => a.date.compareTo(b.date))) + .first + .date; + var lastDate = (selectedWallet!.entries + ..sort((a, b) => b.date.compareTo(a.date))) + .first + .date; + var newDate = await showDatePicker( + context: context, + initialDate: _selectedDate, + firstDate: firstDate, + lastDate: lastDate, + initialEntryMode: (yearly) + ? DatePickerEntryMode.input + : DatePickerEntryMode.calendar, + initialDatePickerMode: (yearly) + ? DatePickerMode.year + : DatePickerMode.day); + if (newDate == null) return; + _selectedDate = newDate; + setState(() {}); + }, + ), + const SizedBox( + height: 5, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + height: 300, + child: ExpensesChart( + date: _selectedDate, + locale: locale ?? "en", + yearly: yearly, + data: generateChartData(), + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/views/home.dart b/lib/views/home.dart index b040a65..6d6e5ef 100644 --- a/lib/views/home.dart +++ b/lib/views/home.dart @@ -1,3 +1,7 @@ +import 'dart:math'; + +import 'package:currency_picker/currency_picker.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:flutter_speed_dial/flutter_speed_dial.dart'; @@ -16,6 +20,7 @@ import 'package:prasule/network/tessdata.dart'; import 'package:prasule/pw/platformbutton.dart'; import 'package:prasule/pw/platformdialog.dart'; import 'package:prasule/pw/platformroute.dart'; +import 'package:prasule/util/drawer.dart'; import 'package:prasule/views/create_entry.dart'; import 'package:prasule/views/settings/settings.dart'; import 'package:prasule/views/settings/tessdata_list.dart'; @@ -61,10 +66,52 @@ class _HomeViewState extends State { @override Widget build(BuildContext context) { return Scaffold( + drawer: makeDrawer(context, 1), floatingActionButton: SpeedDial( icon: Icons.add, activeIcon: Icons.close, children: [ + if (kDebugMode) + SpeedDialChild( + child: const Icon(Icons.bug_report), + label: AppLocalizations.of(context)!.createTestData, + onTap: () { + // debug option to quickly fill a wallet with data + if (selectedWallet == null) return; + selectedWallet!.entries.clear(); + var random = Random(); + for (var i = 0; i < 30; i++) { + selectedWallet!.entries.add( + WalletSingleEntry( + data: EntryData( + name: "Test Entry #${i + 1}", + amount: random.nextInt(20000).toDouble(), + ), + type: EntryType.expense, + date: DateTime( + 2023, + random.nextInt(12) + 1, + random.nextInt(28) + 1, + ), + category: selectedWallet!.categories[ + random.nextInt(selectedWallet!.categories.length)], + id: selectedWallet!.nextId, + ), + ); + } + + logger.i(selectedWallet!.entries.length); + + // save and reload + WalletManager.saveWallet(selectedWallet!).then((value) { + Navigator.of(context).pushReplacement( + platformRoute( + (p0) => const HomeView(), + ), + ); + }); + }, + ), SpeedDialChild( child: const Icon(Icons.edit), label: AppLocalizations.of(context)!.addNew, @@ -193,9 +240,32 @@ class _HomeViewState extends State { style: TextStyle( color: Theme.of(context).colorScheme.primary), ), - elements: selectedWallet!.entries - ..sort((a, b) => a.date.compareTo(b.date)), + elements: selectedWallet!.entries, + itemComparator: (a, b) => b.date.compareTo(a.date), groupBy: (e) => DateFormat.yMMMM(locale).format(e.date), + groupComparator: (a, b) { + // TODO: better sorting algorithm lol + var yearA = RegExp(r'\d+').firstMatch(a); + if (yearA == null) return 0; + var yearB = RegExp(r'\d+').firstMatch(b); + if (yearB == null) return 0; + var compareYears = int.parse(yearA.group(0)!) + .compareTo(int.parse(yearB.group(0)!)); + if (compareYears != 0) return compareYears; + var months = List.generate( + 12, + (index) => DateFormat.MMMM(locale).format( + DateTime(2023, index + 1), + ), + ); + var monthA = RegExp(r'[^0-9 ]+').firstMatch(a); + if (monthA == null) return 0; + var monthB = RegExp(r'[^0-9 ]+').firstMatch(b); + if (monthB == null) return 0; + return months.indexOf(monthB.group(0)!).compareTo( + months.indexOf(monthA.group(0)!), + ); + }, itemBuilder: (context, element) => Slidable( endActionPane: ActionPane(motion: const ScrollMotion(), children: [ diff --git a/lib/views/setup.dart b/lib/views/setup.dart index feb0ca2..24dfbd7 100644 --- a/lib/views/setup.dart +++ b/lib/views/setup.dart @@ -1,5 +1,6 @@ import 'package:currency_picker/currency_picker.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_iconpicker/flutter_iconpicker.dart'; import 'package:introduction_screen/introduction_screen.dart'; import 'package:prasule/api/category.dart'; @@ -36,6 +37,7 @@ class _SetupViewState extends State { }); var categories = []; var name = ""; + var balance = 0.0; @override void didChangeDependencies() { @@ -201,7 +203,27 @@ class _SetupViewState extends State { }, ); }, - ) + ), + const SizedBox( + height: 5, + ), + SizedBox( + width: MediaQuery.of(context).size.width * 0.7, + child: PlatformField( + labelText: + AppLocalizations.of(context)!.setupStartingBalance, + keyboardType: const TextInputType.numberWithOptions( + decimal: true), + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'\d+[\.,]{0,1}\d{0,}'), + ) + ], + onChanged: (t) { + balance = double.parse(t); + }, + ), + ), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index a359f15..f6ae7d1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "36a321c3d2cbe01cbcb3540a87b8843846e0206df3e691fa7b23e19e78de6d49" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "65.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: dfe03b90ec022450e22513b5e5ca1f01c0c01de9c3fba2f7fd233cb57a6b9a07 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.3.0" archive: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: coverage - sha256: "595a29b55ce82d53398e1bcc2cba525d7bd7c59faeb2d2540e9d42c390cfeeeb" + sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9 url: "https://pub.dev" source: hosted - version: "1.6.4" + version: "1.7.1" cross_file: dependency: transitive description: @@ -241,6 +241,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.6.8" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -261,10 +269,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" file_selector_linux: dependency: transitive description: @@ -305,6 +313,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + fl_chart: + dependency: "direct main" + description: + name: fl_chart + sha256: "6b9eb2b3017241d05c482c01f668dd05cc909ec9a0114fdd49acd958ff2432fa" + url: "https://pub.dev" + source: hosted + version: "0.64.0" flutter: dependency: "direct main" description: flutter @@ -628,6 +644,22 @@ packages: url: "https://pub.dev" source: hosted version: "6.7.1" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: "7e108028e3d258667d079986da8c0bc32da4cb57431c2af03b1dc1038621a9dc" + url: "https://pub.dev" + source: hosted + version: "9.0.13" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: b06739349ec2477e943055aea30172c5c7000225f79dad4702e2ec0eda79a6ff + url: "https://pub.dev" + source: hosted + version: "1.0.5" lints: dependency: transitive description: @@ -664,18 +696,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" + sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.8.0" meta: dependency: transitive description: name: meta - sha256: a6e590c838b18133bb482a2745ad77c5bb7715fb0451209e1a7567d416678b8e + sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04 url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.11.0" mime: dependency: transitive description: @@ -776,10 +808,10 @@ packages: dependency: transitive description: name: platform - sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 + sha256: "0a279f0707af40c890e80b1e9df8bb761694c074ba7e1d4ab1bc4b728e200b59" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" plugin_platform_interface: dependency: transitive description: @@ -808,10 +840,10 @@ packages: dependency: transitive description: name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" + sha256: "266ca5be5820feefc777793d0a583acfc8c40834893c87c00c6c09e2cf58ea42" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "5.0.1" provider: dependency: transitive description: @@ -1021,10 +1053,10 @@ packages: dependency: transitive description: name: vm_service - sha256: c538be99af830f478718b51630ec1b6bee5e74e52c8a802d328d9e71d35d2583 + sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957 url: "https://pub.dev" source: hosted - version: "11.10.0" + version: "13.0.0" watcher: dependency: transitive description: @@ -1053,10 +1085,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3c923e918918feeb90c4c9fdf1fe39220fa4c0e8e2c0fffaded174498ef86c49" + sha256: "003d7da9519e1e5f329422b36c4dcdf18d7d2978d1ba099ea4e45ba490ed845e" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webkit_inspection_protocol: dependency: transitive description: @@ -1069,10 +1101,10 @@ packages: dependency: transitive description: name: win32 - sha256: "350a11abd2d1d97e0cc7a28a81b781c08002aa2864d9e3f192ca0ffa18b06ed3" + sha256: "7c99c0e1e2fa190b48d25c81ca5e42036d5cac81430ef249027d97b0935c553f" url: "https://pub.dev" source: hosted - version: "5.0.9" + version: "5.1.0" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0485659..6a17388 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: prasule description: Open-source private expense tracker -version: 1.0.0-alpha+1 +version: 1.0.0-alpha2+2 environment: sdk: '>=3.1.0-262.2.beta <4.0.0' @@ -38,6 +38,7 @@ dependencies: flutter_slidable: ^3.0.0 flutter_localizations: sdk: flutter + fl_chart: ^0.64.0 dev_dependencies: flutter_test: