feat: umožnit ukládat více dnů offline

This commit is contained in:
Matyáš Caras 2022-11-21 20:26:55 +01:00
parent db84284eca
commit c913e3bb95
11 changed files with 226 additions and 97 deletions

View file

@ -1,3 +1,7 @@
# 1.5.0
- umožnit ukládat více dnů offline
- chyba při ukládání offline vás nyní již nevyhodí ale zobrazí pouze zprávu
- "Přihlašování" pop-up zmizí, když není přihlášení úspěšné
# 1.4.2 # 1.4.2
- aktualizace knihovny flutter_local_notifications - aktualizace knihovny flutter_local_notifications
- lepší podpora pro Android 13 - lepší podpora pro Android 13

View file

@ -147,6 +147,8 @@ abstract class Languages {
String get saveOffline; String get saveOffline;
String get saveCount;
String get skipWeekend; String get skipWeekend;
String get checkOrdered; String get checkOrdered;
@ -162,6 +164,8 @@ abstract class Languages {
String get mustLogout; String get mustLogout;
String get errorSaving;
// Oznámit před obědem // Oznámit před obědem
String get lunchNotif; String get lunchNotif;

View file

@ -237,4 +237,11 @@ class LanguageCz extends Languages {
@override @override
String get review => "Ohodnotit aplikaci"; String get review => "Ohodnotit aplikaci";
@override
String get saveCount => "Počet dnů dostupných offline (Akt. limit je 7)";
@override
String get errorSaving =>
"Při ukládání offline nastala chyba, zkuste to znovu později.";
} }

View file

@ -235,4 +235,11 @@ class LanguageEn extends Languages {
@override @override
String get review => "Review the app"; String get review => "Review the app";
@override
String get saveCount => "Number of days to save offline (Current limit is 7)";
@override
String get errorSaving =>
"An error occured while trying to save menu offline, try again later.";
} }

View file

@ -1,4 +1,3 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -13,7 +12,6 @@ import 'package:canteenlib/canteenlib.dart';
import 'package:opencanteen/okna/offline_jidelnicek.dart'; import 'package:opencanteen/okna/offline_jidelnicek.dart';
import 'package:opencanteen/okna/welcome.dart'; import 'package:opencanteen/okna/welcome.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/data/latest_all.dart' as tz;
@ -237,6 +235,7 @@ class _LoginPageState extends State<LoginPage> {
} }
} on PlatformException { } on PlatformException {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop();
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -245,6 +244,7 @@ class _LoginPageState extends State<LoginPage> {
); );
} catch (_) { } catch (_) {
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pop();
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@ -418,31 +418,11 @@ class _LoginPageState extends State<LoginPage> {
/// Získá offline soubor a zobrazí údaje /// Získá offline soubor a zobrazí údaje
void goOffline() async { void goOffline() async {
Directory appDocDir = await getApplicationDocumentsDirectory(); if (!mounted) return;
var den = DateTime.now(); Navigator.pushAndRemoveUntil(
var soubor = File( context,
"${appDocDir.path}/jidelnicek_${den.year}-${den.month}-${den.day}.json"); MaterialPageRoute(builder: ((context) => const OfflineJidelnicek())),
if (soubor.existsSync()) { (route) => false);
// načteme offline jídelníček
var input = await soubor.readAsString();
var r = jsonDecode(input);
List<OfflineJidlo> jidla = [];
for (var j in r) {
jidla.add(OfflineJidlo(
nazev: j["nazev"],
varianta: j["varianta"],
objednano: j["objednano"],
cena: j["cena"],
naBurze: j["naBurze"],
den: DateTime.parse(j["den"])));
}
if (!mounted) return;
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(
builder: ((context) => OfflineJidelnicek(jidla: jidla))),
(route) => false);
}
} }
} }

View file

