feat: localization

This commit is contained in:
Matyáš Caras 2023-10-11 18:37:18 +02:00
parent de8f57fcc8
commit f8f40f6db6
Signed by untrusted user who does not match committer: hernik
GPG key ID: 2A3175F98820C5C6
10 changed files with 247 additions and 111 deletions

3
l10n.yaml Normal file
View file

@ -0,0 +1,3 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

92
lib/l10n/app_en.arb Normal file
View file

@ -0,0 +1,92 @@
{
"categoryHealth": "Health",
"categoryCar": "Car",
"categoryFood": "Food",
"categoryTravel": "Travel",
"next": "Next",
"back": "Back",
"finish": "Finish",
"errorEmptyName": "Name cannot be empty",
"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",
"setupNamePlaceholder": "Your awesome name here...",
"setupCurrency": "Currency: {currency}",
"@setupCurrency": {
"description": "Shows the currently selected currency on the setup screen",
"placeholders": {
"currency": {
"type": "String",
"example": "CZK"
}
}
},
"setupCategoriesHeading": "Create categories",
"setupCategoriesEditHint": "Tap on the icon or name to edit it",
"ok": "Ok",
"cancel": "Cancel",
"setupCategoriesEditingName": "Editing name",
"setupWalletNamePlaceholder": "Edit me",
"addNew": "Add new",
"addCamera": "Add through camera",
"addGallery": "Add through saved image",
"home": "Home",
"settings": "Settings",
"about": "About",
"noEntries": "No entries :(",
"noEntriesSub": "Add one using the floating action button.",
"sureDialog": "Are you sure?",
"deleteSure": "Do you really want to delete this entry?",
"missingOcr": "You don't have any OCR language data downloaded!",
"download": "Download",
"ocrLoading": "Loading text from image, please wait a moment...",
"yes": "Yes",
"no": "No",
"ocrSelect": "Select languages for OCR",
"createEntry": "Create new entry",
"name": "Name",
"amount": "Amount",
"type": "Type",
"expense": "Expense",
"income": "Income",
"category": "Category",
"save": "Save",
"downloadedOcr": "View downloaded OCR data",
"downloadedOcrDesc": "This data is used by the OCR engine to recognize text from pictues",
"ocr": "OCR",
"ocrData": "OCR Data",
"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": {
"description": "Shown when a user wants to delete OCR data through settings",
"placeholders": {
"lang": {
"type": "String",
"example": "ces"
}
}
},
"langDownloadDialog": "Downloading $lang, please wait...",
"@langDownloadDialog": {
"description": "Shown as a title of a dialog while downloading new OCR data",
"placeholders": {
"lang": {
"type": "String",
"example": "ces"
}
}
},
"langDownloadProgress": "Download progress: $progress %",
"@langDownloadProgress": {
"description": "Progress percentage shown while downloading OCR data",
"placeholders": {
"progress":{
"type":"num",
"example":"99.7"
}
}
},
"addingFromOcr": "Add from OCR",
"license":"©️ 2023 Matyáš Caras\nReleased under the GNU AGPL license version 3"
}

View file

