From 64b7f261a029154dff9342b6ffec12cdda52fccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Sat, 28 Jan 2023 14:30:54 +0100 Subject: [PATCH] =?UTF-8?q?fix:=20p=C5=99epsat=20Platform=20UI=20a=20zmeni?= =?UTF-8?q?t=20RefreshIndicator?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/lang/lang.dart | 2 + lib/lang/lang_cz.dart | 3 + lib/lang/lang_en.dart | 3 + lib/loginmanager.dart | 4 +- lib/main.dart | 78 ++-- lib/okna/burza.dart | 179 +++++---- lib/okna/jidelnicek.dart | 661 ++++++++++++++++--------------- lib/okna/login.dart | 312 +++++++-------- lib/okna/nastaveni.dart | 356 +++++++++-------- lib/okna/offline_jidelnicek.dart | 172 ++++---- lib/okna/welcome.dart | 4 +- lib/pw/platformbutton.dart | 18 + lib/pw/platformdialog.dart | 30 ++ lib/pw/platformfield.dart | 54 +++ lib/pw/platformswitch.dart | 28 ++ lib/pw/platformwidget.dart | 22 + lib/util.dart | 69 +++- pubspec.lock | 8 + pubspec.yaml | 5 +- 19 files changed, 1115 insertions(+), 893 deletions(-) create mode 100644 lib/pw/platformbutton.dart create mode 100644 lib/pw/platformdialog.dart create mode 100644 lib/pw/platformfield.dart create mode 100644 lib/pw/platformswitch.dart create mode 100644 lib/pw/platformwidget.dart diff --git a/lib/lang/lang.dart b/lib/lang/lang.dart index 97dbed5..64ae7f8 100644 --- a/lib/lang/lang.dart +++ b/lib/lang/lang.dart @@ -45,6 +45,8 @@ abstract class Languages { // Jídelníček + String get todayTooltip; + String get loading; String get monday; diff --git a/lib/lang/lang_cz.dart b/lib/lang/lang_cz.dart index 1e2397b..3eb6b15 100644 --- a/lib/lang/lang_cz.dart +++ b/lib/lang/lang_cz.dart @@ -244,4 +244,7 @@ class LanguageCz extends Languages { @override String get errorSaving => "Při ukládání offline nastala chyba, zkuste to znovu později."; + + @override + String get todayTooltip => "Přejít na dnešní jídelníček"; } diff --git a/lib/lang/lang_en.dart b/lib/lang/lang_en.dart index 5163b2f..b3e1eae 100644 --- a/lib/lang/lang_en.dart +++ b/lib/lang/lang_en.dart @@ -242,4 +242,7 @@ class LanguageEn extends Languages { @override String get errorSaving => "An error occured while trying to save menu offline, try again later."; + + @override + String get todayTooltip => "Go to today's meal"; } diff --git a/lib/loginmanager.dart b/lib/loginmanager.dart index 0e00e60..51f7f70 100644 --- a/lib/loginmanager.dart +++ b/lib/loginmanager.dart @@ -2,7 +2,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; class LoginManager { static Future?> getDetails() async { - // zkontrolovat secure storage pokud je něco uložené + // check secure storage for details const storage = FlutterSecureStorage(); var user = await storage.read(key: "oc_user"); var pass = await storage.read(key: "oc_pass"); @@ -18,7 +18,7 @@ class LoginManager { await storage.write(key: "oc_url", value: url); } - static Future zapamatovat() async { + static Future rememberme() async { const storage = FlutterSecureStorage(); return await storage.containsKey(key: "oc_pass"); } diff --git a/lib/main.dart b/lib/main.dart index 66ae65a..4096c23 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:io'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; @@ -37,7 +38,8 @@ Copyright (C) 2022 Matyáš Caras a přispěvatelé final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); -void oznamitPredem(SharedPreferences prefs, tz.Location l) async { +/// Used to setup notifications about ordered food +void setupNotification(SharedPreferences prefs, tz.Location l) async { String title; String locale = Intl.getCurrentLocale(); @@ -50,24 +52,24 @@ void oznamitPredem(SharedPreferences prefs, tz.Location l) async { } /*if (prefs.getBool("offline") ?? false) { - // TODO možnost brát z offline dat + // TODO grab data from offline storage } else {*/ - // bere online - var d = await LoginManager.getDetails(); // získat údaje + // data from the web + var d = await LoginManager.getDetails(); // grab login if (d != null) { var c = Canteen(d["url"]!); if (await c.login(d["user"]!, d["pass"]!)) { var jidla = await c.jidelnicekDen(); try { - var jidlo = jidla.jidla.singleWhere( - (element) => element.objednano); // získá objednané jídlo + var jidlo = jidla.jidla + .singleWhere((element) => element.objednano); // grab ordered meal var kdy = DateTime.parse(prefs.getString( - "oznameni_cas")!); // uložíme čas, kdy se má odeslat oznámení - var cas = casNaDate( + "oznameni_cas")!); // save the time the notif should be sent + var cas = timeToDate( TimeOfDay(hour: kdy.hour, minute: kdy.minute), ); if (cas.isBefore(DateTime.now())) return; - // data o oznámení + // notif data const AndroidNotificationDetails androidSpec = AndroidNotificationDetails('predobedem', 'Oznámení před obědem', channelDescription: 'Oznámení o dnešním jídle', @@ -76,7 +78,7 @@ void oznamitPredem(SharedPreferences prefs, tz.Location l) async { styleInformation: BigTextStyleInformation(''), ticker: 'today meal'); - // naplánovat + // plan through lib await flutterLocalNotificationsPlugin.zonedSchedule( 0, title, @@ -87,7 +89,7 @@ void oznamitPredem(SharedPreferences prefs, tz.Location l) async { uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); } on StateError catch (_) { - // nenalezeno + // no ordered meal found } } // } @@ -102,10 +104,10 @@ void main() async { var prefs = await SharedPreferences.getInstance(); if (prefs.getBool("oznamit") ?? false) { - oznamitPredem(prefs, l); + setupNotification(prefs, l); } - // nastavit oznámení + // notif library setup const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('notif_icon'); @@ -115,7 +117,6 @@ void main() async { InitializationSettings(android: initializationSettingsAndroid, iOS: ios); await flutterLocalNotificationsPlugin.initialize(initializationSettings); - // spustit aplikaci runApp(const MyApp()); } @@ -124,23 +125,38 @@ class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - localizationsDelegates: const [ - AppLocalizationsDelegate(), - ...GlobalMaterialLocalizations.delegates - ], - supportedLocales: const [Locale("cs", ""), Locale("en", "")], - title: "OpenCanteen", - theme: ThemeData( - primarySwatch: Colors.purple, - ), - darkTheme: ThemeData( - brightness: Brightness.dark, - primarySwatch: Colors.purple, - ), - home: const LoginPage(), - ); + return (Platform + .isAndroid) // run app based on current platform to make use of the platform's respective UI lib + ? MaterialApp( + debugShowCheckedModeBanner: false, + localizationsDelegates: const [ + AppLocalizationsDelegate(), + ...GlobalMaterialLocalizations.delegates + ], + supportedLocales: const [Locale("cs", ""), Locale("en", "")], + title: "OpenCanteen", + theme: ThemeData( + primarySwatch: Colors.purple, + ), + darkTheme: ThemeData( + brightness: Brightness.dark, + primarySwatch: Colors.purple, + ), + home: const LoginPage(), + ) + : const CupertinoApp( + debugShowCheckedModeBanner: false, + localizationsDelegates: [ + AppLocalizationsDelegate(), + ...GlobalMaterialLocalizations.delegates + ], + supportedLocales: [Locale("cs", ""), Locale("en", "")], + title: "OpenCanteen", + theme: CupertinoThemeData( + primaryColor: Colors.purple, + ), + home: LoginPage(), + ); } } diff --git a/lib/okna/burza.dart b/lib/okna/burza.dart index db3084f..02c435b 100644 --- a/lib/okna/burza.dart +++ b/lib/okna/burza.dart @@ -1,6 +1,8 @@ import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; import 'package:opencanteen/okna/login.dart'; +import 'package:opencanteen/pw/platformbutton.dart'; +import 'package:opencanteen/pw/platformdialog.dart'; import 'package:opencanteen/util.dart'; import '../../lang/lang.dart'; @@ -13,100 +15,101 @@ class BurzaView extends StatefulWidget { } class _BurzaViewState extends State { - List obsah = []; - double kredit = 0.0; - Future nactiBurzu(BuildContext context) async { - obsah = [const CircularProgressIndicator()]; - widget.canteen.ziskejUzivatele().then((kr) { - kredit = kr.kredit; - widget.canteen.ziskatBurzu().then((burza) { - setState(() { - obsah = []; - if (burza.isEmpty) { - obsah = [ - Text( - Languages.of(context)!.noExchange, - style: const TextStyle(fontSize: 20), - ), - Text(Languages.of(context)!.pullToReload) - ]; - } else { - for (var b in burza) { - obsah.add( - Padding( - padding: const EdgeInsets.only(top: 15), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text("${b.den.day}. ${b.den.month}."), - const SizedBox(width: 10), - Flexible( - child: Text( - b.nazev, - ), - ), - Text("${b.pocet}x"), - TextButton( - onPressed: () { - widget.canteen.objednatZBurzy(b).then((a) { - if (a) { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text(Languages.of(context)!.ordered), - content: Text( - Languages.of(context)!.orderSuccess), - actions: [ - TextButton( - child: Text(Languages.of(context)!.ok), - onPressed: () => - Navigator.of(context).pop(), - ) - ], - ), - ); - } else { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: Text( - Languages.of(context)!.cannotOrder), - content: Text( - Languages.of(context)!.errorOrdering), - actions: [ - TextButton( - child: Text(Languages.of(context)!.ok), - onPressed: () => - Navigator.of(context).pop(), - ) - ], - ), - ); - } - nactiBurzu(context); - }); - }, - child: Text(Languages.of(context)!.order)), - ], - ), - ), - ); - } - } - }); - }); - }).catchError((o) { + List content = []; + double balance = 0.0; + + Future loadExchange(BuildContext context) async { + content = [const CircularProgressIndicator()]; + var uzivatel = await widget.canteen.ziskejUzivatele().catchError((o) { if (!widget.canteen.prihlasen) { Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); + context, platformRouter((c) => const LoginPage())); + } + return Uzivatel(kredit: 0); + }); + balance = uzivatel.kredit; + var burza = await widget.canteen.ziskatBurzu(); + setState(() { + content = []; + if (burza.isEmpty) { + content = [ + Text( + Languages.of(context)!.noExchange, + style: const TextStyle(fontSize: 20), + ), + Text(Languages.of(context)!.pullToReload) + ]; + } else { + for (var b in burza) { + content.add( + Padding( + padding: const EdgeInsets.only(top: 15), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("${b.den.day}. ${b.den.month}."), + const SizedBox(width: 10), + Flexible( + child: Text( + b.nazev, + ), + ), + Text("${b.pocet}x"), + PlatformButton( + onPressed: () { + widget.canteen.objednatZBurzy(b).then( + (a) { + if (a) { + showDialog( + context: context, + builder: (context) => PlatformDialog( + title: Languages.of(context)!.ordered, + content: Languages.of(context)!.orderSuccess, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () => + Navigator.of(context).pop(), + ) + ], + ), + ); + } else { + showDialog( + context: context, + builder: (context) => PlatformDialog( + title: Languages.of(context)!.cannotOrder, + content: Languages.of(context)!.errorOrdering, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () => + Navigator.of(context).pop(), + ) + ], + ), + ); + } + loadExchange(context); + }, + ); + }, + text: Languages.of(context)!.order, + ), + ], + ), + ), + ); + } } }); + return; } @override void initState() { super.initState(); - nactiBurzu(context); + loadExchange(context); } @override @@ -123,20 +126,20 @@ class _BurzaViewState extends State { child: Column( children: [ const SizedBox(height: 10), - Text("${Languages.of(context)!.balance}$kredit Kč"), + Text("${Languages.of(context)!.balance}$balance Kč"), const SizedBox(height: 10), SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: SizedBox( height: MediaQuery.of(context).size.height / 1.3, - child: Column(children: obsah), + child: Column(children: content), ), ) ], ), ), ), - onRefresh: () => nactiBurzu(context)), + onRefresh: () => loadExchange(context)), ); } } diff --git a/lib/okna/jidelnicek.dart b/lib/okna/jidelnicek.dart index c362662..3a70add 100644 --- a/lib/okna/jidelnicek.dart +++ b/lib/okna/jidelnicek.dart @@ -6,6 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:opencanteen/okna/login.dart'; import 'package:opencanteen/okna/nastaveni.dart'; +import 'package:opencanteen/pw/platformbutton.dart'; +import 'package:opencanteen/pw/platformdialog.dart'; import 'package:opencanteen/util.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; @@ -14,25 +16,25 @@ import 'package:url_launcher/url_launcher.dart'; import '../../lang/lang.dart'; -class JidelnicekView extends StatefulWidget { - const JidelnicekView({Key? key, required this.canteen}) : super(key: key); +class MealView extends StatefulWidget { + const MealView({Key? key, required this.canteen}) : super(key: key); final Canteen canteen; @override - State createState() => _JidelnicekViewState(); + State createState() => _MealViewState(); } -class _JidelnicekViewState extends State { - List obsah = [const CircularProgressIndicator()]; - DateTime den = DateTime.now(); - String denTydne = ""; - double kredit = 0.0; +class _MealViewState extends State { + List content = [const CircularProgressIndicator()]; + DateTime day = DateTime.now(); + String dayOWeek = ""; + double balance = 0.0; bool _skipWeekend = false; - void kontrolaTyden(BuildContext context) async { + void checkWeek(BuildContext context) async { var prefs = await SharedPreferences.getInstance(); if (prefs.getBool("tyden") ?? false) { - // Zjistit jestli je objednáno na přístí týden - var pristi = den.add(const Duration(days: 6)); + // Check if user has ordered a meal in the next week + var pristi = day.add(const Duration(days: 6)); for (var i = 0; i < 5; i++) { var jidelnicek = await widget.canteen .jidelnicekDen(den: pristi.add(Duration(days: i + 1))); @@ -47,8 +49,8 @@ class _JidelnicekViewState extends State { action: SnackBarAction( onPressed: () => setState( () { - den = pristi.add(Duration(days: i + 1)); - nactiJidlo(); + day = pristi.add(Duration(days: i + 1)); + loadMeals(); }, ), label: Languages.of(context)!.jump, @@ -61,288 +63,295 @@ class _JidelnicekViewState extends State { } } - Future nactiJidlo() async { - obsah = [const CircularProgressIndicator()]; - switch (den.weekday) { + Future loadMeals() async { + content = [const CircularProgressIndicator()]; + switch (day.weekday) { case 2: - denTydne = Languages.of(context)!.tuesday; + dayOWeek = Languages.of(context)!.tuesday; break; case 3: - denTydne = Languages.of(context)!.wednesday; + dayOWeek = Languages.of(context)!.wednesday; break; case 4: - denTydne = Languages.of(context)!.thursday; + dayOWeek = Languages.of(context)!.thursday; break; case 5: - denTydne = Languages.of(context)!.friday; + dayOWeek = Languages.of(context)!.friday; break; case 6: - denTydne = Languages.of(context)!.saturday; + dayOWeek = Languages.of(context)!.saturday; break; case 7: - denTydne = Languages.of(context)!.sunday; + dayOWeek = Languages.of(context)!.sunday; break; default: - denTydne = Languages.of(context)!.monday; + dayOWeek = Languages.of(context)!.monday; } - widget.canteen.ziskejUzivatele().then((kr) { - kredit = kr.kredit; - widget.canteen.jidelnicekDen(den: den).then((jd) async { - setState(() { - obsah = []; - if (jd.jidla.isEmpty) { - obsah.add(Text( - Languages.of(context)!.noFood, - style: const TextStyle(fontSize: 15), - )); - } else { - for (var j in jd.jidla) { - obsah.add( - Padding( - padding: const EdgeInsets.only(top: 15), - child: InkWell( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(j.varianta), - const SizedBox(width: 10), - Flexible( - child: Text( - j.nazev, - ), + var uzivatel = await widget.canteen.ziskejUzivatele().catchError( + (o) { + if (!widget.canteen.prihlasen) { + Navigator.pushReplacement( + context, platformRouter((c) => const LoginPage())); + } + return Uzivatel(kredit: 0); + }, + ); + balance = uzivatel.kredit; + var jd = await widget.canteen.jidelnicekDen(den: day).catchError((_) { + showInfo(context, Languages.of(context)!.errorContacting); + return Jidelnicek(DateTime.now(), []); + }); + setState( + () { + content = []; + if (jd.jidla.isEmpty) { + content.add(Text( + Languages.of(context)!.noFood, + style: const TextStyle(fontSize: 15), + )); + } else { + for (var j in jd.jidla) { + content.add( + Padding( + padding: const EdgeInsets.only(top: 15), + child: InkWell( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(j.varianta), + const SizedBox(width: 10), + Flexible( + child: Text( + j.nazev, ), - Text((j.naBurze) - ? Languages.of(context)!.inExchange - : "${j.cena} Kč"), - Checkbox( - value: j.objednano, - fillColor: (j.lzeObjednat) - ? MaterialStateProperty.all(Colors.purple) - : MaterialStateProperty.all(Colors.grey), - onChanged: (v) async { - if (!j.lzeObjednat) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: Text(Languages.of(context)! - .errorOrdering), - content: Text( - Languages.of(context)!.cannotOrder), - actions: [ - TextButton( - child: - Text(Languages.of(context)!.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - ); - }); - } else { - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Dialog( - child: SizedBox( - height: 100, - child: Row(children: [ - const Padding( - padding: EdgeInsets.all(10), - child: - CircularProgressIndicator(), - ), - Text(Languages.of(context)! - .ordering) - ]), - ), - )); - widget.canteen.objednat(j).then((_) { - Navigator.of(context, rootNavigator: true) - .pop(); - nactiJidlo(); - }).catchError((o) { - Navigator.of(context, rootNavigator: true) - .pop(); - showDialog( - context: context, - builder: (bc) => AlertDialog( - title: Text(Languages.of(context)! - .errorOrdering), - content: Text(o.toString()), - actions: [ - TextButton( - child: Text( - Languages.of(context)! - .close), - onPressed: () { - Navigator.pop(bc); - }, - ) - ], - )); - }); - } - }) - ], - ), - onTap: () async { - if (!j.lzeObjednat) { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - title: - Text(Languages.of(context)!.errorOrdering), - content: - Text(Languages.of(context)!.cannotOrder), - actions: [ - TextButton( - child: Text(Languages.of(context)!.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - ); - }); - } else { - showDialog( - context: context, - barrierDismissible: false, - builder: (_) => Dialog( - child: SizedBox( - height: 100, - child: Row(children: [ + ), + Text((j.naBurze) + ? Languages.of(context)!.inExchange + : "${j.cena} Kč"), + Checkbox( + value: j.objednano, + fillColor: (j.lzeObjednat) + ? MaterialStateProperty.all(Colors.purple) + : MaterialStateProperty.all(Colors.grey), + onChanged: (v) async { + if (!j.lzeObjednat) { + showDialog( + context: context, + builder: (context) { + return PlatformDialog( + title: Languages.of(context)!.errorOrdering, + content: Languages.of(context)!.cannotOrder, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ); + }, + ); + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Dialog( + child: SizedBox( + height: 100, + child: Row( + children: [ const Padding( padding: EdgeInsets.all(10), child: CircularProgressIndicator(), ), Text(Languages.of(context)!.ordering) - ]), + ], ), - )); - widget.canteen.objednat(j).then((_) { - Navigator.of(context, rootNavigator: true).pop(); - nactiJidlo(); - }).catchError( - (o) { - Navigator.of(context, rootNavigator: true).pop(); - showDialog( - context: context, - builder: (bc) => AlertDialog( - title: - Text(Languages.of(context)!.errorOrdering), - content: Text(o.toString()), - actions: [ - TextButton( - child: Text(Languages.of(context)!.close), - onPressed: () { - Navigator.pop(bc); - }, - ) - ], + ), ), ); - }, - ); - } - }, - onLongPress: () async { - if (!j.objednano || j.burzaUrl == null) return; - if (!j.naBurze) { - // pokud není na burze, radši se zeptáme - var d = await showDialog( - context: context, - builder: (bc) => SimpleDialog( - title: Text( - Languages.of(context)!.verifyExchange), - children: [ - SimpleDialogOption( - onPressed: () { - Navigator.pop(bc, true); - }, - child: Text(Languages.of(context)!.yes), - ), - SimpleDialogOption( - onPressed: () { - Navigator.pop(bc, false); - }, - child: Text(Languages.of(context)!.no), - ), - ], - )); - if (d) { - widget.canteen - .doBurzy(j) - .then((_) => nactiJidlo()) - .catchError((o) { - showDialog( - context: context, - builder: (bc) => AlertDialog( - title: - Text(Languages.of(context)!.exchangeError), - content: SingleChildScrollView( - child: Text(o.toString())), - actions: [ - TextButton( - child: Text(Languages.of(context)!.close), - onPressed: () { - Navigator.pop(bc); - }, - ) - ], - ), + widget.canteen.objednat(j).then((_) { + Navigator.of(context, rootNavigator: true).pop(); + loadMeals(); + }).catchError( + (o) { + Navigator.of(context, rootNavigator: true) + .pop(); + showDialog( + context: context, + builder: (bc) => PlatformDialog( + title: Languages.of(context)!.errorOrdering, + content: o.toString(), + actions: [ + PlatformButton( + text: Languages.of(context)!.close, + onPressed: () { + Navigator.pop(bc); + }, + ) + ], + ), + ); + }, ); - }); - } - } else { - // jinak ne - widget.canteen.doBurzy(j).then((_) => nactiJidlo()); - } - }, + } + }, + ) + ], ), + onTap: () async { + if (!j.lzeObjednat) { + showDialog( + context: context, + builder: (context) { + return PlatformDialog( + title: Languages.of(context)!.errorOrdering, + content: Languages.of(context)!.cannotOrder, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ); + }, + ); + } else { + showDialog( + context: context, + barrierDismissible: false, + builder: (_) => Dialog( + child: SizedBox( + height: 100, + child: Row(children: [ + const Padding( + padding: EdgeInsets.all(10), + child: CircularProgressIndicator(), + ), + Text(Languages.of(context)!.ordering) + ]), + ), + ), + ); + widget.canteen.objednat(j).then((_) { + Navigator.of(context, rootNavigator: true).pop(); + loadMeals(); + }).catchError( + (o) { + Navigator.of(context, rootNavigator: true).pop(); + showDialog( + context: context, + builder: (bc) => PlatformDialog( + title: Languages.of(context)!.errorOrdering, + content: o.toString(), + actions: [ + PlatformButton( + text: Languages.of(context)!.close, + onPressed: () { + Navigator.pop(bc); + }, + ) + ], + ), + ); + }, + ); + } + }, + onLongPress: () async { + if (!j.objednano || j.burzaUrl == null) return; + if (!j.naBurze) { + // if not in exchange, we ask + var d = await showDialog( + context: context, + builder: (bc) => PlatformDialog( + title: Languages.of(context)!.verifyExchange, + actions: [ + PlatformButton( + onPressed: () { + Navigator.pop(bc, true); + }, + text: Languages.of(context)!.yes, + ), + PlatformButton( + onPressed: () { + Navigator.pop(bc, false); + }, + text: Languages.of(context)!.no, + ), + ], + ), + ); + if (d) { + widget.canteen + .doBurzy(j) + .then((_) => loadMeals()) + .catchError((o) { + showDialog( + context: context, + builder: (bc) => PlatformDialog( + title: Languages.of(context)!.exchangeError, + content: o.toString(), + actions: [ + PlatformButton( + text: Languages.of(context)!.close, + onPressed: () { + Navigator.pop(bc); + }, + ) + ], + ), + ); + }); + } + } else { + // else no + widget.canteen.doBurzy(j).then((_) => loadMeals()); + } + }, ), - ); - } + ), + ); } - }); - }); - }).catchError((o) { - if (!widget.canteen.prihlasen) { - Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); - } - }); + } + }, + ); + return; } - Future kliknuti(String value, BuildContext context) async { + Future click(String value, BuildContext context) async { if (value == Languages.of(context)!.signOut) { await showDialog( context: context, - builder: (c) => AlertDialog( - title: Text(Languages.of(context)!.warning), - content: Text(Languages.of(context)!.signOutWarn), + builder: (c) => PlatformDialog( + title: Languages.of(context)!.warning, + content: Languages.of(context)!.signOutWarn, actions: [ - TextButton( + PlatformButton( onPressed: () { const storage = FlutterSecureStorage(); storage.deleteAll(); Navigator.pushAndRemoveUntil( context, - MaterialPageRoute(builder: (c) => const LoginPage()), + platformRouter((c) => const LoginPage()), (route) => false); }, - child: Text(Languages.of(context)!.yes)), - TextButton( - onPressed: () => Navigator.of(context).pop(), - child: Text(Languages.of(context)!.no)) + text: Languages.of(context)!.yes), + PlatformButton( + onPressed: () => Navigator.of(context).pop(), + text: Languages.of(context)!.no, + ) ], ), ); } else if (value == Languages.of(context)!.review) { - launchUrl(Uri.parse("market://details?id=cz.hernikplays.opencanteen"), + launchUrl( + Uri.parse((Platform.isAndroid) + ? "market://details?id=cz.hernikplays.opencanteen" + : "https://apps.apple.com/cz/app/opencanteen/id1621124445"), mode: LaunchMode.externalApplication); } else if (value == Languages.of(context)!.reportBugs) { launchUrl(Uri.parse("https://forms.gle/jKN7QeFJwpaApSbC8"), @@ -357,28 +366,29 @@ class _JidelnicekViewState extends State { "${Languages.of(context)!.copyright}\n${Languages.of(context)!.license}", applicationVersion: packageInfo.version, children: [ - TextButton( - onPressed: (() => launchUrl( - Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), - child: Text(Languages.of(context)!.source)) + PlatformButton( + onPressed: (() => launchUrl( + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"), + mode: LaunchMode.externalApplication)), + text: Languages.of(context)!.source, + ) ]); } else if (value == Languages.of(context)!.settings) { - Navigator.push( - context, MaterialPageRoute(builder: (c) => const AndroidNastaveni())); + Navigator.push(context, platformRouter((c) => const AndroidNastaveni())); } } - void nactiNastaveni() async { + void loadSettings() async { var prefs = await SharedPreferences.getInstance(); _skipWeekend = prefs.getBool("skip") ?? false; if (!mounted) return; - kontrolaTyden(context); + checkWeek(context); } - void ulozitDoOffline() async { + void saveOffline() async { var prefs = await SharedPreferences.getInstance(); if (prefs.getBool("offline") ?? false) { - // vyčistit offline + // clear offline storage Directory appDocDir = await getApplicationDocumentsDirectory(); for (var f in appDocDir.listSync()) { if (f.path.contains("jidelnicek")) { @@ -386,11 +396,11 @@ class _JidelnicekViewState extends State { } } - // uložit *pocet* jídelníčků pro offline použití + // save X meal lists var pocet = prefs.getInt("offline_pocet") ?? 1; if (pocet > 7) pocet = 7; for (var i = 0; i < pocet; i++) { - var d = den.add(Duration(days: i)); + var d = day.add(Duration(days: i)); Jidelnicek? j; try { j = await widget.canteen.jidelnicekDen(den: d); @@ -427,9 +437,9 @@ class _JidelnicekViewState extends State { @override void didChangeDependencies() { super.didChangeDependencies(); - nactiNastaveni(); - ulozitDoOffline(); - nactiJidlo(); + loadSettings(); + saveOffline(); + loadMeals(); } @override @@ -440,7 +450,7 @@ class _JidelnicekViewState extends State { title: Text(Languages.of(context)!.menu), actions: [ PopupMenuButton( - onSelected: ((String value) => kliknuti(value, context)), + onSelected: ((String value) => click(value, context)), itemBuilder: (BuildContext context) { return { Languages.of(context)!.reportBugs, @@ -459,62 +469,71 @@ class _JidelnicekViewState extends State { ], ), body: RefreshIndicator( - onRefresh: nactiJidlo, + onRefresh: loadMeals, child: Center( child: SizedBox( width: MediaQuery.of(context).size.width - 50, child: Column( children: [ const SizedBox(height: 10), - Text("${Languages.of(context)!.balance}$kredit Kč"), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - IconButton( - onPressed: () { - setState(() { - den = den.subtract(const Duration(days: 1)); - if (den.weekday == 7 && _skipWeekend) { - den = den.subtract(const Duration(days: 2)); - } - nactiJidlo(); - }); - }, - icon: const Icon(Icons.arrow_left)), - TextButton( + Text("${Languages.of(context)!.balance}$balance Kč"), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + IconButton( + onPressed: () { + setState(() { + day = day.subtract(const Duration(days: 1)); + if (day.weekday == 7 && _skipWeekend) { + day = day.subtract(const Duration(days: 2)); + } + loadMeals(); + }); + }, + icon: const Icon(Icons.arrow_left)), + PlatformButton( onPressed: () async { var datePicked = await showDatePicker( context: context, - initialDate: den, - currentDate: den, + initialDate: day, + currentDate: day, firstDate: DateTime(2019, 1, 1), - lastDate: DateTime(den.year + 1, 12, 31), + lastDate: DateTime(day.year + 1, 12, 31), locale: Localizations.localeOf(context)); if (datePicked == null) return; setState(() { - den = datePicked; - nactiJidlo(); + day = datePicked; + loadMeals(); }); }, - child: Text( - "${den.day}. ${den.month}. ${den.year} - $denTydne")), - IconButton( - onPressed: () { - setState(() { - den = den.add(const Duration(days: 1)); - if (den.weekday == 6 && _skipWeekend) { - den = den.add(const Duration(days: 2)); - } - nactiJidlo(); - }); - }, - icon: const Icon(Icons.arrow_right), - ), - IconButton( - onPressed: () => setState(() { - den = DateTime.now(); - nactiJidlo(); - }), - icon: const Icon(Icons.today)) - ]), + text: "${day.day}. ${day.month}. ${day.year} - $dayOWeek", + ), + IconButton( + onPressed: () { + setState(() { + day = day.add(const Duration(days: 1)); + if (day.weekday == 6 && _skipWeekend) { + day = day.add(const Duration(days: 2)); + } + loadMeals(); + }); + }, + icon: const Icon(Icons.arrow_right), + ), + Tooltip( + message: Languages.of(context)!.todayTooltip, + child: IconButton( + onPressed: () => setState( + () { + day = DateTime.now(); + loadMeals(); + }, + ), + icon: const Icon(Icons.today), + ), + ) + ], + ), SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: GestureDetector( @@ -524,25 +543,27 @@ class _JidelnicekViewState extends State { .onPrimary .withOpacity(0), height: MediaQuery.of(context).size.height / 1.3, - child: Column(children: obsah), + child: Column(children: content), ), onHorizontalDragEnd: (details) { if (details.primaryVelocity?.compareTo(0) == -1) { setState(() { - den = den.add(const Duration(days: 1)); - if (den.weekday == 6 && _skipWeekend) { - den = den.add(const Duration(days: 2)); + day = day.add(const Duration(days: 1)); + if (day.weekday == 6 && _skipWeekend) { + day = day.add(const Duration(days: 2)); } - nactiJidlo(); + loadMeals(); }); } else { - setState(() { - den = den.subtract(const Duration(days: 1)); - if (den.weekday == 7 && _skipWeekend) { - den = den.subtract(const Duration(days: 2)); - } - nactiJidlo(); - }); + setState( + () { + day = day.subtract(const Duration(days: 1)); + if (day.weekday == 7 && _skipWeekend) { + day = day.subtract(const Duration(days: 2)); + } + loadMeals(); + }, + ); } }, ), diff --git a/lib/okna/login.dart b/lib/okna/login.dart index c7a1908..b08584a 100644 --- a/lib/okna/login.dart +++ b/lib/okna/login.dart @@ -1,16 +1,17 @@ -import 'dart:io'; - import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:opencanteen/okna/welcome.dart'; +import 'package:opencanteen/pw/platformbutton.dart'; +import 'package:opencanteen/pw/platformfield.dart'; import '../../lang/lang.dart'; import '../../loginmanager.dart'; import '../../main.dart'; import '../../util.dart'; +import '../pw/platformswitch.dart'; import 'jidelnicek.dart'; import 'offline_jidelnicek.dart'; @@ -32,14 +33,14 @@ class _LoginPageState extends State { void initState() { super.initState(); LoginManager.getDetails().then((r) async { - // žádat o oprávnění na android + // request android notification access flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.requestPermission(); if (r != null) { - // Automaticky přihlásit + // Autologin showDialog( context: context, barrierDismissible: false, @@ -74,36 +75,26 @@ class _LoginPageState extends State { if (odsouhlasil == null || odsouhlasil != "ano") { Navigator.pushAndRemoveUntil( context, - MaterialPageRoute( - builder: (c) => WelcomePage(canteen: canteen), + platformRouter( + (c) => WelcomePage(canteen: canteen), ), (route) => false); } else { Navigator.pushAndRemoveUntil( context, - MaterialPageRoute( - builder: (context) => JidelnicekView(canteen: canteen), + platformRouter( + (context) => MealView(canteen: canteen), ), (route) => false); } } on PlatformException { if (!mounted) return; Navigator.of(context).pop(); - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(Languages.of(context)!.corrupted), - ), - ); + showInfo(context, Languages.of(context)!.corrupted); } catch (_) { if (!mounted) return; Navigator.of(context).pop(); - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(Languages.of(context)!.errorContacting), - ), - ); + showInfo(context, Languages.of(context)!.errorContacting); goOffline(); } } @@ -113,169 +104,150 @@ class _LoginPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar( - title: Text(Languages.of(context)!.logIn), - automaticallyImplyLeading: false, - ), - body: Center( - child: SingleChildScrollView( - child: SizedBox( - width: MediaQuery.of(context).size.width - 50, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - Languages.of(context)!.appName, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, fontSize: 40), + appBar: AppBar( + title: Text(Languages.of(context)!.logIn), + automaticallyImplyLeading: false, + ), + body: Center( + child: SingleChildScrollView( + child: SizedBox( + width: MediaQuery.of(context).size.width - 50, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + Languages.of(context)!.appName, + textAlign: TextAlign.center, + style: const TextStyle( + fontWeight: FontWeight.bold, fontSize: 40), + ), + Text( + Languages.of(context)!.logIn, + textAlign: TextAlign.center, + ), + PlatformField( + controller: userControl, + autofillHints: const [AutofillHints.username], + labelText: Languages.of(context)!.username, + ), + PlatformField( + autofillHints: const [AutofillHints.password], + labelText: Languages.of(context)!.password, + controller: passControl, + obscureText: true, + ), + const SizedBox( + height: 10, + ), + DropdownButton( + isExpanded: true, + value: dropdownUrl, + items: instance.map>((e) { + return DropdownMenuItem( + value: e["url"], + child: Text(e["name"]!), + ); + }).toList(), + onChanged: (String? value) { + setState(() { + if (value == "") { + _showUrl = true; + } else { + _showUrl = false; + } + dropdownUrl = value!; + }); + }, + ), + AnimatedOpacity( + opacity: _showUrl ? 1.0 : 0.0, + duration: const Duration(milliseconds: 300), + child: PlatformField( + autofillHints: const [AutofillHints.url], + labelText: Languages.of(context)!.iCanteenUrl, + keyboardType: TextInputType.url, + controller: canteenControl, ), - Text( - Languages.of(context)!.logIn, - textAlign: TextAlign.center, - ), - TextField( - controller: userControl, - autofillHints: const [AutofillHints.username], - decoration: InputDecoration( - labelText: Languages.of(context)!.username), - ), - TextField( - autofillHints: const [AutofillHints.password], - decoration: InputDecoration( - labelText: Languages.of(context)!.password), - controller: passControl, - obscureText: true, - ), - const SizedBox( - height: 10, - ), - DropdownButton( - isExpanded: true, - value: dropdownUrl, - items: instance.map>((e) { - return DropdownMenuItem( - value: e["url"], - child: Text(e["name"]!), - ); - }).toList(), - onChanged: (String? value) { + ), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + PlatformSwitch( + value: rememberMe, + onChanged: (value) { setState(() { - if (value == "") { - _showUrl = true; - } else { - _showUrl = false; - } - dropdownUrl = value!; + rememberMe = value; }); }, ), - AnimatedOpacity( - opacity: _showUrl ? 1.0 : 0.0, - duration: const Duration(milliseconds: 300), - child: TextField( - autofillHints: const [AutofillHints.url], - decoration: InputDecoration( - labelText: Languages.of(context)!.iCanteenUrl), - keyboardType: TextInputType.url, - controller: canteenControl, - ), - ), - Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Switch( - value: rememberMe, - onChanged: (value) { - setState(() { - rememberMe = value; - }); - }), - Text(Languages.of(context)!.rememberMe) - ]), - TextButton( - onPressed: () async { - var canteenUrl = (dropdownUrl == "") - ? canteenControl.text - : dropdownUrl; - if (!canteenUrl.startsWith("https://") && - !canteenUrl.startsWith("http://")) { - canteenUrl = "https://$canteenUrl"; + Text(Languages.of(context)!.rememberMe) + ]), + PlatformButton( + onPressed: () async { + var canteenUrl = (dropdownUrl == "") + ? canteenControl.text + : dropdownUrl; + if (!canteenUrl.startsWith("https://") && + !canteenUrl.startsWith("http://")) { + canteenUrl = "https://$canteenUrl"; + } + var canteen = Canteen(canteenUrl); + try { + var l = await canteen.login( + userControl.text, passControl.text); + if (!l) { + if (!mounted) return; + showInfo(context, Languages.of(context)!.loginFailed); + return; } - var canteen = Canteen(canteenUrl); - try { - var l = await canteen.login( - userControl.text, passControl.text); - if (!l) { - if (!mounted) return; - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text(Languages.of(context)!.loginFailed), + if (rememberMe) { + LoginManager.setDetails( + userControl.text, passControl.text, canteenUrl); + } + // souhlas + const storage = FlutterSecureStorage(); + var odsouhlasil = await storage.read(key: "oc_souhlas"); + if (!mounted) return; + if (odsouhlasil == null || odsouhlasil != "ano") { + Navigator.pushAndRemoveUntil( + context, + platformRouter( + (context) => WelcomePage( + canteen: canteen, + ), ), - ); - return; - } - if (rememberMe) { - LoginManager.setDetails( - userControl.text, passControl.text, canteenUrl); - } - // souhlas - const storage = FlutterSecureStorage(); - var odsouhlasil = - await storage.read(key: "oc_souhlas"); - if (!mounted) return; - if (odsouhlasil == null || odsouhlasil != "ano") { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (c) => WelcomePage( - canteen: canteen, - )), - (route) => false); - } else { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (context) => JidelnicekView( - canteen: canteen, - )), - (route) => false); - } - } on PlatformException { - if (!mounted) return; - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(Languages.of(context)!.corrupted), - ), - ); - } on Exception catch (_) { - if (!mounted) return; - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: - Text(Languages.of(context)!.errorContacting), - ), - ); - goOffline(); + (route) => false); + } else { + Navigator.pushAndRemoveUntil( + context, + platformRouter( + (context) => MealView( + canteen: canteen, + ), + ), + (route) => false); } - }, - child: Text(Languages.of(context)!.logIn)), - ], - ), + } on PlatformException { + if (!mounted) return; + showInfo(context, Languages.of(context)!.corrupted); + } on Exception catch (_) { + if (!mounted) return; + showInfo( + context, Languages.of(context)!.errorContacting); + //goOffline(); + } + }, + text: Languages.of(context)!.logIn), + ], ), ), - )); + ), + ), + ); } - /// Získá offline soubor a zobrazí údaje + /// Switch to offline view void goOffline() async { if (!mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: ((context) => const AndroidOfflineJidelnicek())), - (route) => false); + Navigator.pushAndRemoveUntil(context, + platformRouter((context) => const OfflineMealView()), (route) => false); } } diff --git a/lib/okna/nastaveni.dart b/lib/okna/nastaveni.dart index a4a80e2..f1feeb5 100644 --- a/lib/okna/nastaveni.dart +++ b/lib/okna/nastaveni.dart @@ -5,6 +5,10 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart'; +import 'package:opencanteen/pw/platformbutton.dart'; +import 'package:opencanteen/pw/platformdialog.dart'; +import 'package:opencanteen/pw/platformfield.dart'; +import 'package:opencanteen/pw/platformswitch.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:timezone/timezone.dart' as tz; @@ -22,45 +26,48 @@ class AndroidNastaveni extends StatefulWidget { } class _AndroidNastaveniState extends State { - bool _ukladatOffline = false; - bool _preskakovatVikend = false; - bool _kontrolovatTyden = false; - bool _oznameniObed = false; - bool _zapamatovany = false; - TimeOfDay _oznameniCas = TimeOfDay.now(); + bool _saveOffline = false; + bool _skipWeekend = false; + bool _checkWeek = false; + bool _notifyMeal = false; + bool _remember = false; + TimeOfDay _notifTime = TimeOfDay.now(); final TextEditingController _countController = TextEditingController(text: "1"); SharedPreferences? preferences; - void najitNastaveni() async { + + void loadSettings() async { preferences = await SharedPreferences.getInstance(); - _zapamatovany = await LoginManager.zapamatovat(); - setState(() { - _ukladatOffline = preferences!.getBool("offline") ?? false; - _preskakovatVikend = preferences!.getBool("skip") ?? false; - _kontrolovatTyden = preferences!.getBool("tyden") ?? false; - _oznameniObed = preferences!.getBool("oznamit") ?? false; - _countController.text = - (preferences!.getInt("offline_pocet") ?? 1).toString(); - var casStr = preferences!.getString("oznameni_cas"); - if (casStr == null) { - var now = DateTime.now(); - _oznameniCas = TimeOfDay.fromDateTime( - DateTime.now().add(const Duration(hours: 1))); - preferences!.setString("oznameni_cas", now.toString()); - } else { - _oznameniCas = TimeOfDay.fromDateTime(DateTime.parse(casStr)); - } - }); + _remember = await LoginManager.rememberme(); + setState( + () { + _saveOffline = preferences!.getBool("offline") ?? false; + _skipWeekend = preferences!.getBool("skip") ?? false; + _checkWeek = preferences!.getBool("tyden") ?? false; + _notifyMeal = preferences!.getBool("oznamit") ?? false; + _countController.text = + (preferences!.getInt("offline_pocet") ?? 1).toString(); + var casStr = preferences!.getString("oznameni_cas"); + if (casStr == null) { + var now = DateTime.now(); + _notifTime = TimeOfDay.fromDateTime( + DateTime.now().add(const Duration(hours: 1))); + preferences!.setString("oznameni_cas", now.toString()); + } else { + _notifTime = TimeOfDay.fromDateTime(DateTime.parse(casStr)); + } + }, + ); } - void zmenitNastaveni(String key, bool value) async { + void changeSetting(String key, bool value) async { preferences!.setBool(key, value); } @override void initState() { super.initState(); - najitNastaveni(); + loadSettings(); } @override @@ -70,168 +77,168 @@ class _AndroidNastaveniState extends State { title: Text(Languages.of(context)!.settings), ), body: Center( - child: SizedBox( - width: MediaQuery.of(context).size.width / 1.1, - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(Languages.of(context)!.saveOffline), - Switch( - value: _ukladatOffline, - activeColor: Colors.purple, + child: SizedBox( + width: MediaQuery.of(context).size.width / 1.1, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(Languages.of(context)!.saveOffline), + PlatformSwitch( + value: _saveOffline, onChanged: (value) { setState(() { - _ukladatOffline = value; - cistit(value); - zmenitNastaveni("offline", value); + _saveOffline = value; + clear(value); + changeSetting("offline", value); }); - }) - ], - ), - 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( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(Languages.of(context)!.skipWeekend), - Switch( - activeColor: Colors.purple, - value: _preskakovatVikend, + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(Languages.of(context)!.saveCount), + SizedBox( + width: 35, + child: PlatformField( + controller: _countController, + enabled: _saveOffline, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + onChanged: (c) { + var cislo = int.tryParse(c); + if (cislo != null) { + preferences!.setInt("offline_pocet", cislo); + } + }, + ), + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(Languages.of(context)!.skipWeekend), + PlatformSwitch( + value: _skipWeekend, onChanged: (value) { - setState(() { - _preskakovatVikend = value; - zmenitNastaveni("skip", value); - }); - }) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible(child: Text(Languages.of(context)!.checkOrdered)), - Switch( - activeColor: Colors.purple, - value: _kontrolovatTyden, + setState( + () { + _skipWeekend = value; + changeSetting("skip", value); + }, + ); + }, + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(Languages.of(context)!.checkOrdered)), + PlatformSwitch( + value: _checkWeek, onChanged: (value) { - setState(() { - _kontrolovatTyden = value; - zmenitNastaveni("tyden", value); - }); - }) - ], - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible(child: Text(Languages.of(context)!.notifyLunch)), - Switch( - activeColor: Colors.purple, - value: _oznameniObed, - thumbColor: (!_zapamatovany - ? MaterialStateProperty.all(Colors.grey) - : null), + setState( + () { + _checkWeek = value; + changeSetting("tyden", value); + }, + ); + }, + ) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(Languages.of(context)!.notifyLunch)), + PlatformSwitch( + value: _notifyMeal, + thumbColor: (!_remember ? Colors.grey : null), onChanged: (value) { - if (!_zapamatovany) { + if (!_remember) { showDialog( - context: context, - builder: (bc) => AlertDialog( - title: Text(Languages.of(context)!.error), - content: - Text(Languages.of(context)!.needRemember), - actions: [ - TextButton( - child: Text(Languages.of(context)!.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - )); + context: context, + builder: (bc) => PlatformDialog( + title: Languages.of(context)!.error, + content: Languages.of(context)!.needRemember, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ), + ); } else { setState(() { - _oznameniObed = value; - if (_oznameniObed) { + _notifyMeal = value; + if (_notifyMeal) { showDialog( - context: context, - builder: (context) => AlertDialog( - title: - Text(Languages.of(context)!.warning), - content: Text( - Languages.of(context)!.notifyWarning), - actions: [ - TextButton( - child: - Text(Languages.of(context)!.ok), - onPressed: () { - Navigator.of(context).pop(); - }, - ) - ], - )); - vytvoritOznameni(casNaDate(_oznameniCas)); + context: context, + builder: (context) => PlatformDialog( + title: Languages.of(context)!.warning, + content: Languages.of(context)!.notifyWarning, + actions: [ + PlatformButton( + text: Languages.of(context)!.ok, + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + ), + ); + createNotif(timeToDate(_notifTime)); } - zmenitNastaveni("oznamit", value); + changeSetting("oznamit", value); }); } - }) - ], - ), - Text(Languages.of(context)!.notifyAt), - TextButton( - onPressed: () async { - if (_oznameniObed) { - var cas = await showTimePicker( - context: context, initialTime: _oznameniCas); - if (cas != null) { - var prefs = await SharedPreferences.getInstance(); - prefs.setString("oznameni_cas", - casNaDate(cas).toString()); // aktualizovat vybraný čas - var den = casNaDate(cas); - debugPrint(den.isAfter(DateTime.now()).toString()); - if (den.isAfter(DateTime.now())) { - // znovu vytvořit oznámení POUZE když je čas v budoucnosti - vytvoritOznameni(den); - } - } - setState(() { - _oznameniCas = cas ?? _oznameniCas; - }); - } - }, - child: Text( - "${(_oznameniCas.hour < 10 ? "0" : "") + _oznameniCas.hour.toString()}:${(_oznameniCas.minute < 10 ? "0" : "") + _oznameniCas.minute.toString()}", - style: TextStyle( - color: (!_oznameniObed) ? Colors.grey : Colors.purple), + }, + ) + ], ), - ), - ], + Text(Languages.of(context)!.notifyAt), + PlatformButton( + onPressed: () async { + if (_notifyMeal) { + var cas = await showTimePicker( + context: context, initialTime: _notifTime); + if (cas != null) { + var prefs = await SharedPreferences.getInstance(); + prefs.setString( + "oznameni_cas", + timeToDate(cas) + .toString()); // aktualizovat vybraný čas + var den = timeToDate(cas); + debugPrint(den.isAfter(DateTime.now()).toString()); + if (den.isAfter(DateTime.now())) { + // znovu vytvořit oznámení POUZE když je čas v budoucnosti + createNotif(den); + } + } + setState(() { + _notifTime = cas ?? _notifTime; + }); + } + }, + text: + "${(_notifTime.hour < 10 ? "0" : "") + _notifTime.hour.toString()}:${(_notifTime.minute < 10 ? "0" : "") + _notifTime.minute.toString()}", + ), + ], + ), ), - )), + ), ); } - void cistit(bool value) async { + void clear(bool value) async { if (!value) { Directory appDocDir = await getApplicationDocumentsDirectory(); for (var f in appDocDir.listSync()) { @@ -243,11 +250,10 @@ class _AndroidNastaveniState extends State { } } - void vytvoritOznameni(DateTime den) async { + void createNotif(DateTime den) async { await flutterLocalNotificationsPlugin.cancelAll(); - var d = await LoginManager.getDetails(); // získat údaje + var d = await LoginManager.getDetails(); // grab details if (d != null) { - // Nové oznámení var c = Canteen(d["url"]!); if (await c.login(d["user"]!, d["pass"]!)) { var jidla = await c.jidelnicekDen(); @@ -264,7 +270,7 @@ class _AndroidNastaveniState extends State { tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()); if (!mounted) return; await flutterLocalNotificationsPlugin.zonedSchedule( - // Vytvoří nové oznámení pro daný čas a datum + // schedules a notification 0, Languages.of(context)!.lunchNotif, "${jidlo.varianta} - ${jidlo.nazev}", @@ -274,7 +280,7 @@ class _AndroidNastaveniState extends State { uiLocalNotificationDateInterpretation: UILocalNotificationDateInterpretation.absoluteTime); } on StateError catch (_) { - // nenalezeno + // no meal found } } } diff --git a/lib/okna/offline_jidelnicek.dart b/lib/okna/offline_jidelnicek.dart index 438825a..1a8f39f 100644 --- a/lib/okna/offline_jidelnicek.dart +++ b/lib/okna/offline_jidelnicek.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:opencanteen/okna/login.dart'; +import 'package:opencanteen/pw/platformbutton.dart'; import 'package:opencanteen/util.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:path_provider/path_provider.dart'; @@ -12,90 +13,90 @@ import 'package:url_launcher/url_launcher.dart'; import '../../lang/lang.dart'; -class AndroidOfflineJidelnicek extends StatefulWidget { - const AndroidOfflineJidelnicek({Key? key}) : super(key: key); +class OfflineMealView extends StatefulWidget { + const OfflineMealView({Key? key}) : super(key: key); @override - State createState() => - _AndroidOfflineJidelnicekState(); + State createState() => _OfflineMealViewState(); } -class _AndroidOfflineJidelnicekState extends State { - List obsah = [const CircularProgressIndicator()]; - var _skipWeekend = false; - DateTime den = DateTime.now(); - String denTydne = ""; - List> data = []; - var jidloIndex = 0; +class _OfflineMealViewState extends State { + List content = [const CircularProgressIndicator()]; // view content + var _skipWeekend = false; // skip weekend setting + DateTime currentDay = DateTime.now(); // the day we are supposed to show + String dayOWeek = ""; // the name of the day (to show to user) + List> data = []; // meal data + var mealIndex = 0; // index of the currently shown day - void nactiZeSouboru() async { + /// Loads the offline data from local storage + void loadFromFile() 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 jidla = []; + List 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"]))); + jidla.add(OfflineMeal( + name: j["nazev"], + variant: j["varianta"], + ordered: j["objednano"], + price: j["cena"], + onExchange: j["naBurze"], + day: DateTime.parse(j["den"]))); } data.add(jidla); } } - nactiJidlo(); + loadFood(); } - Future nactiJidlo() async { - var jidelnicek = data[jidloIndex]; - den = jidelnicek[0].den; - switch (den.weekday) { + Future loadFood() async { + var jidelnicek = data[mealIndex]; + currentDay = jidelnicek[0].day; + switch (currentDay.weekday) { case 2: - denTydne = Languages.of(context)!.tuesday; + dayOWeek = Languages.of(context)!.tuesday; break; case 3: - denTydne = Languages.of(context)!.wednesday; + dayOWeek = Languages.of(context)!.wednesday; break; case 4: - denTydne = Languages.of(context)!.thursday; + dayOWeek = Languages.of(context)!.thursday; break; case 5: - denTydne = Languages.of(context)!.friday; + dayOWeek = Languages.of(context)!.friday; break; case 6: - denTydne = Languages.of(context)!.saturday; + dayOWeek = Languages.of(context)!.saturday; break; case 7: - denTydne = Languages.of(context)!.sunday; + dayOWeek = Languages.of(context)!.sunday; break; default: - denTydne = Languages.of(context)!.monday; + dayOWeek = Languages.of(context)!.monday; } - obsah = []; - for (OfflineJidlo j in jidelnicek) { - obsah.add( + content = []; + for (OfflineMeal j in jidelnicek) { + content.add( Padding( padding: const EdgeInsets.only(top: 15), child: InkWell( child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(j.varianta), + Text(j.variant), const SizedBox(width: 10), Flexible( child: Text( - j.nazev, + j.name, ), ), - Text((j.naBurze) + Text((j.onExchange) ? Languages.of(context)!.inExchange - : "${j.cena} Kč"), + : "${j.price} Kč"), Checkbox( - value: j.objednano, + value: j.ordered, fillColor: MaterialStateProperty.all(Colors.grey), onChanged: (v) async { return; @@ -110,14 +111,17 @@ class _AndroidOfflineJidelnicekState extends State { setState(() {}); } - void kliknuti(String value, BuildContext context) async { + void click(String value, BuildContext context) async { if (value == Languages.of(context)!.signOut) { const storage = FlutterSecureStorage(); storage.deleteAll(); Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); + context, platformRouter((c) => const LoginPage())); } else if (value == Languages.of(context)!.review) { - launchUrl(Uri.parse("market://details?id=cz.hernikplays.opencanteen"), + launchUrl( + Uri.parse((Platform.isAndroid) + ? "market://details?id=cz.hernikplays.opencanteen" + : "https://apps.apple.com/cz/app/opencanteen/id1621124445"), mode: LaunchMode.externalApplication); } else if (value == Languages.of(context)!.reportBugs) { launchUrl(Uri.parse("https://forms.gle/jKN7QeFJwpaApSbC8"), @@ -126,31 +130,33 @@ class _AndroidOfflineJidelnicekState extends State { var packageInfo = await PackageInfo.fromPlatform(); if (!mounted) return; showAboutDialog( - context: context, - applicationName: "OpenCanteen", - applicationLegalese: - "${Languages.of(context)!.copyright}\n${Languages.of(context)!.license}", - applicationVersion: packageInfo.version, - children: [ - TextButton( - onPressed: (() => launchUrl( - Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), - child: Text(Languages.of(context)!.source)) - ]); + context: context, + applicationName: "OpenCanteen", + applicationLegalese: + "${Languages.of(context)!.copyright}\n${Languages.of(context)!.license}", + applicationVersion: packageInfo.version, + children: [ + PlatformButton( + onPressed: (() => launchUrl( + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), + text: Languages.of(context)!.source, + ) + ], + ); } } @override void didChangeDependencies() { super.didChangeDependencies(); - nactiNastaveni(); + loadSettings(); } - void nactiNastaveni() async { + void loadSettings() async { var prefs = await SharedPreferences.getInstance(); _skipWeekend = prefs.getBool("skip") ?? false; if (!mounted) return; - nactiZeSouboru(); + loadFromFile(); } @override @@ -161,7 +167,7 @@ class _AndroidOfflineJidelnicekState extends State { automaticallyImplyLeading: false, actions: [ PopupMenuButton( - onSelected: ((String value) => kliknuti(value, context)), + onSelected: ((String value) => click(value, context)), itemBuilder: (BuildContext context) { return { Languages.of(context)!.reportBugs, @@ -195,69 +201,69 @@ class _AndroidOfflineJidelnicekState extends State { IconButton( onPressed: () { if (data.length <= 1) return; - obsah = [const CircularProgressIndicator()]; + content = [const CircularProgressIndicator()]; setState(() { - if (den.weekday == 1 && _skipWeekend) { + if (currentDay.weekday == 1 && _skipWeekend) { // pokud je pondělí a chceme přeskočit víkend - if (jidloIndex - 2 >= 0) { - jidloIndex -= data.length - 3; + if (mealIndex - 2 >= 0) { + mealIndex -= data.length - 3; } else { - jidloIndex = data.length - 1; + mealIndex = data.length - 1; } - } else if (jidloIndex == 0) { - jidloIndex = data.length - 1; + } else if (mealIndex == 0) { + mealIndex = data.length - 1; } else { - jidloIndex -= 1; + mealIndex -= 1; } - nactiJidlo(); + loadFood(); }); }, icon: const Icon(Icons.arrow_left)), - TextButton( + PlatformButton( onPressed: () async {}, - child: Text( - "${den.day}. ${den.month}. ${den.year} - $denTydne")), + text: + "${currentDay.day}. ${currentDay.month}. ${currentDay.year} - $dayOWeek"), IconButton( onPressed: () { if (data.length <= 1) return; - obsah = [const CircularProgressIndicator()]; + content = [const CircularProgressIndicator()]; setState(() { - if (den.weekday == 5 && _skipWeekend) { + if (currentDay.weekday == 5 && _skipWeekend) { // pokud je pondělí a chceme přeskočit víkend - if (jidloIndex + 2 <= data.length - 1) { - jidloIndex += 2; + if (mealIndex + 2 <= data.length - 1) { + mealIndex += 2; } else { - jidloIndex = 0; + mealIndex = 0; } - } else if (jidloIndex == data.length) { - jidloIndex = 0; + } else if (mealIndex == data.length) { + mealIndex = 0; } else { - jidloIndex += 1; + mealIndex += 1; } - nactiJidlo(); + loadFood(); }); }, icon: const Icon(Icons.arrow_right), ), IconButton( onPressed: () { - jidloIndex = 0; + mealIndex = 0; }, icon: const Icon(Icons.today)) ]), SingleChildScrollView( physics: const AlwaysScrollableScrollPhysics(), child: Column( - children: obsah, + children: content, ), ), ], ), ), ), - onRefresh: () => Navigator.pushReplacement(context, - MaterialPageRoute(builder: ((context) => const LoginPage()))), + onRefresh: () => Navigator.pushReplacement( + context, platformRouter((context) => const LoginPage())), ), ); } diff --git a/lib/okna/welcome.dart b/lib/okna/welcome.dart index 0d1e43f..de660a1 100644 --- a/lib/okna/welcome.dart +++ b/lib/okna/welcome.dart @@ -4,6 +4,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:introduction_screen/introduction_screen.dart'; import 'package:opencanteen/lang/lang.dart'; import 'package:opencanteen/okna/jidelnicek.dart'; +import 'package:opencanteen/util.dart'; class WelcomePage extends StatefulWidget { const WelcomePage({Key? key, required this.canteen}) : super(key: key); @@ -68,8 +69,7 @@ class _WelcomePageState extends State { await storage.write(key: "oc_souhlas", value: "ano"); if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( - MaterialPageRoute( - builder: (c) => JidelnicekView(canteen: widget.canteen)), + platformRouter((c) => MealView(canteen: widget.canteen)), (route) => false); }, ), diff --git a/lib/pw/platformbutton.dart b/lib/pw/platformbutton.dart new file mode 100644 index 0000000..4377da9 --- /dev/null +++ b/lib/pw/platformbutton.dart @@ -0,0 +1,18 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:opencanteen/pw/platformwidget.dart'; + +class PlatformButton extends PlatformWidget { + final String text; + final void Function()? onPressed; + const PlatformButton( + {super.key, required this.text, required this.onPressed}); + + @override + TextButton createAndroidWidget(BuildContext context) => + TextButton(onPressed: onPressed, child: Text(text)); + + @override + CupertinoButton createIosWidget(BuildContext context) => + CupertinoButton(onPressed: onPressed, child: Text(text)); +} diff --git a/lib/pw/platformdialog.dart b/lib/pw/platformdialog.dart new file mode 100644 index 0000000..da90e8b --- /dev/null +++ b/lib/pw/platformdialog.dart @@ -0,0 +1,30 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:opencanteen/pw/platformwidget.dart'; + +class PlatformDialog extends PlatformWidget { + final String title; + final String? content; + final List actions; + const PlatformDialog( + {super.key, required this.title, this.content, this.actions = const []}); + + @override + AlertDialog createAndroidWidget(BuildContext context) => AlertDialog( + title: Text(title), + content: (content != null) + ? SingleChildScrollView(child: Text(content!)) + : null, + actions: actions, + ); + + @override + CupertinoAlertDialog createIosWidget(BuildContext context) => + CupertinoAlertDialog( + title: Text(title), + content: (content != null) + ? SingleChildScrollView(child: Text(content!)) + : null, + actions: actions, + ); +} diff --git a/lib/pw/platformfield.dart b/lib/pw/platformfield.dart new file mode 100644 index 0000000..17c8b51 --- /dev/null +++ b/lib/pw/platformfield.dart @@ -0,0 +1,54 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:opencanteen/pw/platformwidget.dart'; + +class PlatformField extends PlatformWidget { + final TextEditingController? controller; + final bool? enabled; + final bool obscureText; + final String? labelText; + final bool autocorrect; + final TextInputType? keyboardType; + final List inputFormatters; + final void Function(String)? onChanged; + final List? autofillHints; + const PlatformField({ + super.key, + this.controller, + this.enabled, + this.labelText, + this.obscureText = false, + this.autocorrect = false, + this.keyboardType, + this.inputFormatters = const [], + this.onChanged, + this.autofillHints, + }); + + @override + TextField createAndroidWidget(BuildContext context) => TextField( + controller: controller, + enabled: enabled, + obscureText: obscureText, + decoration: InputDecoration(labelText: labelText), + autocorrect: autocorrect, + keyboardType: keyboardType, + inputFormatters: inputFormatters, + onChanged: onChanged, + autofillHints: autofillHints, + ); + + @override + CupertinoTextField createIosWidget(BuildContext context) => + CupertinoTextField( + controller: controller, + enabled: enabled, + obscureText: obscureText, + prefix: (labelText == null) ? null : Text(labelText!), + autocorrect: autocorrect, + keyboardType: keyboardType, + inputFormatters: inputFormatters, + onChanged: onChanged, + ); +} diff --git a/lib/pw/platformswitch.dart b/lib/pw/platformswitch.dart new file mode 100644 index 0000000..6f9b4f0 --- /dev/null +++ b/lib/pw/platformswitch.dart @@ -0,0 +1,28 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:opencanteen/pw/platformwidget.dart'; + +class PlatformSwitch extends PlatformWidget { + final bool value; + final void Function(bool)? onChanged; + final Color? thumbColor; + const PlatformSwitch( + {super.key, + required this.value, + required this.onChanged, + this.thumbColor}); + + @override + Switch createAndroidWidget(BuildContext context) => Switch( + value: value, + onChanged: onChanged, + thumbColor: MaterialStateProperty.all(thumbColor), + ); + + @override + CupertinoSwitch createIosWidget(BuildContext context) => CupertinoSwitch( + value: value, + onChanged: onChanged, + thumbColor: thumbColor, + ); +} diff --git a/lib/pw/platformwidget.dart b/lib/pw/platformwidget.dart new file mode 100644 index 0000000..5c3ba6b --- /dev/null +++ b/lib/pw/platformwidget.dart @@ -0,0 +1,22 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; + +/// Abstract class used to create widgets for the respective platform UI library +abstract class PlatformWidget + extends StatelessWidget { + const PlatformWidget({super.key}); + + @override + Widget build(BuildContext context) { + if (Platform.isAndroid) { + return createAndroidWidget(context); + } else { + return createIosWidget(context); + } + } + + A createAndroidWidget(BuildContext context); + + I createIosWidget(BuildContext context); +} diff --git a/lib/util.dart b/lib/util.dart index 5416490..2437898 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -1,7 +1,9 @@ import 'dart:io'; import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:opencanteen/okna/burza.dart'; import 'package:opencanteen/okna/jidelnicek.dart'; import 'lang/lang.dart'; @@ -28,8 +30,7 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) { title: Text(Languages.of(context)!.exchange), onTap: () => Navigator.push( context, - MaterialPageRoute( - builder: (context) => BurzaView(canteen: canteen)), + platformRouter((context) => BurzaView(canteen: canteen)), ), ), ], @@ -49,8 +50,7 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) { title: Text(Languages.of(context)!.home), onTap: () => Navigator.push( context, - MaterialPageRoute( - builder: (c) => JidelnicekView(canteen: canteen)), + platformRouter((c) => MealView(canteen: canteen)), ), ), ListTile( @@ -66,31 +66,60 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) { return drawer; } -class OfflineJidlo { - String nazev; - String varianta; - bool objednano; - double cena; - bool naBurze; - DateTime den; +class OfflineMeal { + String name; + String variant; + bool ordered; + double price; + bool onExchange; + DateTime day; - OfflineJidlo( - {required this.nazev, - required this.varianta, - required this.objednano, - required this.cena, - required this.naBurze, - required this.den}); + OfflineMeal( + {required this.name, + required this.variant, + required this.ordered, + required this.price, + required this.onExchange, + required this.day}); } -/// Vytvoří [DateTime] z [TimeOfDay] -DateTime casNaDate(TimeOfDay c) { +/// Parses [DateTime] from [TimeOfDay] +DateTime timeToDate(TimeOfDay c) { var now = DateTime.now(); return DateTime.parse( "${now.year}-${(now.month < 10 ? "0" : "") + now.month.toString()}-${(now.day < 10 ? "0" : "") + now.day.toString()} ${(c.hour < 10 ? "0" : "") + c.hour.toString()}:${(c.minute < 10 ? "0" : "") + c.minute.toString()}:00"); } +/// List of instances to be used in the dropdown menu List> instance = [ {"name": "SŠTE Brno, Olomoucká", "url": "https://stravovani.sstebrno.cz"}, {"name": "Jiné", "url": ""} ]; + +/// Used to display either a toas or a snackbar +void showInfo(BuildContext context, String message) { + if (Platform.isAndroid) { + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(message), + duration: const Duration(seconds: 4), + ), + ); + } else { + Fluttertoast.showToast( + msg: message, + toastLength: Toast.LENGTH_SHORT, + gravity: ToastGravity.BOTTOM, + timeInSecForIosWeb: 3, + backgroundColor: Colors.red, + textColor: Colors.white, + fontSize: 16.0, + ); + } +} + +Route platformRouter(Widget Function(BuildContext context) builder) => + (Platform.isAndroid) + ? MaterialPageRoute(builder: builder) + : CupertinoPageRoute(builder: builder); diff --git a/pubspec.lock b/pubspec.lock index 46bff16..7c1b21b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -232,6 +232,14 @@ packages: description: flutter source: sdk version: "0.0.0" + fluttertoast: + dependency: "direct main" + description: + name: fluttertoast + sha256: "7cc92eabe01e3f1babe1571c5560b135dfc762a34e41e9056881e2196b178ec1" + url: "https://pub.dev" + source: hosted + version: "8.1.2" http: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 85bb144..7936e95 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,10 +6,10 @@ publish_to: 'none' # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 # followed by an optional build number separated by a +. -version: 1.6.1+26 +version: 1.7.0+27 environment: - sdk: ">=2.16.1 <3.0.0" + sdk: ">=2.18.2 <3.0.0" dependencies: flutter: @@ -27,6 +27,7 @@ dependencies: flutter_native_timezone: ^2.0.0 intl: ^0.17.0 package_info_plus: ^1.4.3+1 + fluttertoast: ^8.1.2 dev_dependencies: flutter_lints: ^2.0.1