@ -382,44 +382,52 @@ class _JidelnicekPageState extends State<JidelnicekPage> {
kontrolaTyden(context); kontrolaTyden(context);
} }
/// uložení jídelníčku pro dnešek offline void ulozitDoOffline() async {
void ulozitDnesekOffline() async {
var prefs = await SharedPreferences.getInstance(); var prefs = await SharedPreferences.getInstance();
if (prefs.getBool("offline") != null && prefs.getBool("offline")!) { if (prefs.getBool("offline") ?? false) {
// vyčistit offline
Directory appDocDir = await getApplicationDocumentsDirectory(); Directory appDocDir = await getApplicationDocumentsDirectory();
for (var f in appDocDir.listSync()) { for (var f in appDocDir.listSync()) {
// Vymažeme obsah
if (f.path.contains("jidelnicek")) { if (f.path.contains("jidelnicek")) {
f.deleteSync(); f.deleteSync();
} }
} }
// Uložíme nová data // uložit *pocet* jídelníčků pro offline použití
Jidelnicek j = Jidelnicek(DateTime.now(), []); var pocet = prefs.getInt("offline_pocet") ?? 1;
try { if (pocet > 7) pocet = 7;
j = await widget.canteen.jidelnicekDen(); for (var i = 0; i < pocet; i++) {
} catch (e) { var d = den.add(Duration(days: i));
if (!widget.canteen.prihlasen) { Jidelnicek? j;
if (!mounted) return; // ! Přidat chybu, pokud není mounted try {
Navigator.pushReplacement( j = await widget.canteen.jidelnicekDen(den: d);
context, MaterialPageRoute(builder: (c) => const LoginPage())); } catch (e) {
if (!widget.canteen.prihlasen) {
if (!mounted) return; // ! Přidat chybu, pokud není mounted
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(Languages.of(context)!.errorSaving),
duration: const Duration(seconds: 5),
));
break;
}
} }
var soubor = File(
"${appDocDir.path}/jidelnicek_${d.year}-${d.month}-${d.day}.json");
soubor.createSync();
var jidla = [];
for (var jidlo in j!.jidla) {
jidla.add({
"nazev": jidlo.nazev,
"varianta": jidlo.varianta,
"objednano": jidlo.objednano,
"cena": jidlo.cena,
"naBurze": jidlo.naBurze,
"den": d.toString()
});
}
await soubor.writeAsString(json.encode(jidla));
} }
var soubor = File(
"${appDocDir.path}/jidelnicek_${den.year}-${den.month}-${den.day}.json");
soubor.createSync();
var jidla = [];
for (var jidlo in j.jidla) {
jidla.add({
"nazev": jidlo.nazev,
"varianta": jidlo.varianta,
"objednano": jidlo.objednano,
"cena": jidlo.cena,
"naBurze": jidlo.naBurze,
"den": den.toString()
});
}
await soubor.writeAsString(json.encode(jidla));
} }
} }
@ -427,7 +435,7 @@ class _JidelnicekPageState extends State<JidelnicekPage> {
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
nactiNastaveni(); nactiNastaveni();
ulozitDnesekOffline(); ulozitDoOffline();
nactiJidlo(); nactiJidlo();
} }

View file

@ -2,6 +2,7 @@ import 'dart:io';
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -28,21 +29,25 @@ class _NastaveniState extends State<Nastaveni> {
bool _oznameniObed = false; bool _oznameniObed = false;
bool _zapamatovany = false; bool _zapamatovany = false;
TimeOfDay _oznameniCas = TimeOfDay.now(); TimeOfDay _oznameniCas = TimeOfDay.now();
final TextEditingController _countController =
TextEditingController(text: "1");
SharedPreferences? preferences;
void najitNastaveni() async { void najitNastaveni() async {
var preferences = await SharedPreferences.getInstance(); preferences = await SharedPreferences.getInstance();
_zapamatovany = await LoginManager.zapamatovat(); _zapamatovany = await LoginManager.zapamatovat();
setState(() { setState(() {
_ukladatOffline = preferences.getBool("offline") ?? false; _ukladatOffline = preferences!.getBool("offline") ?? false;
_preskakovatVikend = preferences.getBool("skip") ?? false; _preskakovatVikend = preferences!.getBool("skip") ?? false;
_kontrolovatTyden = preferences.getBool("tyden") ?? false; _kontrolovatTyden = preferences!.getBool("tyden") ?? false;
_oznameniObed = preferences.getBool("oznamit") ?? false; _oznameniObed = preferences!.getBool("oznamit") ?? false;
var casStr = preferences.getString("oznameni_cas"); _countController.text =
(preferences!.getInt("offline_pocet") ?? 1).toString();
var casStr = preferences!.getString("oznameni_cas");
if (casStr == null) { if (casStr == null) {
var now = DateTime.now(); var now = DateTime.now();
_oznameniCas = TimeOfDay.fromDateTime( _oznameniCas = TimeOfDay.fromDateTime(
DateTime.now().add(const Duration(hours: 1))); DateTime.now().add(const Duration(hours: 1)));
preferences.setString("oznameni_cas", now.toString()); preferences!.setString("oznameni_cas", now.toString());
} else { } else {
_oznameniCas = TimeOfDay.fromDateTime(DateTime.parse(casStr)); _oznameniCas = TimeOfDay.fromDateTime(DateTime.parse(casStr));
} }
@ -50,8 +55,7 @@ class _NastaveniState extends State<Nastaveni> {
} }
void zmenitNastaveni(String key, bool value) async { void zmenitNastaveni(String key, bool value) async {
var preferences = await SharedPreferences.getInstance(); preferences!.setBool(key, value);
preferences.setBool(key, value);
} }
@override @override
@ -86,6 +90,27 @@ class _NastaveniState extends State<Nastaveni> {
}) })
], ],
), ),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(Languages.of(context)!.saveCount),
SizedBox(
width: 35,
child: TextField(
controller: _countController,
enabled: _ukladatOffline,
keyboardType: TextInputType.number,
inputFormatters: [FilteringTextInputFormatter.digitsOnly],
onChanged: (c) {
var cislo = int.tryParse(c);
if (cislo != null) {
preferences!.setInt("offline_pocet", cislo);
}
},
),
)
],
),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [

View file

@ -1,27 +1,57 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:package_info_plus/package_info_plus.dart'; import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import '../lang/lang.dart'; import '../lang/lang.dart';
import '../main.dart'; import '../main.dart';
class OfflineJidelnicek extends StatefulWidget { class OfflineJidelnicek extends StatefulWidget {
const OfflineJidelnicek({Key? key, required this.jidla}) : super(key: key); const OfflineJidelnicek({Key? key}) : super(key: key);
final List<OfflineJidlo> jidla;
@override @override
State<OfflineJidelnicek> createState() => _OfflineJidelnicekState(); State<OfflineJidelnicek> createState() => _OfflineJidelnicekState();
} }
class _OfflineJidelnicekState extends State<OfflineJidelnicek> { class _OfflineJidelnicekState extends State<OfflineJidelnicek> {
List<Widget> obsah = [const CircularProgressIndicator()]; List<Widget> obsah = [const CircularProgressIndicator()];
var _skipWeekend = false;
DateTime den = DateTime.now(); DateTime den = DateTime.now();
String denTydne = ""; String denTydne = "";
List<List<OfflineJidlo>> data = [];
var jidloIndex = 0;
void nactiZeSouboru() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
for (var f in appDocDir.listSync()) {
if (f.path.contains("jidelnicek")) {
var soubor = File(f.path);
var input = await soubor.readAsString();
var r = jsonDecode(input);
List<OfflineJidlo> jidla = [];
for (var j in r) {
jidla.add(OfflineJidlo(
nazev: j["nazev"],
varianta: j["varianta"],
objednano: j["objednano"],
cena: j["cena"],
naBurze: j["naBurze"],
den: DateTime.parse(j["den"])));
}
data.add(jidla);
}
}
nactiJidlo();
}
Future<void> nactiJidlo() async { Future<void> nactiJidlo() async {
den = widget.jidla[0].den; var jidelnicek = data[jidloIndex];
den = jidelnicek[0].den;
switch (den.weekday) { switch (den.weekday) {
case 2: case 2:
denTydne = Languages.of(context)!.tuesday; denTydne = Languages.of(context)!.tuesday;
@ -45,32 +75,33 @@ class _OfflineJidelnicekState extends State<OfflineJidelnicek> {
denTydne = Languages.of(context)!.monday; denTydne = Languages.of(context)!.monday;
} }
obsah = []; obsah = [];
for (OfflineJidlo j in widget.jidla) { for (OfflineJidlo j in jidelnicek) {
obsah.add( obsah.add(
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: InkWell( child: InkWell(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(j.varianta), Text(j.varianta),
const SizedBox(width: 10), const SizedBox(width: 10),
Flexible( Flexible(
child: Text( child: Text(
j.nazev, j.nazev,
),
), ),
), Text((j.naBurze)
Text((j.naBurze) ? Languages.of(context)!.inExchange
? Languages.of(context)!.inExchange : "${j.cena}"),
: "${j.cena}"), Checkbox(
Checkbox( value: j.objednano,
value: j.objednano, fillColor: MaterialStateProperty.all(Colors.grey),
fillColor: MaterialStateProperty.all(Colors.grey), onChanged: (v) async {
onChanged: (v) async { return;
return; })
}) ],
], ),
)), ),
), ),
); );
} }
@ -116,7 +147,14 @@ class _OfflineJidelnicekState extends State<OfflineJidelnicek> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
nactiJidlo(); nactiNastaveni();
}
void nactiNastaveni() async {
var prefs = await SharedPreferences.getInstance();
_skipWeekend = prefs.getBool("skip") ?? false;
if (!mounted) return;
nactiZeSouboru();
} }
@override @override
@ -157,11 +195,61 @@ class _OfflineJidelnicekState extends State<OfflineJidelnicek> {
), ),
Text(Languages.of(context)!.mustLogout), Text(Languages.of(context)!.mustLogout),
const SizedBox(height: 10), const SizedBox(height: 10),
TextButton( Row(mainAxisAlignment: MainAxisAlignment.center, children: [
child: IconButton(
Text("${den.day}. ${den.month}. ${den.year} - $denTydne"), onPressed: () {
onPressed: () => {}, if (data.length <= 1) return;
), obsah = [const CircularProgressIndicator()];
setState(() {
if (den.weekday == 1 && _skipWeekend) {
// pokud je pondělí a chceme přeskočit víkend
if (jidloIndex - 2 >= 0) {
jidloIndex -= data.length - 3;
} else {
jidloIndex = data.length - 1;
}
} else if (jidloIndex == 0) {
jidloIndex = data.length - 1;
} else {
jidloIndex -= 1;
}
nactiJidlo();
});
},
icon: const Icon(Icons.arrow_left)),
TextButton(
onPressed: () async {},
child: Text(
"${den.day}. ${den.month}. ${den.year} - $denTydne")),
IconButton(
onPressed: () {
if (data.length <= 1) return;
obsah = [const CircularProgressIndicator()];
setState(() {
if (den.weekday == 5 && _skipWeekend) {
// pokud je pondělí a chceme přeskočit víkend
if (jidloIndex + 2 <= data.length - 1) {
jidloIndex += 2;
} else {
jidloIndex = 0;
}
} else if (jidloIndex == data.length) {
jidloIndex = 0;
} else {
jidloIndex += 1;
}
nactiJidlo();
});
},
icon: const Icon(Icons.arrow_right),
),
IconButton(
onPressed: () {
jidloIndex = 0;
},
icon: const Icon(Icons.today))
]),
SingleChildScrollView( SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Column( child: Column(

View file

@ -0,0 +1,3 @@
- Umožnit ukládat více dnů offline
- Pop-up "Přihlašování" nyní zmizí v případě neúspěšného přihlášení
- chyba při ukládání offline vás nyní již nevyhodí ale zobrazí pouze zprávu

View file

@ -0,0 +1,3 @@
- Support downloading multiple days of meal lists offline
- "Signing in" pop-up disappears when not signed in succesfully
- Error while saving menu offline no longer throws you to the log-in screen, but shows just an error message

View file

@ -6,7 +6,7 @@ publish_to: 'none'
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43 # A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +. # followed by an optional build number separated by a +.
version: 1.4.2+22 version: 1.5.0+23
environment: environment:
sdk: ">=2.16.1 <3.0.0" sdk: ">=2.16.1 <3.0.0"