@ -3,9 +3,11 @@ import 'dart:io';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:logger/logger.dart'; 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';
void main() { void main() {
runApp(const MyApp()); runApp(const MyApp());
@ -22,6 +24,12 @@ class MyApp extends StatelessWidget {
return (Platform.isAndroid) return (Platform.isAndroid)
? DynamicColorBuilder( ? DynamicColorBuilder(
builder: (light, dark) => MaterialApp( builder: (light, dark) => MaterialApp(
localizationsDelegates: const [
AppLocalizations.delegate,
...GlobalMaterialLocalizations.delegates,
...GlobalCupertinoLocalizations.delegates
],
supportedLocales: AppLocalizations.supportedLocales,
title: 'Prašule', title: 'Prašule',
theme: ThemeData( theme: ThemeData(
colorScheme: light ?? lightColorScheme, colorScheme: light ?? lightColorScheme,

View file

@ -7,6 +7,7 @@ import 'package:prasule/api/wallet.dart';
import 'package:prasule/api/walletmanager.dart'; import 'package:prasule/api/walletmanager.dart';
import 'package:prasule/pw/platformbutton.dart'; import 'package:prasule/pw/platformbutton.dart';
import 'package:prasule/pw/platformfield.dart'; import 'package:prasule/pw/platformfield.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class CreateEntryView extends StatefulWidget { class CreateEntryView extends StatefulWidget {
final Wallet w; final Wallet w;
@ -43,7 +44,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: const Text("Create new entry"), title: Text(AppLocalizations.of(context)!.createEntry),
), ),
body: SizedBox( body: SizedBox(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
@ -56,7 +57,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: "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;
@ -69,7 +70,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: "Amount", labelText: AppLocalizations.of(context)!.amount,
controller: TextEditingController( controller: TextEditingController(
text: newEntry.data.amount.toString()), text: newEntry.data.amount.toString()),
keyboardType: keyboardType:
@ -99,8 +100,8 @@ class _CreateEntryViewState extends State<CreateEntryView> {
value: EntryType.expense, value: EntryType.expense,
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width * 0.8 - 24, width: MediaQuery.of(context).size.width * 0.8 - 24,
child: const Text( child: Text(
"Expense", AppLocalizations.of(context)!.expense,
), ),
), ),
), ),
@ -108,7 +109,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: const Text("Income"), child: Text(AppLocalizations.of(context)!.income),
), ),
), ),
], ],
@ -122,7 +123,7 @@ class _CreateEntryViewState extends State<CreateEntryView> {
const SizedBox( const SizedBox(
height: 20, height: 20,
), ),
const Text("Category"), Text(AppLocalizations.of(context)!.category),
const SizedBox( const SizedBox(
height: 10, height: 10,
), ),
@ -155,13 +156,14 @@ class _CreateEntryViewState extends State<CreateEntryView> {
height: 15, height: 15,
), ),
PlatformButton( PlatformButton(
text: "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(
const SnackBar( SnackBar(
content: Text("Name cannot be empty"), content: Text(
AppLocalizations.of(context)!.errorEmptyName),
), ),
); );
return; return;

View file

@ -19,6 +19,7 @@ import 'package:prasule/views/multientry_creator.dart';
import 'package:prasule/views/settings/settings.dart'; import 'package:prasule/views/settings/settings.dart';
import 'package:prasule/views/settings/tessdata_list.dart'; import 'package:prasule/views/settings/tessdata_list.dart';
import 'package:prasule/views/setup.dart'; import 'package:prasule/views/setup.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class HomeView extends StatefulWidget { class HomeView extends StatefulWidget {
const HomeView({super.key}); const HomeView({super.key});
@ -64,7 +65,7 @@ class _HomeViewState extends State<HomeView> {
children: [ children: [
SpeedDialChild( SpeedDialChild(
child: const Icon(Icons.edit), child: const Icon(Icons.edit),
label: "Add new", 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(
@ -78,7 +79,7 @@ class _HomeViewState extends State<HomeView> {
}), }),
SpeedDialChild( SpeedDialChild(
child: const Icon(Icons.camera_alt), child: const Icon(Icons.camera_alt),
label: "Add through camera", label: AppLocalizations.of(context)!.addCamera,
onTap: () async { onTap: () async {
final ImagePicker picker = ImagePicker(); final ImagePicker picker = ImagePicker();
final XFile? media = final XFile? media =
@ -88,7 +89,7 @@ class _HomeViewState extends State<HomeView> {
), ),
SpeedDialChild( SpeedDialChild(
child: const Icon(Icons.image), child: const Icon(Icons.image),
label: "Add through saved image", label: AppLocalizations.of(context)!.addGallery,
onTap: () { onTap: () {
startOcr(ImageSource.gallery); startOcr(ImageSource.gallery);
}, },
@ -96,24 +97,24 @@ class _HomeViewState extends State<HomeView> {
], ],
), ),
appBar: AppBar( appBar: AppBar(
title: const Text("Home"), title: Text(AppLocalizations.of(context)!.home),
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
itemBuilder: (context) => ["Settings", "About"] itemBuilder: (context) => [
.map((e) => PopupMenuItem(value: e, child: Text(e))) AppLocalizations.of(context)!.settings,
.toList(), AppLocalizations.of(context)!.about
].map((e) => PopupMenuItem(value: e, child: Text(e))).toList(),
onSelected: (value) { onSelected: (value) {
if (value == "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 == "About") { } else if (value == AppLocalizations.of(context)!.about) {
showAboutDialog( showAboutDialog(
context: context, context: context,
applicationLegalese: applicationLegalese: AppLocalizations.of(context)!.license,
"©️ 2023 Matyáš Caras\nReleased under the GNU AGPL license version 3",
applicationName: "Prašule"); applicationName: "Prašule");
} }
}, },
@ -135,17 +136,17 @@ class _HomeViewState extends State<HomeView> {
], ],
) )
: (selectedWallet!.entries.isEmpty) : (selectedWallet!.entries.isEmpty)
? const Column( ? Column(
children: [ children: [
Text( Text(
"No entries :(", AppLocalizations.of(context)!.noEntries,
style: TextStyle( style: const TextStyle(
fontSize: 20, fontSize: 20,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
Text( Text(
"Add one using the floating action button.", AppLocalizations.of(context)!.noEntriesSub,
) )
], ],
) )
@ -198,9 +199,10 @@ class _HomeViewState extends State<HomeView> {
showDialog( showDialog(
context: context, context: context,
builder: (cx) => PlatformDialog( builder: (cx) => PlatformDialog(
title: "Are you sure", title:
content: const Text( AppLocalizations.of(context)!.sureDialog,
"Do you really want to delete this entry?"), content: Text(
AppLocalizations.of(context)!.deleteSure),
actions: [ actions: [
PlatformButton( PlatformButton(
text: "Yes", text: "Yes",
@ -256,10 +258,9 @@ class _HomeViewState extends State<HomeView> {
if (!mounted) return; if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: content: Text(AppLocalizations.of(context)!.missingOcr),
const Text("You do not have any OCR language data downloaded"),
action: SnackBarAction( action: SnackBarAction(
label: "Download", label: AppLocalizations.of(context)!.download,
onPressed: () { onPressed: () {
Navigator.of(context).push( Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
@ -299,9 +300,8 @@ class _HomeViewState extends State<HomeView> {
if (!mounted) return; if (!mounted) return;
showDialog( showDialog(
context: context, context: context,
builder: (c) => const PlatformDialog( builder: (c) => PlatformDialog(
title: title: AppLocalizations.of(context)!.ocrLoading),
"Loading text from image, please wait a moment..."),
barrierDismissible: false); barrierDismissible: false);
var string = await FlutterTesseractOcr.extractText(media.path, var string = await FlutterTesseractOcr.extractText(media.path,
language: selected, language: selected,
@ -339,7 +339,7 @@ class _HomeViewState extends State<HomeView> {
}, },
child: const Text("Cancel")), child: const Text("Cancel")),
], ],
title: "Select languages for OCR", title: AppLocalizations.of(context)!.ocrSelect,
content: Column( content: Column(
children: [ children: [
...List.generate( ...List.generate(

View file

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
class SettingsView extends StatefulWidget { class SettingsView extends StatefulWidget {
const SettingsView({super.key}); const SettingsView({super.key});
@ -13,7 +14,7 @@ class _SettingsViewState extends State<SettingsView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text("Settings")), appBar: AppBar(title: Text(AppLocalizations.of(context)!.settings)),
body: SettingsList( body: SettingsList(
applicationType: ApplicationType.both, applicationType: ApplicationType.both,
darkTheme: SettingsThemeData( darkTheme: SettingsThemeData(
@ -23,15 +24,15 @@ class _SettingsViewState extends State<SettingsView> {
SettingsSection( SettingsSection(
tiles: [ tiles: [
SettingsTile.navigation( SettingsTile.navigation(
title: const Text("View downloaded OCR data"), title: Text(AppLocalizations.of(context)!.downloadedOcr),
description: const Text( description:
"This data is used by the OCR to recognise text from pictures"), Text(AppLocalizations.of(context)!.downloadedOcrDesc),
onPressed: (context) => Navigator.of(context).push( onPressed: (context) => Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (c) => const TessdataListView())), builder: (c) => const TessdataListView())),
) )
], ],
title: const Text("OCR"), title: Text(AppLocalizations.of(context)!.ocr),
), ),
], ],
), ),

View file

@ -7,6 +7,7 @@ import 'package:prasule/main.dart';
import 'package:prasule/network/tessdata.dart'; import 'package:prasule/network/tessdata.dart';
import 'package:prasule/pw/platformbutton.dart'; import 'package:prasule/pw/platformbutton.dart';
import 'package:prasule/pw/platformdialog.dart'; import 'package:prasule/pw/platformdialog.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class TessdataListView extends StatefulWidget { class TessdataListView extends StatefulWidget {
const TessdataListView({super.key}); const TessdataListView({super.key});
@ -28,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: const Text("OCR data")), 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,
@ -49,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]!
? "Downloaded" ? AppLocalizations.of(context)!.downloaded
: "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]!) {
@ -58,12 +59,12 @@ class _TessdataListViewState extends State<TessdataListView> {
showDialog( showDialog(
context: context, context: context,
builder: (context) => PlatformDialog( builder: (context) => PlatformDialog(
title: "Warning", title: AppLocalizations.of(context)!.sureDialog,
content: Text( content: Text(AppLocalizations.of(context)!
"Do you want to delete '$lang' OCR data?\nYou will not be able to utilize this language when using the OCR functions."), .deleteOcr(lang)),
actions: [ actions: [
PlatformButton( PlatformButton(
text: "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;
@ -71,7 +72,7 @@ class _TessdataListViewState extends State<TessdataListView> {
}, },
), ),
PlatformButton( PlatformButton(
text: "No", text: AppLocalizations.of(context)!.no,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@ -90,7 +91,8 @@ class _TessdataListViewState extends State<TessdataListView> {
showDialog( showDialog(
context: context, context: context,
builder: (c) => PlatformDialog( builder: (c) => PlatformDialog(
title: "Downloading $lang, please wait...", title: AppLocalizations.of(context)!
.langDownloadDialog(lang),
content: StreamBuilder( content: StreamBuilder(
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == if (snapshot.connectionState ==
@ -100,8 +102,8 @@ class _TessdataListViewState extends State<TessdataListView> {
if (snapshot.hasError) { if (snapshot.hasError) {
return const Text("Error"); return const Text("Error");
} }
return Text( return Text(AppLocalizations.of(context)!
"Download progress: ${snapshot.data} %"); .langDownloadProgress(snapshot.data!));
}, },
stream: progressStream.stream, stream: progressStream.stream,
), ),

View file

@ -9,6 +9,7 @@ import 'package:prasule/pw/platformbutton.dart';
import 'package:prasule/pw/platformdialog.dart'; import 'package:prasule/pw/platformdialog.dart';
import 'package:prasule/pw/platformfield.dart'; import 'package:prasule/pw/platformfield.dart';
import 'package:prasule/views/home.dart'; import 'package:prasule/views/home.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
class SetupView extends StatefulWidget { class SetupView extends StatefulWidget {
const SetupView({super.key}); const SetupView({super.key});
@ -31,34 +32,42 @@ class _SetupViewState extends State<SetupView> {
"space_between_amount_and_symbol": false, "space_between_amount_and_symbol": false,
"symbol_on_left": true, "symbol_on_left": true,
}); });
var categories = <WalletCategory>[ var categories = <WalletCategory>[];
var name = "";
@override
void initState() {
super.initState();
categories = [
WalletCategory( WalletCategory(
name: "Health", 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: "Car", name: AppLocalizations.of(context)!.categoryCar,
type: EntryType.expense, type: EntryType.expense,
id: 2, id: 2,
icon: IconData(Icons.car_repair.codePoint, fontFamily: 'MaterialIcons'), icon: IconData(Icons.car_repair.codePoint, fontFamily: 'MaterialIcons'),
), ),
WalletCategory( WalletCategory(
name: "Food", name: AppLocalizations.of(context)!.categoryFood,
type: EntryType.expense, type: EntryType.expense,
id: 3, id: 3,
icon: IconData(Icons.restaurant.codePoint, fontFamily: 'MaterialIcons'), icon: IconData(Icons.restaurant.codePoint, fontFamily: 'MaterialIcons'),
), ),
WalletCategory( WalletCategory(
name: "Travel", 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'),
), ),
]; ];
var name = ""; setState(() {});
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -73,15 +82,16 @@ class _SetupViewState extends State<SetupView> {
showNextButton: true, showNextButton: true,
showBackButton: true, showBackButton: true,
showDoneButton: true, showDoneButton: true,
next: const Text("Next"), next: Text(AppLocalizations.of(context)!.next),
back: const Text("Back"), back: Text(AppLocalizations.of(context)!.back),
done: const Text("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( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
const SnackBar(content: Text("Name cannot be empty"))); content:
Text(AppLocalizations.of(context)!.errorEmptyName)));
return; return;
} }
var wallet = Wallet( var wallet = Wallet(
@ -100,39 +110,43 @@ class _SetupViewState extends State<SetupView> {
PageViewModel( PageViewModel(
decoration: decoration:
const PageDecoration(bodyAlignment: Alignment.center), const PageDecoration(bodyAlignment: Alignment.center),
titleWidget: const Padding( titleWidget: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
"Welcome!", AppLocalizations.of(context)!.welcome,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
), ),
bodyWidget: const Column( bodyWidget: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Flexible( Flexible(
child: Text( child: Text(
"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.")), AppLocalizations.of(context)!.welcomeAboutPrasule),
SizedBox( ),
const SizedBox(
height: 5, height: 5,
), ),
Flexible( Flexible(
child: Text( child: Text(
"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.")), AppLocalizations.of(context)!.welcomeInstruction),
),
], ],
), ),
), ),
PageViewModel( PageViewModel(
decoration: decoration:
const PageDecoration(bodyAlignment: Alignment.center), const PageDecoration(bodyAlignment: Alignment.center),
titleWidget: const Padding( titleWidget: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
"Set your wallet's name and currency", AppLocalizations.of(context)!.setupWalletNameCurrency,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
), ),
), ),
bodyWidget: Column( bodyWidget: Column(
@ -141,7 +155,8 @@ class _SetupViewState extends State<SetupView> {
SizedBox( SizedBox(
width: MediaQuery.of(context).size.width * 0.7, width: MediaQuery.of(context).size.width * 0.7,
child: PlatformField( child: PlatformField(
labelText: "Your awesome name here...", labelText:
AppLocalizations.of(context)!.setupNamePlaceholder,
onChanged: (t) { onChanged: (t) {
name = t; name = t;
}, },
@ -151,7 +166,8 @@ class _SetupViewState extends State<SetupView> {
height: 5, height: 5,
), ),
PlatformButton( PlatformButton(
text: "Currency: ${_selectedCurrency.code}", text: AppLocalizations.of(context)!
.setupCurrency(_selectedCurrency.code),
onPressed: () { onPressed: () {
showCurrencyPicker( showCurrencyPicker(
context: context, context: context,
@ -168,19 +184,20 @@ class _SetupViewState extends State<SetupView> {
PageViewModel( PageViewModel(
decoration: decoration:
const PageDecoration(bodyAlignment: Alignment.center), const PageDecoration(bodyAlignment: Alignment.center),
titleWidget: const Padding( titleWidget: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
"Create categories", AppLocalizations.of(context)!.setupCategoriesHeading,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold), style: const TextStyle(
fontSize: 24, fontWeight: FontWeight.bold),
), ),
), ),
bodyWidget: Column( bodyWidget: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const Text( Text(
"Tap on the icon or name to edit it", AppLocalizations.of(context)!.setupCategoriesEditHint,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
SizedBox( SizedBox(
@ -232,16 +249,19 @@ class _SetupViewState extends State<SetupView> {
categories[i].name = controller.text; categories[i].name = controller.text;
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text("Ok"), child: Text(
AppLocalizations.of(context)!.ok),
), ),
TextButton( TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text("Cancel"), child: Text(
AppLocalizations.of(context)!.cancel),
), ),
], ],
title: "Editing name", title: AppLocalizations.of(context)!
.setupCategoriesEditingName,
content: SizedBox( content: SizedBox(
width: 400, width: 400,
child: child:
@ -270,7 +290,8 @@ class _SetupViewState extends State<SetupView> {
} }
categories.add( categories.add(
WalletCategory( WalletCategory(
name: "Edit me", name: AppLocalizations.of(context)!
.setupWalletNamePlaceholder,
type: EntryType.expense, type: EntryType.expense,
id: id, id: id,
icon: IconData(Icons.question_mark.codePoint, icon: IconData(Icons.question_mark.codePoint,

View file

@ -387,6 +387,11 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.3" version: "2.0.3"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:

View file

@ -45,12 +45,14 @@ dependencies:
flutter_iconpicker: ^3.2.4 flutter_iconpicker: ^3.2.4
dynamic_color: ^1.6.6 dynamic_color: ^1.6.6
introduction_screen: ^3.1.11 introduction_screen: ^3.1.11
intl: ^0.18.1 intl: any
grouped_list: ^5.1.2 grouped_list: ^5.1.2
flutter_speed_dial: ^7.0.0 flutter_speed_dial: ^7.0.0
image_picker: ^1.0.1 image_picker: ^1.0.1
flutter_tesseract_ocr: ^0.4.23 flutter_tesseract_ocr: ^0.4.23
flutter_slidable: ^3.0.0 flutter_slidable: ^3.0.0
flutter_localizations:
sdk: flutter
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@ -91,7 +93,7 @@ flutter_launcher_icons:
# The following section is specific to Flutter packages. # The following section is specific to Flutter packages.
flutter: flutter:
generate: true
# The following line ensures that the Material Icons font is # The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in # included with your application, so that you can use the icons in
# the material Icons class. # the material Icons class.