feat: add graphs #16
21 changed files with 949 additions and 269 deletions
2
.flutter
2
.flutter
|
@ -1 +1 @@
|
||||||
Subproject commit 593a031efb1e54aef4d083fc810482c2877ba1d2
|
Subproject commit fed06b31d938f7620ea7417295b8d8d19cf7cf1d
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -2,6 +2,7 @@
|
||||||
"conventionalCommits.scopes": [
|
"conventionalCommits.scopes": [
|
||||||
"ocr",
|
"ocr",
|
||||||
"ui",
|
"ui",
|
||||||
"translations"
|
"translations",
|
||||||
|
"graphs"
|
||||||
]
|
]
|
||||||
}
|
}
|
|
@ -1,2 +1,5 @@
|
||||||
|
# 1.0.0-alpha.2
|
||||||
|
- Fixed localization issues
|
||||||
|
- Added graphs for expenses and income per month/year
|
||||||
# 1.0.0-alpha
|
# 1.0.0-alpha
|
||||||
- First public release
|
- First public release
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Generated file.
|
||||||
|
//
|
||||||
|
// If you wish to remove Flutter's multidex support, delete this entire file.
|
||||||
|
//
|
||||||
|
// Modifications to this file should be done in a copy under a different name
|
||||||
|
// as this file may be regenerated.
|
||||||
|
|
||||||
|
package io.flutter.app;
|
||||||
|
|
||||||
|
import android.app.Application;
|
||||||
|
import android.content.Context;
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.multidex.MultiDex;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension of {@link android.app.Application}, adding multidex support.
|
||||||
|
*/
|
||||||
|
public class FlutterMultiDexApplication extends Application {
|
||||||
|
@Override
|
||||||
|
@CallSuper
|
||||||
|
protected void attachBaseContext(Context base) {
|
||||||
|
super.attachBaseContext(base);
|
||||||
|
MultiDex.install(this);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,7 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
|
validateDistributionUrl=true
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
arb-dir: lib/l10n
|
arb-dir: lib/l10n
|
||||||
template-arb-file: app_en.arb
|
template-arb-file: app_en.arb
|
||||||
output-localization-file: app_localizations.dart
|
output-localization-file: app_localizations.dart
|
||||||
|
nullable-getter: false
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"@@locale": "cs",
|
||||||
"categoryHealth": "Zdraví",
|
"categoryHealth": "Zdraví",
|
||||||
"categoryCar": "Auto",
|
"categoryCar": "Auto",
|
||||||
"categoryFood": "Jídlo",
|
"categoryFood": "Jídlo",
|
||||||
|
@ -48,9 +49,9 @@
|
||||||
"ocr": "OCR",
|
"ocr": "OCR",
|
||||||
"ocrData": "OCR Data",
|
"ocrData": "OCR Data",
|
||||||
"downloaded": "Staženo",
|
"downloaded": "Staženo",
|
||||||
"deleteOcr": "Opravdu chcete smazat '$lang' OCR data?\nJiž tato data nebudete moct použít při skenování obrázků.",
|
"deleteOcr": "Opravdu chcete smazat '{lang}' OCR data?\nJiž tato data nebudete moct použít při skenování obrázků.",
|
||||||
"langDownloadDialog": "Stahuji $lang, vyčkejte prosím...",
|
"langDownloadDialog": "Stahuji {lang}, vyčkejte prosím...",
|
||||||
"langDownloadProgress": "Postup: $progress %",
|
"langDownloadProgress": "Postup: {progress} %",
|
||||||
"addingFromOcr": "Přidat skrz OCR",
|
"addingFromOcr": "Přidat skrz OCR",
|
||||||
"license":"©️ 2023 Matyáš Caras\nVydáno pod licencí GNU AGPL license verze 3",
|
"license":"©️ 2023 Matyáš Caras\nVydáno pod licencí GNU AGPL license verze 3",
|
||||||
"description":"Popis",
|
"description":"Popis",
|
||||||
|
@ -59,8 +60,20 @@
|
||||||
"setupStartingBalance":"Počáteční zůstatek",
|
"setupStartingBalance":"Počáteční zůstatek",
|
||||||
"graphs":"Grafy",
|
"graphs":"Grafy",
|
||||||
"createTestData":"Vytvořit vzorková data",
|
"createTestData":"Vytvořit vzorková data",
|
||||||
"spendingStats":"Statistiky utrácení",
|
|
||||||
"yearly":"Roční",
|
"yearly":"Roční",
|
||||||
"monthly":"Měsíční"
|
"monthly":"Měsíční",
|
||||||
|
"expenses":"Výdaje",
|
||||||
|
"expensesForMonth": "Výdaje za {month}: {value}",
|
||||||
|
"incomeForMonth": "Příjmy za {month}: {value}",
|
||||||
|
"expensesForDay": "Výdaje: {value}",
|
||||||
|
"incomeForDay": "Příjmy: {value}",
|
||||||
|
"settingsAppearance":"Vzhled",
|
||||||
|
"graphType":"Druh grafu",
|
||||||
|
"graphTypeDesc":"Zvolte, zda-li použít sloupcový, nebo spojnicový graf, a kde",
|
||||||
|
"lineChart":"Spojnicový",
|
||||||
|
"barChart":"Sloupcový",
|
||||||
|
"selectType":"Zvolte typ",
|
||||||
|
"enableYou":"Povolit Material You (Může vyžadovat restart aplikace)",
|
||||||
|
"enableYouDesc":"Aplikace použije barevné schéma z vaší tapety"
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
{
|
{
|
||||||
|
"@@locale": "en",
|
||||||
"categoryHealth": "Health",
|
"categoryHealth": "Health",
|
||||||
"categoryCar": "Car",
|
"categoryCar": "Car",
|
||||||
"categoryFood": "Food",
|
"categoryFood": "Food",
|
||||||
|
@ -57,7 +58,7 @@
|
||||||
"ocr": "OCR",
|
"ocr": "OCR",
|
||||||
"ocrData": "OCR Data",
|
"ocrData": "OCR Data",
|
||||||
"downloaded": "Downloaded",
|
"downloaded": "Downloaded",
|
||||||
"deleteOcr": "Do you really want to delete '$lang' OCR data?\nYou will not be able to use these language data when scanning pictures.",
|
"deleteOcr": "Do you really want to delete '{lang}' OCR data?\nYou will not be able to use these language data when scanning pictures.",
|
||||||
"@deleteOcr": {
|
"@deleteOcr": {
|
||||||
"description": "Shown when a user wants to delete OCR data through settings",
|
"description": "Shown when a user wants to delete OCR data through settings",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -67,7 +68,7 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"langDownloadDialog": "Downloading $lang, please wait...",
|
"langDownloadDialog": "Downloading {lang}, please wait...",
|
||||||
"@langDownloadDialog": {
|
"@langDownloadDialog": {
|
||||||
"description": "Shown as a title of a dialog while downloading new OCR data",
|
"description": "Shown as a title of a dialog while downloading new OCR data",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
|
@ -77,25 +78,81 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"langDownloadProgress": "Download progress: $progress %",
|
"langDownloadProgress": "Download progress: {progress} %",
|
||||||
"@langDownloadProgress": {
|
"@langDownloadProgress": {
|
||||||
"description": "Progress percentage shown while downloading OCR data",
|
"description": "Progress percentage shown while downloading OCR data",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"progress":{
|
"progress": {
|
||||||
"type":"num",
|
"type": "num",
|
||||||
"example":"99.7"
|
"example": "99.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"addingFromOcr": "Add from OCR",
|
"addingFromOcr": "Add from OCR",
|
||||||
"license":"©️ 2023 Matyáš Caras\nReleased under the GNU AGPL license version 3",
|
"license": "©️ 2023 Matyáš Caras\nReleased under the GNU AGPL license version 3",
|
||||||
"description":"Description",
|
"description": "Description",
|
||||||
"newWallet":"Add new wallet",
|
"newWallet": "Add new wallet",
|
||||||
"walletExists":"A wallet with this name already exists!",
|
"walletExists": "A wallet with this name already exists!",
|
||||||
"setupStartingBalance":"Starting balance",
|
"setupStartingBalance": "Starting balance",
|
||||||
"graphs":"Graphs",
|
"graphs": "Graphs",
|
||||||
"createTestData":"Create test data",
|
"createTestData": "Create test data",
|
||||||
"spendingStats":"Spending statistics",
|
"yearly": "Yearly",
|
||||||
"yearly":"Yearly",
|
"monthly": "Monthly",
|
||||||
"monthly":"Monthly"
|
"expenses": "Expenses",
|
||||||
|
"expensesForMonth": "{month} expenses: {value}",
|
||||||
|
"@expensesForMonth": {
|
||||||
|
"description": "Shown as a tooltip when touching expense graph",
|
||||||
|
"placeholders": {
|
||||||
|
"month": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "January"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "5000 Kč"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"incomeForMonth": "{month} income: {value}",
|
||||||
|
"@incomeForMonth": {
|
||||||
|
"description": "Shown as a tooltip when touching income graph",
|
||||||
|
"placeholders": {
|
||||||
|
"month": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "January"
|
||||||
|
},
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "5000 Kč"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expensesForDay": "Expenses: {value}",
|
||||||
|
"@expensesForDay":{
|
||||||
|
"description": "Shown as a tooltip when touching expense graph",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "5000 Kč"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"incomeForDay": "Income: {value}",
|
||||||
|
"@incomeForDay":{
|
||||||
|
"description": "Shown as a tooltip when touching expense graph",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"type": "String",
|
||||||
|
"example": "5000 Kč"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settingsAppearance":"Appearance",
|
||||||
|
"graphType":"Graph type",
|
||||||
|
"graphTypeDesc":"Choose whether to show line or bar chart and where",
|
||||||
|
"lineChart":"Line chart",
|
||||||
|
"barChart":"Bar chart",
|
||||||
|
"selectType":"Select type",
|
||||||
|
"enableYou":"Enable Material You (May require an app restart)",
|
||||||
|
"enableYouDesc":"The app will use a color scheme from your wallpaper"
|
||||||
}
|
}
|
|
@ -8,8 +8,13 @@ import 'package:logger/logger.dart';
|
||||||
import 'package:prasule/util/color_schemes.g.dart';
|
import 'package:prasule/util/color_schemes.g.dart';
|
||||||
import 'package:prasule/views/home.dart';
|
import 'package:prasule/views/home.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
void main() {
|
var _materialYou = false;
|
||||||
|
void main() async {
|
||||||
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
_materialYou = s.getBool("useMaterialYou") ?? true;
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,29 +22,37 @@ final logger = Logger();
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const MyApp({super.key});
|
const MyApp({super.key});
|
||||||
|
static bool appliedYou = false;
|
||||||
// This widget is the root of your application.
|
// This widget is the root of your application.
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return (Platform.isAndroid)
|
return (Platform.isAndroid)
|
||||||
? DynamicColorBuilder(
|
? DynamicColorBuilder(
|
||||||
builder: (light, dark) => MaterialApp(
|
builder: (light, dark) {
|
||||||
debugShowCheckedModeBanner: false,
|
appliedYou = light != null;
|
||||||
localizationsDelegates: const [
|
return MaterialApp(
|
||||||
AppLocalizations.delegate,
|
debugShowCheckedModeBanner: false,
|
||||||
...GlobalMaterialLocalizations.delegates,
|
localizationsDelegates: const [
|
||||||
...GlobalCupertinoLocalizations.delegates
|
AppLocalizations.delegate,
|
||||||
],
|
...GlobalMaterialLocalizations.delegates,
|
||||||
supportedLocales: AppLocalizations.supportedLocales,
|
...GlobalCupertinoLocalizations.delegates
|
||||||
title: 'Prašule',
|
],
|
||||||
theme: ThemeData(
|
supportedLocales: AppLocalizations.supportedLocales,
|
||||||
colorScheme: light ?? lightColorScheme,
|
title: 'Prašule',
|
||||||
useMaterial3: true,
|
theme: ThemeData(
|
||||||
),
|
colorScheme: (_materialYou)
|
||||||
darkTheme: ThemeData(
|
? light ?? lightColorScheme
|
||||||
useMaterial3: true, colorScheme: dark ?? darkColorScheme),
|
: lightColorScheme,
|
||||||
home: const HomeView(),
|
useMaterial3: true,
|
||||||
),
|
),
|
||||||
|
darkTheme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
colorScheme: (_materialYou)
|
||||||
|
? dark ?? darkColorScheme
|
||||||
|
: darkColorScheme),
|
||||||
|
home: const HomeView(),
|
||||||
|
);
|
||||||
|
},
|
||||||
)
|
)
|
||||||
: Theme(
|
: Theme(
|
||||||
data: ThemeData(
|
data: ThemeData(
|
||||||
|
|
|
@ -5,12 +5,16 @@ import 'package:prasule/pw/platformwidget.dart';
|
||||||
class PlatformButton extends PlatformWidget<TextButton, CupertinoButton> {
|
class PlatformButton extends PlatformWidget<TextButton, CupertinoButton> {
|
||||||
final String text;
|
final String text;
|
||||||
final void Function()? onPressed;
|
final void Function()? onPressed;
|
||||||
|
final ButtonStyle? style;
|
||||||
const PlatformButton(
|
const PlatformButton(
|
||||||
{super.key, required this.text, required this.onPressed});
|
{super.key, required this.text, required this.onPressed, this.style});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TextButton createAndroidWidget(BuildContext context) =>
|
TextButton createAndroidWidget(BuildContext context) => TextButton(
|
||||||
TextButton(onPressed: onPressed, child: Text(text));
|
onPressed: onPressed,
|
||||||
|
style: style,
|
||||||
|
child: Text(text),
|
||||||
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
CupertinoButton createIosWidget(BuildContext context) =>
|
CupertinoButton createIosWidget(BuildContext context) =>
|
||||||
|
|
|
@ -12,7 +12,7 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.home),
|
leading: const Icon(Icons.home),
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context)!.home,
|
AppLocalizations.of(context).home,
|
||||||
),
|
),
|
||||||
selected: page == 1,
|
selected: page == 1,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
@ -27,7 +27,7 @@ Drawer makeDrawer(BuildContext context, int page) => Drawer(
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: const Icon(Icons.bar_chart),
|
leading: const Icon(Icons.bar_chart),
|
||||||
title: Text(
|
title: Text(
|
||||||
AppLocalizations.of(context)!.graphs,
|
AppLocalizations.of(context).graphs,
|
||||||
),
|
),
|
||||||
selected: page == 2,
|
selected: page == 2,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|
|
@ -1,47 +1,129 @@
|
||||||
|
import 'package:currency_picker/currency_picker.dart';
|
||||||
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
import 'package:fl_chart/fl_chart.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
|
||||||
/// Monthly/Yearly expenses [LineChart]
|
/// Monthly/Yearly expense/income [LineChart]
|
||||||
class ExpensesChart extends StatelessWidget {
|
class ExpensesLineChart extends StatelessWidget {
|
||||||
const ExpensesChart(
|
const ExpensesLineChart(
|
||||||
{super.key,
|
{super.key,
|
||||||
required this.date,
|
required this.date,
|
||||||
required this.locale,
|
required this.locale,
|
||||||
this.data = const [],
|
required this.expenseData,
|
||||||
|
required this.incomeData,
|
||||||
|
required this.currency,
|
||||||
this.yearly = false});
|
this.yearly = false});
|
||||||
final bool yearly;
|
final bool yearly;
|
||||||
final DateTime date;
|
final DateTime date;
|
||||||
final String locale;
|
final String locale;
|
||||||
final List<double> data;
|
final List<double> expenseData;
|
||||||
List<double> get dataSorted {
|
final Currency currency;
|
||||||
var list = List<double>.from(data);
|
List<double> get expenseDataSorted {
|
||||||
|
var list = List<double>.from(expenseData);
|
||||||
list.sort((a, b) => a.compareTo(b));
|
list.sort((a, b) => a.compareTo(b));
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<double> incomeData;
|
||||||
|
List<double> get incomeDataSorted {
|
||||||
|
var list = List<double>.from(incomeData);
|
||||||
|
list.sort((a, b) => a.compareTo(b));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
double get maxY {
|
||||||
|
if (incomeData.isEmpty) return expenseDataSorted.last;
|
||||||
|
if (expenseData.isEmpty) return incomeDataSorted.last;
|
||||||
|
if (expenseDataSorted.last > incomeDataSorted.last) {
|
||||||
|
return expenseDataSorted.last;
|
||||||
|
} else {
|
||||||
|
return incomeDataSorted.last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return LineChart(
|
return LineChart(
|
||||||
LineChartData(
|
LineChartData(
|
||||||
maxX: (yearly) ? 12 : DateTime(date.year, date.month, 0).day.toDouble(),
|
lineTouchData: LineTouchData(
|
||||||
maxY: dataSorted.last,
|
touchTooltipData: LineTouchTooltipData(
|
||||||
minX: 1,
|
getTooltipItems: (spots) => List<LineTooltipItem>.generate(
|
||||||
minY: 0,
|
spots.length,
|
||||||
backgroundColor: Theme.of(context).colorScheme.background,
|
(index) => LineTooltipItem(
|
||||||
lineBarsData: [
|
(spots[index].barIndex == 0)
|
||||||
LineChartBarData(
|
? (yearly
|
||||||
isCurved: true,
|
? AppLocalizations.of(context).incomeForMonth(
|
||||||
barWidth: 8,
|
DateFormat.MMMM(locale).format(DateTime(
|
||||||
isStrokeCapRound: true,
|
date.year, spots[index].x.toInt() + 1, 1)),
|
||||||
dotData: const FlDotData(show: false),
|
NumberFormat.compactCurrency(
|
||||||
belowBarData: BarAreaData(show: false),
|
locale: locale,
|
||||||
color: Theme.of(context).colorScheme.primary,
|
symbol: currency.symbol,
|
||||||
spots: List.generate(
|
name: currency.name)
|
||||||
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
|
.format(spots[index].y))
|
||||||
(index) => FlSpot(index.toDouble() + 1, data[index]),
|
: AppLocalizations.of(context).incomeForDay(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(spots[index].y),
|
||||||
|
))
|
||||||
|
: (yearly
|
||||||
|
? AppLocalizations.of(context).expensesForMonth(
|
||||||
|
DateFormat.MMMM(locale).format(DateTime(
|
||||||
|
date.year, spots[index].x.toInt() + 1, 1)),
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(spots[index].y))
|
||||||
|
: AppLocalizations.of(context).expensesForDay(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(spots[index].y),
|
||||||
|
)),
|
||||||
|
TextStyle(color: spots[index].bar.color),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
|
maxY: maxY,
|
||||||
|
maxX: (yearly) ? 12 : DateTime(date.year, date.month, 0).day.toDouble(),
|
||||||
|
minY: 0,
|
||||||
|
minX: 0,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.background,
|
||||||
|
lineBarsData: [
|
||||||
|
if (incomeData.isNotEmpty)
|
||||||
|
LineChartBarData(
|
||||||
|
isCurved: true,
|
||||||
|
barWidth: 8,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
color: Colors.green
|
||||||
|
.harmonizeWith(Theme.of(context).colorScheme.secondary),
|
||||||
|
spots: List.generate(
|
||||||
|
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
|
||||||
|
(index) => FlSpot(index.toDouble(), incomeData[index]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (expenseData.isNotEmpty)
|
||||||
|
LineChartBarData(
|
||||||
|
isCurved: true,
|
||||||
|
barWidth: 8,
|
||||||
|
isStrokeCapRound: true,
|
||||||
|
dotData: const FlDotData(show: false),
|
||||||
|
belowBarData: BarAreaData(show: false),
|
||||||
|
color: Colors.red
|
||||||
|
.harmonizeWith(Theme.of(context).colorScheme.secondary),
|
||||||
|
spots: List.generate(
|
||||||
|
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
|
||||||
|
(index) => FlSpot(index.toDouble() + 1, expenseData[index]),
|
||||||
|
),
|
||||||
|
),
|
||||||
], // actual data
|
], // actual data
|
||||||
titlesData: FlTitlesData(
|
titlesData: FlTitlesData(
|
||||||
rightTitles: const AxisTitles(
|
rightTitles: const AxisTitles(
|
||||||
|
@ -52,15 +134,16 @@ class ExpensesChart extends StatelessWidget {
|
||||||
),
|
),
|
||||||
bottomTitles: AxisTitles(
|
bottomTitles: AxisTitles(
|
||||||
sideTitles: SideTitles(
|
sideTitles: SideTitles(
|
||||||
|
reservedSize: 30,
|
||||||
showTitles: true,
|
showTitles: true,
|
||||||
getTitlesWidget: (value, meta) {
|
getTitlesWidget: (value, meta) {
|
||||||
String text;
|
String text;
|
||||||
if (yearly) {
|
if (yearly) {
|
||||||
text = DateFormat.MMM(locale).format(
|
text = DateFormat.MMM(locale).format(
|
||||||
DateTime(date.year, value.toInt(), 1),
|
DateTime(date.year, value.toInt() + 1, 1),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
text = value.toInt().toString();
|
text = (value.toInt() + 1).toString();
|
||||||
}
|
}
|
||||||
return SideTitleWidget(
|
return SideTitleWidget(
|
||||||
axisSide: meta.axisSide, child: Text(text));
|
axisSide: meta.axisSide, child: Text(text));
|
||||||
|
@ -72,3 +155,143 @@ class ExpensesChart extends StatelessWidget {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ExpensesBarChart extends StatelessWidget {
|
||||||
|
const ExpensesBarChart(
|
||||||
|
{super.key,
|
||||||
|
required this.yearly,
|
||||||
|
required this.date,
|
||||||
|
required this.locale,
|
||||||
|
required this.expenseData,
|
||||||
|
required this.incomeData,
|
||||||
|
required this.currency});
|
||||||
|
final bool yearly;
|
||||||
|
final DateTime date;
|
||||||
|
final String locale;
|
||||||
|
final List<double> expenseData;
|
||||||
|
List<double> get expenseDataSorted {
|
||||||
|
var list = List<double>.from(expenseData);
|
||||||
|
list.sort((a, b) => a.compareTo(b));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Currency currency;
|
||||||
|
|
||||||
|
final List<double> incomeData;
|
||||||
|
List<double> get incomeDataSorted {
|
||||||
|
var list = List<double>.from(incomeData);
|
||||||
|
list.sort((a, b) => a.compareTo(b));
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
double get maxY {
|
||||||
|
if (incomeData.isEmpty) return expenseDataSorted.last;
|
||||||
|
if (expenseData.isEmpty) return incomeDataSorted.last;
|
||||||
|
if (expenseDataSorted.last > incomeDataSorted.last) {
|
||||||
|
return expenseDataSorted.last;
|
||||||
|
} else {
|
||||||
|
return incomeDataSorted.last;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => BarChart(
|
||||||
|
BarChartData(
|
||||||
|
barTouchData: BarTouchData(
|
||||||
|
enabled: true,
|
||||||
|
touchTooltipData: BarTouchTooltipData(
|
||||||
|
getTooltipItem: (group, groupIndex, rod, rodIndex) =>
|
||||||
|
(yearly) // create custom tooltips for graph bars
|
||||||
|
? BarTooltipItem(
|
||||||
|
(rodIndex == 1)
|
||||||
|
? AppLocalizations.of(context).expensesForMonth(
|
||||||
|
DateFormat.MMMM(locale).format(
|
||||||
|
DateTime(date.year, groupIndex + 1, 1)),
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(rod.toY),
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context).incomeForMonth(
|
||||||
|
DateFormat.MMMM(locale).format(
|
||||||
|
DateTime(date.year, groupIndex + 1, 1)),
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(rod.toY),
|
||||||
|
),
|
||||||
|
TextStyle(color: rod.color),
|
||||||
|
)
|
||||||
|
: BarTooltipItem(
|
||||||
|
(rodIndex == 1)
|
||||||
|
? AppLocalizations.of(context).expensesForDay(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(rod.toY),
|
||||||
|
)
|
||||||
|
: AppLocalizations.of(context).incomeForDay(
|
||||||
|
NumberFormat.compactCurrency(
|
||||||
|
locale: locale,
|
||||||
|
symbol: currency.symbol,
|
||||||
|
name: currency.name)
|
||||||
|
.format(rod.toY),
|
||||||
|
),
|
||||||
|
TextStyle(color: rod.color),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
titlesData: FlTitlesData(
|
||||||
|
rightTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
topTitles: const AxisTitles(
|
||||||
|
sideTitles: SideTitles(showTitles: false),
|
||||||
|
),
|
||||||
|
bottomTitles: AxisTitles(
|
||||||
|
sideTitles: SideTitles(
|
||||||
|
showTitles: true,
|
||||||
|
reservedSize: 30,
|
||||||
|
getTitlesWidget: (value, meta) {
|
||||||
|
String text;
|
||||||
|
if (yearly) {
|
||||||
|
text = DateFormat.MMM(locale).format(
|
||||||
|
DateTime(date.year, value.toInt() + 1, 1),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
text = (value.toInt() + 1).toString();
|
||||||
|
}
|
||||||
|
return SideTitleWidget(
|
||||||
|
axisSide: meta.axisSide, child: Text(text));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
), // axis descriptions,
|
||||||
|
minY: 0,
|
||||||
|
maxY: maxY,
|
||||||
|
barGroups: List<BarChartGroupData>.generate(
|
||||||
|
(yearly) ? 12 : DateTime(date.year, date.month, 0).day,
|
||||||
|
(index) => BarChartGroupData(
|
||||||
|
x: index,
|
||||||
|
barRods: [
|
||||||
|
if (incomeData.isNotEmpty)
|
||||||
|
BarChartRodData(
|
||||||
|
toY: incomeData[index],
|
||||||
|
color: Colors.green
|
||||||
|
.harmonizeWith(Theme.of(context).colorScheme.secondary),
|
||||||
|
),
|
||||||
|
if (expenseData.isNotEmpty)
|
||||||
|
BarChartRodData(
|
||||||
|
toY: expenseData[index],
|
||||||
|
color: Colors.red
|
||||||
|
.harmonizeWith(Theme.of(context).colorScheme.secondary),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -40,7 +40,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(AppLocalizations.of(context)!.createEntry),
|
title: Text(AppLocalizations.of(context).createEntry),
|
||||||
),
|
),
|
||||||
body: SizedBox(
|
body: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
|
@ -53,7 +53,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.8,
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
child: PlatformField(
|
child: PlatformField(
|
||||||
labelText: AppLocalizations.of(context)!.name,
|
labelText: AppLocalizations.of(context).name,
|
||||||
controller: TextEditingController(text: newEntry.data.name),
|
controller: TextEditingController(text: newEntry.data.name),
|
||||||
onChanged: (v) {
|
onChanged: (v) {
|
||||||
newEntry.data.name = v;
|
newEntry.data.name = v;
|
||||||
|
@ -66,7 +66,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.8,
|
width: MediaQuery.of(context).size.width * 0.8,
|
||||||
child: PlatformField(
|
child: PlatformField(
|
||||||
labelText: AppLocalizations.of(context)!.amount,
|
labelText: AppLocalizations.of(context).amount,
|
||||||
controller: TextEditingController(
|
controller: TextEditingController(
|
||||||
text: newEntry.data.amount.toString()),
|
text: newEntry.data.amount.toString()),
|
||||||
keyboardType:
|
keyboardType:
|
||||||
|
@ -83,7 +83,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
Text(AppLocalizations.of(context)!.type),
|
Text(AppLocalizations.of(context).type),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
|
@ -97,7 +97,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.expense,
|
AppLocalizations.of(context).expense,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -105,7 +105,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
value: EntryType.income,
|
value: EntryType.income,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
width: MediaQuery.of(context).size.width * 0.8 - 24,
|
||||||
child: Text(AppLocalizations.of(context)!.income),
|
child: Text(AppLocalizations.of(context).income),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -119,7 +119,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
Text(AppLocalizations.of(context)!.category),
|
Text(AppLocalizations.of(context).category),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
|
@ -151,7 +151,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 20,
|
height: 20,
|
||||||
),
|
),
|
||||||
Text(AppLocalizations.of(context)!.description),
|
Text(AppLocalizations.of(context).description),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 10,
|
height: 10,
|
||||||
),
|
),
|
||||||
|
@ -175,14 +175,14 @@ class _CreateEntryViewState extends State<CreateEntryView> {
|
||||||
height: 15,
|
height: 15,
|
||||||
),
|
),
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!.save,
|
text: AppLocalizations.of(context).save,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (newEntry.data.name.isEmpty) {
|
if (newEntry.data.name.isEmpty) {
|
||||||
ScaffoldMessenger.of(context).clearSnackBars();
|
ScaffoldMessenger.of(context).clearSnackBars();
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
AppLocalizations.of(context)!.errorEmptyName),
|
AppLocalizations.of(context).errorEmptyName),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import 'package:fl_chart/fl_chart.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:prasule/api/category.dart';
|
||||||
import 'package:prasule/api/wallet.dart';
|
import 'package:prasule/api/wallet.dart';
|
||||||
import 'package:prasule/api/walletmanager.dart';
|
import 'package:prasule/api/walletmanager.dart';
|
||||||
import 'package:prasule/main.dart';
|
import 'package:prasule/main.dart';
|
||||||
|
@ -11,6 +11,7 @@ import 'package:prasule/util/graphs.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:prasule/views/settings/settings.dart';
|
import 'package:prasule/views/settings/settings.dart';
|
||||||
import 'package:prasule/views/setup.dart';
|
import 'package:prasule/views/setup.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class GraphView extends StatefulWidget {
|
class GraphView extends StatefulWidget {
|
||||||
const GraphView({super.key});
|
const GraphView({super.key});
|
||||||
|
@ -25,6 +26,7 @@ class _GraphViewState extends State<GraphView> {
|
||||||
List<Wallet> wallets = [];
|
List<Wallet> wallets = [];
|
||||||
String? locale;
|
String? locale;
|
||||||
var yearlyBtnSet = {"monthly"};
|
var yearlyBtnSet = {"monthly"};
|
||||||
|
var graphTypeSet = {"expense", "income"};
|
||||||
bool get yearly => yearlyBtnSet.contains("yearly");
|
bool get yearly => yearlyBtnSet.contains("yearly");
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -33,20 +35,22 @@ class _GraphViewState extends State<GraphView> {
|
||||||
locale ??= Localizations.localeOf(context).languageCode;
|
locale ??= Localizations.localeOf(context).languageCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<double> generateChartData() {
|
List<double> generateChartData(EntryType type) {
|
||||||
if (selectedWallet == null) return [0];
|
|
||||||
var data = List<double>.filled(
|
var data = List<double>.filled(
|
||||||
(yearly)
|
(yearly)
|
||||||
? 12
|
? 12
|
||||||
: DateTime(_selectedDate.year, _selectedDate.month, 0).day,
|
: DateTime(_selectedDate.year, _selectedDate.month, 0).day,
|
||||||
0.0);
|
0.0);
|
||||||
|
if (selectedWallet == null) return [];
|
||||||
for (var i = 0; i < data.length; i++) {
|
for (var i = 0; i < data.length; i++) {
|
||||||
var entriesForRange = selectedWallet!.entries.where((element) => (!yearly)
|
var entriesForRange = selectedWallet!.entries.where((element) =>
|
||||||
? element.date.month == _selectedDate.month &&
|
((!yearly)
|
||||||
element.date.year == _selectedDate.year &&
|
? element.date.month == _selectedDate.month &&
|
||||||
element.date.day == i + 1
|
element.date.year == _selectedDate.year &&
|
||||||
: element.date.month == i + 1 &&
|
element.date.day == i + 1
|
||||||
element.date.year == _selectedDate.year);
|
: element.date.month == i + 1 &&
|
||||||
|
element.date.year == _selectedDate.year) &&
|
||||||
|
element.type == type);
|
||||||
var sum = 0.0;
|
var sum = 0.0;
|
||||||
for (var e in entriesForRange) {
|
for (var e in entriesForRange) {
|
||||||
sum += e.data.amount;
|
sum += e.data.amount;
|
||||||
|
@ -67,10 +71,15 @@ class _GraphViewState extends State<GraphView> {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int? chartType;
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
loadWallet();
|
loadWallet();
|
||||||
|
SharedPreferences.getInstance().then((s) {
|
||||||
|
chartType = s.getInt("monthlygraph") ?? 2;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -91,7 +100,7 @@ class _GraphViewState extends State<GraphView> {
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: -1,
|
value: -1,
|
||||||
child: Text(AppLocalizations.of(context)!.newWallet),
|
child: Text(AppLocalizations.of(context).newWallet),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
onChanged: (v) async {
|
onChanged: (v) async {
|
||||||
|
@ -116,20 +125,20 @@ class _GraphViewState extends State<GraphView> {
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
AppLocalizations.of(context)!.settings,
|
AppLocalizations.of(context).settings,
|
||||||
AppLocalizations.of(context)!.about
|
AppLocalizations.of(context).about
|
||||||
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == AppLocalizations.of(context)!.settings) {
|
if (value == AppLocalizations.of(context).settings) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const SettingsView(),
|
builder: (context) => const SettingsView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (value == AppLocalizations.of(context)!.about) {
|
} else if (value == AppLocalizations.of(context).about) {
|
||||||
showAboutDialog(
|
showAboutDialog(
|
||||||
context: context,
|
context: context,
|
||||||
applicationLegalese: AppLocalizations.of(context)!.license,
|
applicationLegalese: AppLocalizations.of(context).license,
|
||||||
applicationName: "Prašule");
|
applicationName: "Prašule");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -139,88 +148,160 @@ class _GraphViewState extends State<GraphView> {
|
||||||
drawer: makeDrawer(context, 2),
|
drawer: makeDrawer(context, 2),
|
||||||
body: SingleChildScrollView(
|
body: SingleChildScrollView(
|
||||||
child: Center(
|
child: Center(
|
||||||
child: SizedBox(
|
child: (selectedWallet == null)
|
||||||
width: MediaQuery.of(context).size.width * 0.9,
|
? const CircularProgressIndicator(
|
||||||
height: MediaQuery.of(context).size.height,
|
strokeWidth: 5,
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
SegmentedButton<String>(
|
|
||||||
segments: [
|
|
||||||
ButtonSegment<String>(
|
|
||||||
value: "spending",
|
|
||||||
label: Text(AppLocalizations.of(context)!.spendingStats),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
selected: const {"spending"},
|
|
||||||
// TODO: onSelectionChanged
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 5,
|
|
||||||
),
|
|
||||||
SegmentedButton<String>(
|
|
||||||
segments: [
|
|
||||||
ButtonSegment<String>(
|
|
||||||
value: "yearly",
|
|
||||||
label: Text(AppLocalizations.of(context)!.yearly),
|
|
||||||
),
|
|
||||||
ButtonSegment<String>(
|
|
||||||
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(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
],
|
: SizedBox(
|
||||||
),
|
width: MediaQuery.of(context).size.width,
|
||||||
),
|
height: MediaQuery.of(context).size.height,
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
SegmentedButton<String>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment<String>(
|
||||||
|
value: "expense",
|
||||||
|
label: Text(AppLocalizations.of(context).expenses),
|
||||||
|
),
|
||||||
|
ButtonSegment<String>(
|
||||||
|
value: "income",
|
||||||
|
label: Text(AppLocalizations.of(context).income),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: graphTypeSet,
|
||||||
|
multiSelectionEnabled: true,
|
||||||
|
onSelectionChanged: (selection) {
|
||||||
|
graphTypeSet = selection;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 5,
|
||||||
|
),
|
||||||
|
SegmentedButton<String>(
|
||||||
|
segments: [
|
||||||
|
ButtonSegment<String>(
|
||||||
|
value: "yearly",
|
||||||
|
label: Text(AppLocalizations.of(context).yearly),
|
||||||
|
),
|
||||||
|
ButtonSegment<String>(
|
||||||
|
value: "monthly",
|
||||||
|
label: Text(AppLocalizations.of(context).monthly),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
selected: yearlyBtnSet,
|
||||||
|
onSelectionChanged: (selection) async {
|
||||||
|
yearlyBtnSet = selection;
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
chartType = (yearly)
|
||||||
|
? (s.getInt("yearlygraph") ?? 1)
|
||||||
|
: (s.getInt("monthlygraph") ?? 2);
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 5),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
PlatformButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).colorScheme.primary),
|
||||||
|
foregroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onPrimary)),
|
||||||
|
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;
|
||||||
|
logger.i(firstDate);
|
||||||
|
logger.i(lastDate);
|
||||||
|
var newDate = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: DateTime(_selectedDate.year,
|
||||||
|
_selectedDate.month, 1),
|
||||||
|
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: (chartType == null)
|
||||||
|
? const CircularProgressIndicator()
|
||||||
|
: (chartType == 1)
|
||||||
|
? ExpensesBarChart(
|
||||||
|
currency: selectedWallet!.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: (graphTypeSet
|
||||||
|
.contains("expense"))
|
||||||
|
? generateChartData(
|
||||||
|
EntryType.expense)
|
||||||
|
: [],
|
||||||
|
incomeData: (graphTypeSet
|
||||||
|
.contains("income"))
|
||||||
|
? generateChartData(
|
||||||
|
EntryType.income)
|
||||||
|
: [],
|
||||||
|
)
|
||||||
|
: ExpensesLineChart(
|
||||||
|
currency: selectedWallet!.currency,
|
||||||
|
date: _selectedDate,
|
||||||
|
locale: locale ?? "en",
|
||||||
|
yearly: yearly,
|
||||||
|
expenseData: (graphTypeSet
|
||||||
|
.contains("expense"))
|
||||||
|
? generateChartData(
|
||||||
|
EntryType.expense)
|
||||||
|
: [],
|
||||||
|
incomeData: (graphTypeSet
|
||||||
|
.contains("income"))
|
||||||
|
? generateChartData(
|
||||||
|
EntryType.income)
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
import 'package:currency_picker/currency_picker.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
|
@ -74,7 +72,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
if (kDebugMode)
|
if (kDebugMode)
|
||||||
SpeedDialChild(
|
SpeedDialChild(
|
||||||
child: const Icon(Icons.bug_report),
|
child: const Icon(Icons.bug_report),
|
||||||
label: AppLocalizations.of(context)!.createTestData,
|
label: AppLocalizations.of(context).createTestData,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
// debug option to quickly fill a wallet with data
|
// debug option to quickly fill a wallet with data
|
||||||
if (selectedWallet == null) return;
|
if (selectedWallet == null) return;
|
||||||
|
@ -87,7 +85,9 @@ class _HomeViewState extends State<HomeView> {
|
||||||
name: "Test Entry #${i + 1}",
|
name: "Test Entry #${i + 1}",
|
||||||
amount: random.nextInt(20000).toDouble(),
|
amount: random.nextInt(20000).toDouble(),
|
||||||
),
|
),
|
||||||
type: EntryType.expense,
|
type: (random.nextInt(3) > 0)
|
||||||
|
? EntryType.expense
|
||||||
|
: EntryType.income,
|
||||||
date: DateTime(
|
date: DateTime(
|
||||||
2023,
|
2023,
|
||||||
random.nextInt(12) + 1,
|
random.nextInt(12) + 1,
|
||||||
|
@ -114,7 +114,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
SpeedDialChild(
|
SpeedDialChild(
|
||||||
child: const Icon(Icons.edit),
|
child: const Icon(Icons.edit),
|
||||||
label: AppLocalizations.of(context)!.addNew,
|
label: AppLocalizations.of(context).addNew,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
var sw = await Navigator.of(context).push<Wallet>(
|
var sw = await Navigator.of(context).push<Wallet>(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -128,7 +128,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
}),
|
}),
|
||||||
SpeedDialChild(
|
SpeedDialChild(
|
||||||
child: const Icon(Icons.camera_alt),
|
child: const Icon(Icons.camera_alt),
|
||||||
label: AppLocalizations.of(context)!.addCamera,
|
label: AppLocalizations.of(context).addCamera,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
final ImagePicker picker = ImagePicker();
|
final ImagePicker picker = ImagePicker();
|
||||||
final XFile? media =
|
final XFile? media =
|
||||||
|
@ -138,7 +138,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
SpeedDialChild(
|
SpeedDialChild(
|
||||||
child: const Icon(Icons.image),
|
child: const Icon(Icons.image),
|
||||||
label: AppLocalizations.of(context)!.addGallery,
|
label: AppLocalizations.of(context).addGallery,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
startOcr(ImageSource.gallery);
|
startOcr(ImageSource.gallery);
|
||||||
},
|
},
|
||||||
|
@ -160,7 +160,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
),
|
),
|
||||||
DropdownMenuItem(
|
DropdownMenuItem(
|
||||||
value: -1,
|
value: -1,
|
||||||
child: Text(AppLocalizations.of(context)!.newWallet),
|
child: Text(AppLocalizations.of(context).newWallet),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
onChanged: (v) async {
|
onChanged: (v) async {
|
||||||
|
@ -185,20 +185,20 @@ class _HomeViewState extends State<HomeView> {
|
||||||
actions: [
|
actions: [
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
itemBuilder: (context) => [
|
itemBuilder: (context) => [
|
||||||
AppLocalizations.of(context)!.settings,
|
AppLocalizations.of(context).settings,
|
||||||
AppLocalizations.of(context)!.about
|
AppLocalizations.of(context).about
|
||||||
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
|
||||||
onSelected: (value) {
|
onSelected: (value) {
|
||||||
if (value == AppLocalizations.of(context)!.settings) {
|
if (value == AppLocalizations.of(context).settings) {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => const SettingsView(),
|
builder: (context) => const SettingsView(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (value == AppLocalizations.of(context)!.about) {
|
} else if (value == AppLocalizations.of(context).about) {
|
||||||
showAboutDialog(
|
showAboutDialog(
|
||||||
context: context,
|
context: context,
|
||||||
applicationLegalese: AppLocalizations.of(context)!.license,
|
applicationLegalese: AppLocalizations.of(context).license,
|
||||||
applicationName: "Prašule");
|
applicationName: "Prašule");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -223,14 +223,14 @@ class _HomeViewState extends State<HomeView> {
|
||||||
? Column(
|
? Column(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.noEntries,
|
AppLocalizations.of(context).noEntries,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.noEntriesSub,
|
AppLocalizations.of(context).noEntriesSub,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
@ -307,12 +307,12 @@ class _HomeViewState extends State<HomeView> {
|
||||||
context: context,
|
context: context,
|
||||||
builder: (cx) => PlatformDialog(
|
builder: (cx) => PlatformDialog(
|
||||||
title:
|
title:
|
||||||
AppLocalizations.of(context)!.sureDialog,
|
AppLocalizations.of(context).sureDialog,
|
||||||
content: Text(
|
content: Text(
|
||||||
AppLocalizations.of(context)!.deleteSure),
|
AppLocalizations.of(context).deleteSure),
|
||||||
actions: [
|
actions: [
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!.yes,
|
text: AppLocalizations.of(context).yes,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
selectedWallet?.entries.removeWhere(
|
selectedWallet?.entries.removeWhere(
|
||||||
(e) => e.id == element.id);
|
(e) => e.id == element.id);
|
||||||
|
@ -323,7 +323,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!.no,
|
text: AppLocalizations.of(context).no,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(cx).pop();
|
Navigator.of(cx).pop();
|
||||||
},
|
},
|
||||||
|
@ -365,9 +365,9 @@ class _HomeViewState extends State<HomeView> {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content: Text(AppLocalizations.of(context)!.missingOcr),
|
content: Text(AppLocalizations.of(context).missingOcr),
|
||||||
action: SnackBarAction(
|
action: SnackBarAction(
|
||||||
label: AppLocalizations.of(context)!.download,
|
label: AppLocalizations.of(context).download,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -408,7 +408,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => PlatformDialog(
|
builder: (c) => PlatformDialog(
|
||||||
title: AppLocalizations.of(context)!.ocrLoading),
|
title: AppLocalizations.of(context).ocrLoading),
|
||||||
barrierDismissible: false);
|
barrierDismissible: false);
|
||||||
var string = await FlutterTesseractOcr.extractText(media.path,
|
var string = await FlutterTesseractOcr.extractText(media.path,
|
||||||
language: selected,
|
language: selected,
|
||||||
|
@ -472,7 +472,7 @@ class _HomeViewState extends State<HomeView> {
|
||||||
child: const Text("Cancel"),
|
child: const Text("Cancel"),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
title: AppLocalizations.of(context)!.ocrSelect,
|
title: AppLocalizations.of(context).ocrSelect,
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
...List.generate(
|
...List.generate(
|
||||||
|
|
156
lib/views/settings/graph_type.dart
Normal file
156
lib/views/settings/graph_type.dart
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:prasule/pw/platformdialog.dart';
|
||||||
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class GraphTypeSettingsView extends StatefulWidget {
|
||||||
|
const GraphTypeSettingsView({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<GraphTypeSettingsView> createState() => _GraphTypeSettingsViewState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GraphTypeSettingsViewState extends State<GraphTypeSettingsView> {
|
||||||
|
var _yearly = 1;
|
||||||
|
var _monthly = 2;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SharedPreferences.getInstance().then((prefs) {
|
||||||
|
_yearly = prefs.getInt("yearlygraph") ?? 1;
|
||||||
|
_monthly = prefs.getInt("monthlygraph") ?? 2;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(AppLocalizations.of(context).graphType),
|
||||||
|
),
|
||||||
|
body: SettingsList(
|
||||||
|
applicationType: ApplicationType.both,
|
||||||
|
darkTheme: SettingsThemeData(
|
||||||
|
settingsListBackground: Theme.of(context).colorScheme.background,
|
||||||
|
titleTextColor: Theme.of(context).colorScheme.primary),
|
||||||
|
sections: [
|
||||||
|
SettingsSection(
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.navigation(
|
||||||
|
title: Text(AppLocalizations.of(context).yearly),
|
||||||
|
value: Text(_yearly == 1
|
||||||
|
? AppLocalizations.of(context).barChart
|
||||||
|
: AppLocalizations.of(context).lineChart),
|
||||||
|
onPressed: (c) => showDialog(
|
||||||
|
context: c,
|
||||||
|
builder: (ctx) => PlatformDialog(
|
||||||
|
title: AppLocalizations.of(context).selectType,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(ctx).size.width,
|
||||||
|
child: InkWell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(AppLocalizations.of(context).barChart,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
s.setInt("yearlygraph", 1);
|
||||||
|
_yearly = 1;
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
child: InkWell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).lineChart,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
s.setInt("yearlygraph", 2);
|
||||||
|
_yearly = 2;
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SettingsTile.navigation(
|
||||||
|
title: Text(AppLocalizations.of(context).monthly),
|
||||||
|
value: Text(_monthly == 1
|
||||||
|
? AppLocalizations.of(context).barChart
|
||||||
|
: AppLocalizations.of(context).lineChart),
|
||||||
|
onPressed: (c) => showDialog(
|
||||||
|
context: c,
|
||||||
|
builder: (ctx) => PlatformDialog(
|
||||||
|
title: AppLocalizations.of(context).selectType,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(ctx).size.width,
|
||||||
|
child: InkWell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).barChart,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
s.setInt("monthlygraph", 1);
|
||||||
|
_monthly = 1;
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: MediaQuery.of(ctx).size.width,
|
||||||
|
child: InkWell(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
AppLocalizations.of(context).lineChart,
|
||||||
|
textAlign: TextAlign.center),
|
||||||
|
),
|
||||||
|
onTap: () async {
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
s.setInt("monthlygraph", 2);
|
||||||
|
_monthly = 2;
|
||||||
|
if (!mounted) return;
|
||||||
|
Navigator.of(ctx).pop();
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,13 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:prasule/main.dart';
|
||||||
|
import 'package:prasule/pw/platformroute.dart';
|
||||||
|
import 'package:prasule/views/settings/graph_type.dart';
|
||||||
import 'package:prasule/views/settings/tessdata_list.dart';
|
import 'package:prasule/views/settings/tessdata_list.dart';
|
||||||
import 'package:settings_ui/settings_ui.dart';
|
import 'package:settings_ui/settings_ui.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
class SettingsView extends StatefulWidget {
|
class SettingsView extends StatefulWidget {
|
||||||
const SettingsView({super.key});
|
const SettingsView({super.key});
|
||||||
|
@ -11,10 +17,21 @@ class SettingsView extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SettingsViewState extends State<SettingsView> {
|
class _SettingsViewState extends State<SettingsView> {
|
||||||
|
var _useMaterialYou = true;
|
||||||
|
final _supportsYou = MyApp.appliedYou;
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
SharedPreferences.getInstance().then((s) {
|
||||||
|
_useMaterialYou = s.getBool("useMaterialYou") ?? true;
|
||||||
|
setState(() {});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
|
appBar: AppBar(title: Text(AppLocalizations.of(context).settings)),
|
||||||
body: SettingsList(
|
body: SettingsList(
|
||||||
applicationType: ApplicationType.both,
|
applicationType: ApplicationType.both,
|
||||||
darkTheme: SettingsThemeData(
|
darkTheme: SettingsThemeData(
|
||||||
|
@ -24,16 +41,43 @@ class _SettingsViewState extends State<SettingsView> {
|
||||||
SettingsSection(
|
SettingsSection(
|
||||||
tiles: [
|
tiles: [
|
||||||
SettingsTile.navigation(
|
SettingsTile.navigation(
|
||||||
title: Text(AppLocalizations.of(context)!.downloadedOcr),
|
title: Text(AppLocalizations.of(context).downloadedOcr),
|
||||||
description:
|
description:
|
||||||
Text(AppLocalizations.of(context)!.downloadedOcrDesc),
|
Text(AppLocalizations.of(context).downloadedOcrDesc),
|
||||||
onPressed: (context) => Navigator.of(context).push(
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
MaterialPageRoute(
|
onPressed: (context) => Navigator.of(context)
|
||||||
builder: (c) => const TessdataListView())),
|
.push(platformRoute((c) => const TessdataListView())),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
title: Text(AppLocalizations.of(context)!.ocr),
|
title: Text(AppLocalizations.of(context).ocr),
|
||||||
),
|
),
|
||||||
|
SettingsSection(
|
||||||
|
title: Text(AppLocalizations.of(context).settingsAppearance),
|
||||||
|
tiles: [
|
||||||
|
SettingsTile.navigation(
|
||||||
|
title: Text(AppLocalizations.of(context).graphType),
|
||||||
|
description: Text(AppLocalizations.of(context).graphTypeDesc),
|
||||||
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||||
|
onPressed: (c) => Navigator.of(c).push(
|
||||||
|
platformRoute(
|
||||||
|
(p0) => const GraphTypeSettingsView(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (Platform.isAndroid && _supportsYou)
|
||||||
|
SettingsTile.switchTile(
|
||||||
|
initialValue: _useMaterialYou,
|
||||||
|
onToggle: (v) async {
|
||||||
|
var s = await SharedPreferences.getInstance();
|
||||||
|
s.setBool("useMaterialYou", v);
|
||||||
|
_useMaterialYou = v;
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
title: Text(AppLocalizations.of(context).enableYou),
|
||||||
|
description: Text(AppLocalizations.of(context).enableYouDesc),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,7 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(title: Text(AppLocalizations.of(context)!.ocrData)),
|
appBar: AppBar(title: Text(AppLocalizations.of(context).ocrData)),
|
||||||
body: Center(
|
body: Center(
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: MediaQuery.of(context).size.width * 0.9,
|
width: MediaQuery.of(context).size.width * 0.9,
|
||||||
|
@ -50,8 +50,8 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
title: Text(_tessdata[i].keys.first),
|
title: Text(_tessdata[i].keys.first),
|
||||||
trailing: TextButton(
|
trailing: TextButton(
|
||||||
child: Text(_tessdata[i][_tessdata[i].keys.first]!
|
child: Text(_tessdata[i][_tessdata[i].keys.first]!
|
||||||
? AppLocalizations.of(context)!.downloaded
|
? AppLocalizations.of(context).downloaded
|
||||||
: AppLocalizations.of(context)!.download),
|
: AppLocalizations.of(context).download),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var lang = _tessdata[i].keys.first;
|
var lang = _tessdata[i].keys.first;
|
||||||
if (_tessdata[i][lang]!) {
|
if (_tessdata[i][lang]!) {
|
||||||
|
@ -59,12 +59,12 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => PlatformDialog(
|
builder: (context) => PlatformDialog(
|
||||||
title: AppLocalizations.of(context)!.sureDialog,
|
title: AppLocalizations.of(context).sureDialog,
|
||||||
content: Text(AppLocalizations.of(context)!
|
content: Text(AppLocalizations.of(context)
|
||||||
.deleteOcr(lang)),
|
.deleteOcr(lang)),
|
||||||
actions: [
|
actions: [
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!.yes,
|
text: AppLocalizations.of(context).yes,
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await TessdataApi.deleteData(lang);
|
await TessdataApi.deleteData(lang);
|
||||||
_tessdata[i][lang] = true;
|
_tessdata[i][lang] = true;
|
||||||
|
@ -72,7 +72,7 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!.no,
|
text: AppLocalizations.of(context).no,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
|
@ -91,7 +91,7 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
showDialog(
|
showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (c) => PlatformDialog(
|
builder: (c) => PlatformDialog(
|
||||||
title: AppLocalizations.of(context)!
|
title: AppLocalizations.of(context)
|
||||||
.langDownloadDialog(lang),
|
.langDownloadDialog(lang),
|
||||||
content: StreamBuilder(
|
content: StreamBuilder(
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
|
@ -102,7 +102,7 @@ class _TessdataListViewState extends State<TessdataListView> {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
return const Text("Error");
|
return const Text("Error");
|
||||||
}
|
}
|
||||||
return Text(AppLocalizations.of(context)!
|
return Text(AppLocalizations.of(context)
|
||||||
.langDownloadProgress(snapshot.data!));
|
.langDownloadProgress(snapshot.data!));
|
||||||
},
|
},
|
||||||
stream: progressStream.stream,
|
stream: progressStream.stream,
|
||||||
|
|
|
@ -45,28 +45,28 @@ class _SetupViewState extends State<SetupView> {
|
||||||
if (categories.isEmpty) {
|
if (categories.isEmpty) {
|
||||||
categories = [
|
categories = [
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)!.categoryHealth,
|
name: AppLocalizations.of(context).categoryHealth,
|
||||||
type: EntryType.expense,
|
type: EntryType.expense,
|
||||||
id: 1,
|
id: 1,
|
||||||
icon: IconData(Icons.medical_information.codePoint,
|
icon: IconData(Icons.medical_information.codePoint,
|
||||||
fontFamily: 'MaterialIcons'),
|
fontFamily: 'MaterialIcons'),
|
||||||
),
|
),
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)!.categoryCar,
|
name: AppLocalizations.of(context).categoryCar,
|
||||||
type: EntryType.expense,
|
type: EntryType.expense,
|
||||||
id: 2,
|
id: 2,
|
||||||
icon:
|
icon:
|
||||||
IconData(Icons.car_repair.codePoint, fontFamily: 'MaterialIcons'),
|
IconData(Icons.car_repair.codePoint, fontFamily: 'MaterialIcons'),
|
||||||
),
|
),
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)!.categoryFood,
|
name: AppLocalizations.of(context).categoryFood,
|
||||||
type: EntryType.expense,
|
type: EntryType.expense,
|
||||||
id: 3,
|
id: 3,
|
||||||
icon:
|
icon:
|
||||||
IconData(Icons.restaurant.codePoint, fontFamily: 'MaterialIcons'),
|
IconData(Icons.restaurant.codePoint, fontFamily: 'MaterialIcons'),
|
||||||
),
|
),
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)!.categoryTravel,
|
name: AppLocalizations.of(context).categoryTravel,
|
||||||
type: EntryType.expense,
|
type: EntryType.expense,
|
||||||
id: 4,
|
id: 4,
|
||||||
icon: IconData(Icons.train.codePoint, fontFamily: 'MaterialIcons'),
|
icon: IconData(Icons.train.codePoint, fontFamily: 'MaterialIcons'),
|
||||||
|
@ -90,16 +90,16 @@ class _SetupViewState extends State<SetupView> {
|
||||||
showNextButton: true,
|
showNextButton: true,
|
||||||
showBackButton: true,
|
showBackButton: true,
|
||||||
showDoneButton: true,
|
showDoneButton: true,
|
||||||
next: Text(AppLocalizations.of(context)!.next),
|
next: Text(AppLocalizations.of(context).next),
|
||||||
back: Text(AppLocalizations.of(context)!.back),
|
back: Text(AppLocalizations.of(context).back),
|
||||||
done: Text(AppLocalizations.of(context)!.finish),
|
done: Text(AppLocalizations.of(context).finish),
|
||||||
onDone: () {
|
onDone: () {
|
||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
ScaffoldMessenger.of(context)
|
ScaffoldMessenger.of(context)
|
||||||
.clearSnackBars(); // TODO: iOS replacement
|
.clearSnackBars(); // TODO: iOS replacement
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content:
|
content:
|
||||||
Text(AppLocalizations.of(context)!.errorEmptyName)));
|
Text(AppLocalizations.of(context).errorEmptyName)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
var wallet = Wallet(
|
var wallet = Wallet(
|
||||||
|
@ -113,7 +113,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
content:
|
content:
|
||||||
Text(AppLocalizations.of(context)!.walletExists),
|
Text(AppLocalizations.of(context).walletExists),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
|
@ -137,7 +137,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
titleWidget: Padding(
|
titleWidget: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.welcome,
|
AppLocalizations.of(context).welcome,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24, fontWeight: FontWeight.bold),
|
fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
|
@ -150,7 +150,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
if (!widget.newWallet)
|
if (!widget.newWallet)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.welcomeAboutPrasule),
|
AppLocalizations.of(context).welcomeAboutPrasule),
|
||||||
),
|
),
|
||||||
if (!widget.newWallet)
|
if (!widget.newWallet)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
|
@ -158,7 +158,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.welcomeInstruction),
|
AppLocalizations.of(context).welcomeInstruction),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -169,7 +169,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
titleWidget: Padding(
|
titleWidget: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.setupWalletNameCurrency,
|
AppLocalizations.of(context).setupWalletNameCurrency,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24, fontWeight: FontWeight.bold),
|
fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
@ -182,7 +182,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
width: MediaQuery.of(context).size.width * 0.7,
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
child: PlatformField(
|
child: PlatformField(
|
||||||
labelText:
|
labelText:
|
||||||
AppLocalizations.of(context)!.setupNamePlaceholder,
|
AppLocalizations.of(context).setupNamePlaceholder,
|
||||||
onChanged: (t) {
|
onChanged: (t) {
|
||||||
name = t;
|
name = t;
|
||||||
},
|
},
|
||||||
|
@ -192,7 +192,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
height: 5,
|
height: 5,
|
||||||
),
|
),
|
||||||
PlatformButton(
|
PlatformButton(
|
||||||
text: AppLocalizations.of(context)!
|
text: AppLocalizations.of(context)
|
||||||
.setupCurrency(_selectedCurrency.code),
|
.setupCurrency(_selectedCurrency.code),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
showCurrencyPicker(
|
showCurrencyPicker(
|
||||||
|
@ -211,7 +211,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
width: MediaQuery.of(context).size.width * 0.7,
|
width: MediaQuery.of(context).size.width * 0.7,
|
||||||
child: PlatformField(
|
child: PlatformField(
|
||||||
labelText:
|
labelText:
|
||||||
AppLocalizations.of(context)!.setupStartingBalance,
|
AppLocalizations.of(context).setupStartingBalance,
|
||||||
keyboardType: const TextInputType.numberWithOptions(
|
keyboardType: const TextInputType.numberWithOptions(
|
||||||
decimal: true),
|
decimal: true),
|
||||||
inputFormatters: [
|
inputFormatters: [
|
||||||
|
@ -233,7 +233,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
titleWidget: Padding(
|
titleWidget: Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.setupCategoriesHeading,
|
AppLocalizations.of(context).setupCategoriesHeading,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontSize: 24, fontWeight: FontWeight.bold),
|
fontSize: 24, fontWeight: FontWeight.bold),
|
||||||
|
@ -243,7 +243,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
AppLocalizations.of(context)!.setupCategoriesEditHint,
|
AppLocalizations.of(context).setupCategoriesEditHint,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
|
@ -296,17 +296,17 @@ class _SetupViewState extends State<SetupView> {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.ok),
|
AppLocalizations.of(context).ok),
|
||||||
),
|
),
|
||||||
TextButton(
|
TextButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
AppLocalizations.of(context)!.cancel),
|
AppLocalizations.of(context).cancel),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
title: AppLocalizations.of(context)!
|
title: AppLocalizations.of(context)
|
||||||
.setupCategoriesEditingName,
|
.setupCategoriesEditingName,
|
||||||
content: SizedBox(
|
content: SizedBox(
|
||||||
width: 400,
|
width: 400,
|
||||||
|
@ -336,7 +336,7 @@ class _SetupViewState extends State<SetupView> {
|
||||||
}
|
}
|
||||||
categories.add(
|
categories.add(
|
||||||
WalletCategory(
|
WalletCategory(
|
||||||
name: AppLocalizations.of(context)!
|
name: AppLocalizations.of(context)
|
||||||
.setupWalletNamePlaceholder,
|
.setupWalletNamePlaceholder,
|
||||||
type: EntryType.expense,
|
type: EntryType.expense,
|
||||||
id: id,
|
id: id,
|
||||||
|
|
108
pubspec.lock
108
pubspec.lock
|
@ -77,10 +77,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: "64e12b0521812d1684b1917bc80945625391cb9bdd4312536b1d69dcb6133ed8"
|
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.1"
|
version: "2.4.2"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
|
@ -109,10 +109,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: built_value
|
name: built_value
|
||||||
sha256: "69acb7007eb2a31dc901512bfe0f7b767168be34cb734835d54c070bfa74c1b2"
|
sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.8.0"
|
version: "8.8.1"
|
||||||
characters:
|
characters:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -133,10 +133,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: cli_util
|
name: cli_util
|
||||||
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.0"
|
version: "0.4.1"
|
||||||
clock:
|
clock:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -149,10 +149,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: code_builder
|
name: code_builder
|
||||||
sha256: b2151ce26a06171005b379ecff6e08d34c470180ffe16b8e14b6d52be292b55f
|
sha256: feee43a5c05e7b3199bb375a86430b8ada1b04104f2923d0e03cc01ca87b6d84
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.8.0"
|
version: "4.9.0"
|
||||||
collection:
|
collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -173,10 +173,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: coverage
|
name: coverage
|
||||||
sha256: ac86d3abab0f165e4b8f561280ff4e066bceaac83c424dd19f1ae2c2fcd12ca9
|
sha256: "8acabb8306b57a409bf4c83522065672ee13179297a6bb0cb9ead73948df7c76"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.7.1"
|
version: "1.7.2"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -205,10 +205,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: currency_picker
|
name: currency_picker
|
||||||
sha256: "5b87c259dbdb4e032c6b9abd22158782868505b5217b453c6c36445612a3d34c"
|
sha256: eb75deb7bc92e3f31e1b8ad4efacf71371e8e49d7a0eebd1c1a8e9fae58cc23d
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.19"
|
version: "2.0.20"
|
||||||
dart_style:
|
dart_style:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -237,10 +237,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: dynamic_color
|
name: dynamic_color
|
||||||
sha256: "8b8bd1d798bd393e11eddeaa8ae95b12ff028bf7d5998fc5d003488cd5f4ce2f"
|
sha256: a866f1f8947bfdaf674d7928e769eac7230388a2e7a2542824fad4bb5b87be3b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.6.8"
|
version: "1.6.9"
|
||||||
equatable:
|
equatable:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -317,10 +317,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: fl_chart
|
name: fl_chart
|
||||||
sha256: fe6fec7d85975a99c73b9515a69a6e291364accfa0e4a5b3ce6de814d74b9a1c
|
sha256: "5a74434cc83bf64346efb562f1a06eefaf1bcb530dc3d96a104f631a1eff8d79"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.66.0"
|
version: "0.65.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
|
@ -531,18 +531,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker
|
name: image_picker
|
||||||
sha256: "7d7f2768df2a8b0a3cefa5ef4f84636121987d403130e70b17ef7e2cf650ba84"
|
sha256: fc712337719239b0b6e41316aa133350b078fa39b6cbd706b61f3fd421b03c77
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.5"
|
||||||
image_picker_android:
|
image_picker_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_android
|
name: image_picker_android
|
||||||
sha256: d6a6e78821086b0b737009b09363018309bbc6de3fd88cc5c26bc2bb44a4957f
|
sha256: ecdc963d2aa67af5195e723a40580f802d4392e31457a12a562b3e2bd6a396fe
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.8+2"
|
version: "0.8.9+1"
|
||||||
image_picker_for_web:
|
image_picker_for_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -555,10 +555,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: image_picker_ios
|
name: image_picker_ios
|
||||||
sha256: "76ec722aeea419d03aa915c2c96bf5b47214b053899088c9abb4086ceecf97a7"
|
sha256: eac0a62104fa12feed213596df0321f57ce5a572562f72a68c4ff81e9e4caacf
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.8.8+4"
|
version: "0.8.9"
|
||||||
image_picker_linux:
|
image_picker_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -876,6 +876,62 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.2"
|
version: "2.0.2"
|
||||||
|
shared_preferences:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: shared_preferences
|
||||||
|
sha256: "81429e4481e1ccfb51ede496e916348668fd0921627779233bd24cc3ff6abd02"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
shared_preferences_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_android
|
||||||
|
sha256: "8568a389334b6e83415b6aae55378e158fbc2314e074983362d20c562780fb06"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.1"
|
||||||
|
shared_preferences_foundation:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_foundation
|
||||||
|
sha256: "7bf53a9f2d007329ee6f3df7268fd498f8373602f943c975598bbb34649b62a7"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.4"
|
||||||
|
shared_preferences_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_linux
|
||||||
|
sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
|
shared_preferences_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_platform_interface
|
||||||
|
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
|
shared_preferences_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_web
|
||||||
|
sha256: "7b15ffb9387ea3e237bb7a66b8a23d2147663d391cafc5c8f37b2e7b4bde5d21"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.2"
|
||||||
|
shared_preferences_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: shared_preferences_windows
|
||||||
|
sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.2"
|
||||||
shelf:
|
shelf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -917,10 +973,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: source_gen
|
name: source_gen
|
||||||
sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16
|
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.5.0"
|
||||||
source_helper:
|
source_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -1077,10 +1133,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: web_socket_channel
|
name: web_socket_channel
|
||||||
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
|
sha256: "045ec2137c27bf1a32e6ffa0e734d532a6677bf9016a0d1a406c54e499ff945b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.1"
|
||||||
webdriver:
|
webdriver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
name: prasule
|
name: prasule
|
||||||
description: Open-source private expense tracker
|
description: Open-source private expense tracker
|
||||||
|
|
||||||
version: 1.0.0-alpha2+2
|
version: 1.0.0-alpha.2+2
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.1.0-262.2.beta <4.0.0'
|
sdk: '>=3.1.0-262.2.beta <4.0.0'
|
||||||
|
@ -38,7 +38,8 @@ dependencies:
|
||||||
flutter_slidable: ^3.0.0
|
flutter_slidable: ^3.0.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
fl_chart: ^0.66.0
|
fl_chart: ^0.65.0
|
||||||
|
shared_preferences: ^2.2.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue