diff --git a/.metadata b/.metadata index 05f4d5d..9e70ade 100644 --- a/.metadata +++ b/.metadata @@ -1,10 +1,45 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 097d3313d8e2c7f901932d63e537c1acefb87800 + revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: android + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: ios + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: linux + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: macos + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: web + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + - platform: windows + create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a6408d..a35d17a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +# 1.6.0 +- rozdělit iOS a Android UI zvlášť pro možnost využití Cupertino knihovny +- opravit chybu s FlutterLocalNotifications na iOS # 1.5.1 - aktualizovat knihovnu canteenlib - přidat podporu pro splitování APK podle ABI diff --git a/README.md b/README.md index 8d9c48b..111fe69 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,9 @@ Open-Source **neoficiální** aplikace pro přístup do iCanteen [![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b) -[![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhernikplays%2Fopencanteen.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhernikplays%2Fopencanteen?ref=badge_shield) [![Codemagic build status](https://api.codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/status_badge.svg)](https://codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/latest_build) [![Commit Style: Conventional Commits](https://img.shields.io/badge/commit%20style-conventional%20commits-pink)](https://www.conventionalcommits.org/en/v1.0.0/) + [![Codemagic build status](https://api.codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/status_badge.svg)](https://codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/latest_build) [![Commit Style: Conventional Commits](https://img.shields.io/badge/commit%20style-conventional%20commits-pink)](https://www.conventionalcommits.org/en/v1.0.0/) + +### Upstream repozitář je nyní na adrese [https://git.mnau.xyz/hernik/opencanteen](https://git.mnau.xyz/hernik/opencanteen), všechny problémy hlaste tam. ## Co umí Aplikace vás přihlásí do vaší iCanteen ***pokud ji podporuje [canteenlib](https://github.com/hernikplays/canteenlib/blob/main/COMPATIBILITY.md), jinak experimentálně***, a umožní vám zobrazit, objednávat obědy, objednávat nebo přidávat jídlo na burzu. diff --git a/ios/Flutter/AppFrameworkInfo.plist b/ios/Flutter/AppFrameworkInfo.plist index 8d4492f..9625e10 100644 --- a/ios/Flutter/AppFrameworkInfo.plist +++ b/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 9.0 + 11.0 diff --git a/ios/Podfile b/ios/Podfile index 1e8c3c9..88359b2 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project -# platform :ios, '9.0' +# platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 6cb9f25..03e4a2b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,37 +1,41 @@ PODS: - - connectivity_plus (0.0.1): - - Flutter - - ReachabilitySwift - Flutter (1.0.0) + - flutter_local_notifications (0.0.1): + - Flutter + - flutter_native_timezone (0.0.1): + - Flutter - flutter_secure_storage (3.3.1): - Flutter + - package_info_plus (0.4.5): + - Flutter - path_provider_ios (0.0.1): - Flutter - - ReachabilitySwift (5.0.0) - shared_preferences_ios (0.0.1): - Flutter - url_launcher_ios (0.0.1): - Flutter DEPENDENCIES: - - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - Flutter (from `Flutter`) + - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) + - flutter_native_timezone (from `.symlinks/plugins/flutter_native_timezone/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) + - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) -SPEC REPOS: - trunk: - - ReachabilitySwift - EXTERNAL SOURCES: - connectivity_plus: - :path: ".symlinks/plugins/connectivity_plus/ios" Flutter: :path: Flutter + flutter_local_notifications: + :path: ".symlinks/plugins/flutter_local_notifications/ios" + flutter_native_timezone: + :path: ".symlinks/plugins/flutter_native_timezone/ios" flutter_secure_storage: :path: ".symlinks/plugins/flutter_secure_storage/ios" + package_info_plus: + :path: ".symlinks/plugins/package_info_plus/ios" path_provider_ios: :path: ".symlinks/plugins/path_provider_ios/ios" shared_preferences_ios: @@ -40,14 +44,15 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: - connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e - Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a + Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 + flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec + package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02 - ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825 shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de -PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c +PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 COCOAPODS: 1.11.3 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index dad830c..a52916f 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -340,7 +340,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -417,7 +417,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -466,7 +466,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 9.0; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 57d7799..78e1f39 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,5 +1,6 @@ import UIKit import Flutter +import flutter_local_notifications @UIApplicationMain @objc class AppDelegate: FlutterAppDelegate { diff --git a/lib/main.dart b/lib/main.dart index a7dfc66..c92756e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,16 +1,14 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:opencanteen/lang/lang_cz.dart'; import 'package:opencanteen/loginmanager.dart'; import 'package:canteenlib/canteenlib.dart'; -import 'package:opencanteen/okna/offline_jidelnicek.dart'; -import 'package:opencanteen/okna/welcome.dart'; +import 'package:opencanteen/okna/android/login.dart'; +import 'package:opencanteen/okna/ios/login.dart'; import 'package:opencanteen/util.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:intl/intl.dart'; @@ -19,7 +17,6 @@ import 'package:timezone/timezone.dart' as tz; import 'lang/lang.dart'; import 'lang/lang_en.dart'; -import 'okna/jidelnicek.dart'; /* Copyright (C) 2022 Matyáš Caras a přispěvatelé @@ -113,9 +110,10 @@ void main() async { const AndroidInitializationSettings initializationSettingsAndroid = AndroidInitializationSettings('notif_icon'); - const InitializationSettings initializationSettings = InitializationSettings( - android: initializationSettingsAndroid, - ); + const ios = DarwinInitializationSettings(); + + const InitializationSettings initializationSettings = + InitializationSettings(android: initializationSettingsAndroid, iOS: ios); await flutterLocalNotificationsPlugin.initialize(initializationSettings); // spustit aplikaci @@ -142,290 +140,11 @@ class MyApp extends StatelessWidget { brightness: Brightness.dark, primarySwatch: Colors.purple, ), - home: const LoginPage(), + home: (Platform.isAndroid) ? const AndroidLogin() : const IOSLogin(), ); } } -class LoginPage extends StatefulWidget { - const LoginPage({Key? key}) : super(key: key); - - @override - State createState() => _LoginPageState(); -} - -class _LoginPageState extends State { - TextEditingController userControl = TextEditingController(); - TextEditingController passControl = TextEditingController(); - TextEditingController canteenControl = TextEditingController(); - bool rememberMe = false; - bool _showUrl = false; - String dropdownUrl = instance.first["url"] ?? ""; - - @override - void initState() { - super.initState(); - LoginManager.getDetails().then((r) async { - if (Platform.isIOS) { - // žádat o oprávnění na iOS - await flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - IOSFlutterLocalNotificationsPlugin>() - ?.requestPermissions( - alert: true, - badge: true, - sound: true, - ); - } else if (Platform.isAndroid) { - // žádat o oprávnění na android - flutterLocalNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.requestPermission(); - } - if (r != null) { - // Automaticky přihlásit - 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)!.loggingIn) - ]), - ), - )); - var canteen = Canteen(r["url"]!); - try { - var l = await canteen.login(r["user"]!, r["pass"]!); - if (!l) { - if (!mounted) return; - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(Languages.of(context)!.loginFailed), - ), - ); - return; - } - 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) => WelcomeScreen( - canteen: canteen, n: flutterLocalNotificationsPlugin), - ), - (route) => false); - } else { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (context) => JidelnicekPage( - canteen: canteen, n: flutterLocalNotificationsPlugin), - ), - (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), - ), - ); - } catch (_) { - if (!mounted) return; - Navigator.of(context).pop(); - ScaffoldMessenger.of(context).hideCurrentSnackBar(); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(Languages.of(context)!.errorContacting), - ), - ); - goOffline(); - } - } - }); - } - - @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), - ), - 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) { - setState(() { - if (value == "") { - _showUrl = true; - } else { - _showUrl = false; - } - dropdownUrl = 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"; - } - 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), - ), - ); - 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) => WelcomeScreen( - canteen: canteen, - n: flutterLocalNotificationsPlugin)), - (route) => false); - } else { - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute( - builder: (context) => JidelnicekPage( - canteen: canteen, - n: flutterLocalNotificationsPlugin)), - (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(); - } - }, - child: Text(Languages.of(context)!.logIn)), - ], - ), - ), - ), - )); - } - - /// Získá offline soubor a zobrazí údaje - void goOffline() async { - if (!mounted) return; - Navigator.pushAndRemoveUntil( - context, - MaterialPageRoute(builder: ((context) => const OfflineJidelnicek())), - (route) => false); - } -} - class AppLocalizationsDelegate extends LocalizationsDelegate { const AppLocalizationsDelegate(); diff --git a/lib/okna/burza.dart b/lib/okna/android/burza.dart similarity index 89% rename from lib/okna/burza.dart rename to lib/okna/android/burza.dart index fce72a3..4e7dfd7 100644 --- a/lib/okna/burza.dart +++ b/lib/okna/android/burza.dart @@ -1,21 +1,18 @@ import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:opencanteen/okna/android/login.dart'; import 'package:opencanteen/util.dart'; -import '../lang/lang.dart'; -import '../main.dart'; +import '../../lang/lang.dart'; -class BurzaPage extends StatefulWidget { - const BurzaPage({Key? key, required this.canteen, required this.n}) - : super(key: key); +class AndroidBurza extends StatefulWidget { + const AndroidBurza({Key? key, required this.canteen}) : super(key: key); final Canteen canteen; - final FlutterLocalNotificationsPlugin n; @override - State createState() => _BurzaPageState(); + State createState() => _AndroidBurzaState(); } -class _BurzaPageState extends State { +class _AndroidBurzaState extends State { List obsah = []; double kredit = 0.0; Future nactiBurzu(BuildContext context) async { @@ -101,7 +98,7 @@ class _BurzaPageState extends State { }).catchError((o) { if (!widget.canteen.prihlasen) { Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); + context, MaterialPageRoute(builder: (c) => const AndroidLogin())); } }); } @@ -115,7 +112,7 @@ class _BurzaPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - drawer: drawerGenerator(context, widget.canteen, 3, widget.n), + drawer: drawerGenerator(context, widget.canteen, 3), appBar: AppBar( title: Text(Languages.of(context)!.exchange), ), diff --git a/lib/okna/jidelnicek.dart b/lib/okna/android/jidelnicek.dart similarity index 95% rename from lib/okna/jidelnicek.dart rename to lib/okna/android/jidelnicek.dart index 52cbe1c..26ed693 100644 --- a/lib/okna/jidelnicek.dart +++ b/lib/okna/android/jidelnicek.dart @@ -3,28 +3,25 @@ import 'dart:io'; import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:opencanteen/okna/nastaveni.dart'; +import 'package:opencanteen/okna/android/login.dart'; +import 'package:opencanteen/okna/android/nastaveni.dart'; import 'package:opencanteen/util.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 '../lang/lang.dart'; -import '../main.dart'; +import '../../lang/lang.dart'; -class JidelnicekPage extends StatefulWidget { - const JidelnicekPage({Key? key, required this.canteen, required this.n}) - : super(key: key); +class AndroidJidelnicek extends StatefulWidget { + const AndroidJidelnicek({Key? key, required this.canteen}) : super(key: key); final Canteen canteen; - final FlutterLocalNotificationsPlugin n; @override - State createState() => _JidelnicekPageState(); + State createState() => _AndroidJidelnicekState(); } -class _JidelnicekPageState extends State { +class _AndroidJidelnicekState extends State { List obsah = [const CircularProgressIndicator()]; DateTime den = DateTime.now(); String denTydne = ""; @@ -120,7 +117,7 @@ class _JidelnicekPageState extends State { Checkbox( value: j.objednano, fillColor: (j.lzeObjednat) - ? MaterialStateProperty.all(Colors.blue) + ? MaterialStateProperty.all(Colors.purple) : MaterialStateProperty.all(Colors.grey), onChanged: (v) async { if (!j.lzeObjednat) { @@ -312,13 +309,12 @@ class _JidelnicekPageState extends State { }).catchError((o) { if (!widget.canteen.prihlasen) { Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); + context, MaterialPageRoute(builder: (c) => const AndroidLogin())); } }); } - Future kliknuti(String value, BuildContext context, - FlutterLocalNotificationsPlugin n) async { + Future kliknuti(String value, BuildContext context) async { if (value == Languages.of(context)!.signOut) { await showDialog( context: context, @@ -332,7 +328,7 @@ class _JidelnicekPageState extends State { storage.deleteAll(); Navigator.pushAndRemoveUntil( context, - MaterialPageRoute(builder: (c) => const LoginPage()), + MaterialPageRoute(builder: (c) => const AndroidLogin()), (route) => false); }, child: Text(Languages.of(context)!.yes)), @@ -366,12 +362,12 @@ class _JidelnicekPageState extends State { children: [ TextButton( onPressed: (() => launchUrl( - Uri.parse("https://github.com/hernikplays/opencanteen"))), + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), child: Text(Languages.of(context)!.source)) ]); } else if (value == Languages.of(context)!.settings) { Navigator.push( - context, MaterialPageRoute(builder: (c) => Nastaveni(n: n))); + context, MaterialPageRoute(builder: (c) => const AndroidNastaveni())); } } @@ -403,7 +399,7 @@ class _JidelnicekPageState extends State { j = await widget.canteen.jidelnicekDen(den: d); } catch (e) { if (!widget.canteen.prihlasen) { - if (!mounted) return; // ! Přidat chybu, pokud není mounted + if (!mounted) return; ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar(SnackBar( content: Text(Languages.of(context)!.errorSaving), @@ -442,12 +438,12 @@ class _JidelnicekPageState extends State { @override Widget build(BuildContext context) { return Scaffold( - drawer: drawerGenerator(context, widget.canteen, 1, widget.n), + drawer: drawerGenerator(context, widget.canteen, 1), appBar: AppBar( title: Text(Languages.of(context)!.menu), actions: [ PopupMenuButton( - onSelected: ((String value) => kliknuti(value, context, widget.n)), + onSelected: ((String value) => kliknuti(value, context)), itemBuilder: (BuildContext context) { return { Languages.of(context)!.reportBugs, diff --git a/lib/okna/android/login.dart b/lib/okna/android/login.dart new file mode 100644 index 0000000..d7d4915 --- /dev/null +++ b/lib/okna/android/login.dart @@ -0,0 +1,283 @@ +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/android/welcome.dart'; + +import '../../lang/lang.dart'; +import '../../loginmanager.dart'; +import '../../main.dart'; +import '../../util.dart'; +import 'jidelnicek.dart'; +import 'offline_jidelnicek.dart'; + +class AndroidLogin extends StatefulWidget { + const AndroidLogin({Key? key}) : super(key: key); + @override + State createState() => _AndroidLoginState(); +} + +class _AndroidLoginState extends State { + TextEditingController userControl = TextEditingController(); + TextEditingController passControl = TextEditingController(); + TextEditingController canteenControl = TextEditingController(); + bool rememberMe = false; + bool _showUrl = false; + String dropdownUrl = instance.first["url"] ?? ""; + + @override + void initState() { + super.initState(); + LoginManager.getDetails().then((r) async { + // žádat o oprávnění na android + flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestPermission(); + + if (r != null) { + // Automaticky přihlásit + 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)!.loggingIn) + ]), + ), + )); + var canteen = Canteen(r["url"]!); + try { + var l = await canteen.login(r["user"]!, r["pass"]!); + if (!l) { + if (!mounted) return; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Languages.of(context)!.loginFailed), + ), + ); + return; + } + 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) => AndroidWelcome(canteen: canteen), + ), + (route) => false); + } else { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => AndroidJidelnicek(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), + ), + ); + } catch (_) { + if (!mounted) return; + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Languages.of(context)!.errorContacting), + ), + ); + goOffline(); + } + } + }); + } + + @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), + ), + 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) { + setState(() { + if (value == "") { + _showUrl = true; + } else { + _showUrl = false; + } + dropdownUrl = 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"; + } + 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), + ), + ); + 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) => AndroidWelcome( + canteen: canteen, + )), + (route) => false); + } else { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => AndroidJidelnicek( + 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(); + } + }, + child: Text(Languages.of(context)!.logIn)), + ], + ), + ), + ), + )); + } + + /// Získá offline soubor a zobrazí údaje + void goOffline() async { + if (!mounted) return; + if (Platform.isAndroid) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: ((context) => const AndroidOfflineJidelnicek())), + (route) => false); + } + } +} diff --git a/lib/okna/nastaveni.dart b/lib/okna/android/nastaveni.dart similarity index 95% rename from lib/okna/nastaveni.dart rename to lib/okna/android/nastaveni.dart index 9ad82a7..05cbd3c 100644 --- a/lib/okna/nastaveni.dart +++ b/lib/okna/android/nastaveni.dart @@ -9,20 +9,19 @@ import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:timezone/timezone.dart' as tz; -import '../lang/lang.dart'; -import '../loginmanager.dart'; -import '../util.dart'; +import '../../lang/lang.dart'; +import '../../loginmanager.dart'; +import '../../main.dart'; +import '../../util.dart'; -class Nastaveni extends StatefulWidget { - const Nastaveni({Key? key, required this.n}) : super(key: key); - - final FlutterLocalNotificationsPlugin n; +class AndroidNastaveni extends StatefulWidget { + const AndroidNastaveni({Key? key}) : super(key: key); @override - State createState() => _NastaveniState(); + State createState() => _AndroidNastaveniState(); } -class _NastaveniState extends State { +class _AndroidNastaveniState extends State { bool _ukladatOffline = false; bool _preskakovatVikend = false; bool _kontrolovatTyden = false; @@ -241,7 +240,7 @@ class _NastaveniState extends State { } void vytvoritOznameni(DateTime den) async { - await widget.n.cancelAll(); + await flutterLocalNotificationsPlugin.cancelAll(); var d = await LoginManager.getDetails(); // získat údaje if (d != null) { // Nové oznámení @@ -260,7 +259,7 @@ class _NastaveniState extends State { var l = tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()); if (!mounted) return; - await widget.n.zonedSchedule( + await flutterLocalNotificationsPlugin.zonedSchedule( // Vytvoří nové oznámení pro daný čas a datum 0, Languages.of(context)!.lunchNotif, diff --git a/lib/okna/offline_jidelnicek.dart b/lib/okna/android/offline_jidelnicek.dart similarity index 91% rename from lib/okna/offline_jidelnicek.dart rename to lib/okna/android/offline_jidelnicek.dart index fb25934..794af12 100644 --- a/lib/okna/offline_jidelnicek.dart +++ b/lib/okna/android/offline_jidelnicek.dart @@ -3,22 +3,23 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:opencanteen/okna/android/login.dart'; import 'package:opencanteen/util.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 '../lang/lang.dart'; -import '../main.dart'; +import '../../lang/lang.dart'; -class OfflineJidelnicek extends StatefulWidget { - const OfflineJidelnicek({Key? key}) : super(key: key); +class AndroidOfflineJidelnicek extends StatefulWidget { + const AndroidOfflineJidelnicek({Key? key}) : super(key: key); @override - State createState() => _OfflineJidelnicekState(); + State createState() => + _AndroidOfflineJidelnicekState(); } -class _OfflineJidelnicekState extends State { +class _AndroidOfflineJidelnicekState extends State { List obsah = [const CircularProgressIndicator()]; var _skipWeekend = false; DateTime den = DateTime.now(); @@ -94,11 +95,12 @@ class _OfflineJidelnicekState extends State { ? Languages.of(context)!.inExchange : "${j.cena} Kč"), Checkbox( - value: j.objednano, - fillColor: MaterialStateProperty.all(Colors.grey), - onChanged: (v) async { - return; - }) + value: j.objednano, + fillColor: MaterialStateProperty.all(Colors.grey), + onChanged: (v) async { + return; + }, + ) ], ), ), @@ -113,7 +115,7 @@ class _OfflineJidelnicekState extends State { const storage = FlutterSecureStorage(); storage.deleteAll(); Navigator.pushReplacement( - context, MaterialPageRoute(builder: (c) => const LoginPage())); + context, MaterialPageRoute(builder: (c) => const AndroidLogin())); } else if (value == Languages.of(context)!.review) { (Platform.isAndroid) ? launchUrl( @@ -138,7 +140,7 @@ class _OfflineJidelnicekState extends State { children: [ TextButton( onPressed: (() => launchUrl( - Uri.parse("https://github.com/hernikplays/opencanteen"))), + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), child: Text(Languages.of(context)!.source)) ]); } @@ -261,7 +263,7 @@ class _OfflineJidelnicekState extends State { ), ), onRefresh: () => Navigator.pushReplacement(context, - MaterialPageRoute(builder: ((context) => const LoginPage()))), + MaterialPageRoute(builder: ((context) => const AndroidLogin()))), ), ); } diff --git a/lib/okna/welcome.dart b/lib/okna/android/welcome.dart similarity index 80% rename from lib/okna/welcome.dart rename to lib/okna/android/welcome.dart index 676e023..9141ff9 100644 --- a/lib/okna/welcome.dart +++ b/lib/okna/android/welcome.dart @@ -1,23 +1,20 @@ import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; 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/okna/android/jidelnicek.dart'; -class WelcomeScreen extends StatefulWidget { - const WelcomeScreen({Key? key, required this.canteen, required this.n}) - : super(key: key); +class AndroidWelcome extends StatefulWidget { + const AndroidWelcome({Key? key, required this.canteen}) : super(key: key); final Canteen canteen; - final FlutterLocalNotificationsPlugin n; @override - State createState() => _WelcomeScreenState(); + State createState() => _AndroidWelcomeState(); } -class _WelcomeScreenState extends State { +class _AndroidWelcomeState extends State { @override Widget build(BuildContext context) { var listPagesViewModel = [ @@ -72,8 +69,7 @@ class _WelcomeScreenState extends State { if (!mounted) return; Navigator.of(context).pushAndRemoveUntil( MaterialPageRoute( - builder: (c) => - JidelnicekPage(canteen: widget.canteen, n: widget.n)), + builder: (c) => AndroidJidelnicek(canteen: widget.canteen)), (route) => false); }, ), diff --git a/lib/okna/ios/burza.dart b/lib/okna/ios/burza.dart new file mode 100644 index 0000000..c2340ed --- /dev/null +++ b/lib/okna/ios/burza.dart @@ -0,0 +1,143 @@ +import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:opencanteen/okna/ios/login.dart'; +import 'package:opencanteen/util.dart'; + +import '../../lang/lang.dart'; + +class IOSBurza extends StatefulWidget { + const IOSBurza({Key? key, required this.canteen}) : super(key: key); + final Canteen canteen; + @override + State createState() => _IOSBurzaState(); +} + +class _IOSBurzaState 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"), + CupertinoButton( + onPressed: () { + widget.canteen.objednatZBurzy(b).then((a) { + if (a) { + showDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: Text(Languages.of(context)!.ordered), + content: Text( + Languages.of(context)!.orderSuccess), + actions: [ + CupertinoButton( + child: Text(Languages.of(context)!.ok), + onPressed: () => + Navigator.of(context).pop(), + ) + ], + ), + ); + } else { + showDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: Text( + Languages.of(context)!.cannotOrder), + content: Text( + Languages.of(context)!.errorOrdering), + actions: [ + CupertinoButton( + child: Text(Languages.of(context)!.ok), + onPressed: () => + Navigator.of(context).pop(), + ) + ], + ), + ); + } + nactiBurzu(context); + }); + }, + child: Text(Languages.of(context)!.order)), + ], + ), + ), + ); + } + } + }); + }); + }).catchError((o) { + if (!widget.canteen.prihlasen) { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (c) => const IOSLogin())); + } + }); + } + + @override + void initState() { + super.initState(); + nactiBurzu(context); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: drawerGenerator(context, widget.canteen, 3), + appBar: AppBar( + title: Text(Languages.of(context)!.exchange), + ), + body: RefreshIndicator( + 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č"), + const SizedBox(height: 10), + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: SizedBox( + height: MediaQuery.of(context).size.height / 1.3, + child: Column(children: obsah), + ), + ) + ], + ), + ), + ), + onRefresh: () => nactiBurzu(context)), + ); + } +} diff --git a/lib/okna/ios/jidelnicek.dart b/lib/okna/ios/jidelnicek.dart new file mode 100644 index 0000000..ff6a60d --- /dev/null +++ b/lib/okna/ios/jidelnicek.dart @@ -0,0 +1,568 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:opencanteen/okna/ios/login.dart'; +import 'package:opencanteen/okna/ios/nastaveni.dart'; +import 'package:opencanteen/util.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 '../../lang/lang.dart'; + +class IOSJidelnicek extends StatefulWidget { + const IOSJidelnicek({Key? key, required this.canteen}) : super(key: key); + final Canteen canteen; + @override + State createState() => _IOSJidelnicekState(); +} + +class _IOSJidelnicekState extends State { + List obsah = [const CircularProgressIndicator()]; + DateTime den = DateTime.now(); + String denTydne = ""; + double kredit = 0.0; + bool _skipWeekend = false; + + void kontrolaTyden(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)); + for (var i = 0; i < 5; i++) { + var jidelnicek = await widget.canteen + .jidelnicekDen(den: pristi.add(Duration(days: i + 1))); + if (jidelnicek.jidla.isNotEmpty && + !jidelnicek.jidla.any((element) => element.objednano == true)) { + if (!mounted) break; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Languages.of(context)!.noOrder), + duration: const Duration(seconds: 5), + action: SnackBarAction( + onPressed: () => setState( + () { + den = pristi.add(Duration(days: i + 1)); + nactiJidlo(); + }, + ), + label: Languages.of(context)!.jump, + ), + ), + ); + break; + } + } + } + } + + Future nactiJidlo() async { + obsah = [const CircularProgressIndicator()]; + switch (den.weekday) { + case 2: + denTydne = Languages.of(context)!.tuesday; + break; + case 3: + denTydne = Languages.of(context)!.wednesday; + break; + case 4: + denTydne = Languages.of(context)!.thursday; + break; + case 5: + denTydne = Languages.of(context)!.friday; + break; + case 6: + denTydne = Languages.of(context)!.saturday; + break; + case 7: + denTydne = Languages.of(context)!.sunday; + break; + default: + denTydne = 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, + ), + ), + 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 CupertinoAlertDialog( + title: Text(Languages.of(context)! + .errorOrdering), + content: Text( + Languages.of(context)!.cannotOrder), + actions: [ + CupertinoButton( + 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) => CupertinoAlertDialog( + title: Text(Languages.of(context)! + .errorOrdering), + content: Text(o.toString()), + actions: [ + CupertinoButton( + child: Text( + Languages.of(context)! + .close), + onPressed: () { + Navigator.pop(bc); + }, + ) + ], + )); + }); + } + }) + ], + ), + onTap: () async { + if (!j.lzeObjednat) { + showDialog( + context: context, + builder: (context) { + return CupertinoAlertDialog( + title: + Text(Languages.of(context)!.errorOrdering), + content: + Text(Languages.of(context)!.cannotOrder), + actions: [ + CupertinoButton( + 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) => CupertinoAlertDialog( + title: + Text(Languages.of(context)!.errorOrdering), + content: Text(o.toString()), + actions: [ + CupertinoButton( + 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) => CupertinoAlertDialog( + title: Text( + Languages.of(context)!.exchangeError), + content: Text(o.toString()), + actions: [ + CupertinoButton( + child: Text( + Languages.of(context)!.close), + onPressed: () { + Navigator.pop(bc); + }, + ) + ], + )); + }); + } + } else { + // jinak ne + widget.canteen.doBurzy(j).then((_) => nactiJidlo()); + } + }, + ), + ), + ); + } + } + }); + }); + }).catchError((o) { + if (!widget.canteen.prihlasen) { + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (c) => const IOSLogin())); + } + }); + } + + Future kliknuti(String value, BuildContext context) async { + if (value == Languages.of(context)!.signOut) { + await showDialog( + context: context, + builder: (c) => CupertinoAlertDialog( + title: Text(Languages.of(context)!.warning), + content: Text(Languages.of(context)!.signOutWarn), + actions: [ + CupertinoButton( + onPressed: () { + const storage = FlutterSecureStorage(); + storage.deleteAll(); + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute(builder: (c) => const IOSLogin()), + (route) => false); + }, + child: Text(Languages.of(context)!.yes)), + CupertinoButton( + onPressed: () => Navigator.of(context).pop(), + child: Text(Languages.of(context)!.no)) + ], + ), + ); + } else if (value == Languages.of(context)!.review) { + launchUrl( + Uri.parse("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"), + mode: LaunchMode.externalApplication); + } else if (value == Languages.of(context)!.about) { + 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: [ + CupertinoButton( + onPressed: (() => launchUrl( + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), + child: Text(Languages.of(context)!.source)) + ]); + } else if (value == Languages.of(context)!.settings) { + Navigator.push( + context, MaterialPageRoute(builder: (c) => const IOSNastaveni())); + } + } + + void nactiNastaveni() async { + var prefs = await SharedPreferences.getInstance(); + _skipWeekend = prefs.getBool("skip") ?? false; + if (!mounted) return; + kontrolaTyden(context); + } + + void ulozitDoOffline() async { + var prefs = await SharedPreferences.getInstance(); + if (prefs.getBool("offline") ?? false) { + // vyčistit offline + Directory appDocDir = await getApplicationDocumentsDirectory(); + for (var f in appDocDir.listSync()) { + if (f.path.contains("jidelnicek")) { + f.deleteSync(); + } + } + + // uložit *pocet* jídelníčků pro offline použití + 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)); + Jidelnicek? j; + try { + j = await widget.canteen.jidelnicekDen(den: d); + } catch (e) { + if (!widget.canteen.prihlasen) { + if (!mounted) return; + 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)); + } + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + nactiNastaveni(); + ulozitDoOffline(); + nactiJidlo(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: drawerGenerator(context, widget.canteen, 1), + appBar: AppBar( + title: Text(Languages.of(context)!.menu), + actions: [ + PopupMenuButton( + onSelected: ((String value) => kliknuti(value, context)), + itemBuilder: (BuildContext context) { + return { + Languages.of(context)!.reportBugs, + Languages.of(context)!.review, + Languages.of(context)!.settings, + Languages.of(context)!.about, + Languages.of(context)!.signOut + }.map((String choice) { + return PopupMenuItem( + value: choice, + child: Text(choice), + ); + }).toList(); + }, + ), + ], + ), + body: RefreshIndicator( + onRefresh: nactiJidlo, + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width - 45, + 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)), + CupertinoButton( + onPressed: () async { + await showCupertinoModalPopup( + context: context, + builder: (c) => Container( + height: 216, + padding: const EdgeInsets.only(top: 6), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground + .resolveFrom(context), + child: SafeArea( + top: false, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.date, + onDateTimeChanged: (t) { + den = t; + }, + ), + ), + ), + ); + nactiJidlo(); + }, + 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)) + ]), + SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: GestureDetector( + child: Container( + color: Theme.of(context) + .colorScheme + .onPrimary + .withOpacity(0), + height: MediaQuery.of(context).size.height / 1.3, + child: Column(children: obsah), + ), + 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)); + } + nactiJidlo(); + }); + } else { + setState(() { + den = den.subtract(const Duration(days: 1)); + if (den.weekday == 7 && _skipWeekend) { + den = den.subtract(const Duration(days: 2)); + } + nactiJidlo(); + }); + } + }, + ), + ) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/okna/ios/login.dart b/lib/okna/ios/login.dart new file mode 100644 index 0000000..3335e09 --- /dev/null +++ b/lib/okna/ios/login.dart @@ -0,0 +1,294 @@ +import 'dart:io'; + +import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/cupertino.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/ios/welcome.dart'; + +import '../../lang/lang.dart'; +import '../../loginmanager.dart'; +import '../../main.dart'; +import '../../util.dart'; +import 'jidelnicek.dart'; +import 'offline_jidelnicek.dart'; + +class IOSLogin extends StatefulWidget { + const IOSLogin({Key? key}) : super(key: key); + @override + State createState() => _IOSLoginState(); +} + +class _IOSLoginState extends State { + TextEditingController userControl = TextEditingController(); + TextEditingController passControl = TextEditingController(); + TextEditingController canteenControl = TextEditingController(); + bool rememberMe = false; + bool _showUrl = false; + String dropdownUrl = instance.first["url"] ?? ""; + + @override + void initState() { + super.initState(); + LoginManager.getDetails().then((r) async { + if (Platform.isIOS) { + // žádat o oprávnění na iOS + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + IOSFlutterLocalNotificationsPlugin>() + ?.requestPermissions( + alert: true, + badge: true, + sound: true, + ); + } else if (Platform.isAndroid) { + // žádat o oprávnění na android + flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestPermission(); + } + if (r != null) { + // Automaticky přihlásit + 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)!.loggingIn) + ]), + ), + )); + var canteen = Canteen(r["url"]!); + try { + var l = await canteen.login(r["user"]!, r["pass"]!); + if (!l) { + if (!mounted) return; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Languages.of(context)!.loginFailed), + ), + ); + return; + } + 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) => IOSWelcome(canteen: canteen), + ), + (route) => false); + } else { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => IOSJidelnicek(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), + ), + ); + } catch (_) { + if (!mounted) return; + Navigator.of(context).pop(); + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text(Languages.of(context)!.errorContacting), + ), + ); + goOffline(); + } + } + }); + } + + @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), + ), + 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) { + setState(() { + if (value == "") { + _showUrl = true; + } else { + _showUrl = false; + } + dropdownUrl = 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: [ + CupertinoSwitch( + value: rememberMe, + onChanged: (value) { + setState(() { + rememberMe = value; + }); + }), + Text(Languages.of(context)!.rememberMe) + ]), + CupertinoButton( + 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; + ScaffoldMessenger.of(context).hideCurrentSnackBar(); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: + Text(Languages.of(context)!.loginFailed), + ), + ); + 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) => IOSWelcome( + canteen: canteen, + )), + (route) => false); + } else { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (context) => + IOSJidelnicek(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(); + } + }, + child: Text(Languages.of(context)!.logIn)), + ], + ), + ), + ), + )); + } + + /// Získá offline soubor a zobrazí údaje + void goOffline() async { + if (!mounted) return; + if (Platform.isAndroid) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: ((context) => const IOSOfflineJidelnicek())), + (route) => false); + } + } +} diff --git a/lib/okna/ios/nastaveni.dart b/lib/okna/ios/nastaveni.dart new file mode 100644 index 0000000..ac22020 --- /dev/null +++ b/lib/okna/ios/nastaveni.dart @@ -0,0 +1,279 @@ +import 'dart:io'; + +import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/cupertino.dart'; +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:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:timezone/timezone.dart' as tz; + +import '../../lang/lang.dart'; +import '../../loginmanager.dart'; +import '../../main.dart'; +import '../../util.dart'; + +class IOSNastaveni extends StatefulWidget { + const IOSNastaveni({Key? key}) : super(key: key); + + @override + State createState() => _IOSNastaveniState(); +} + +class _IOSNastaveniState extends State { + bool _ukladatOffline = false; + bool _preskakovatVikend = false; + bool _kontrolovatTyden = false; + bool _oznameniObed = false; + bool _zapamatovany = false; + TimeOfDay _oznameniCas = TimeOfDay.now(); + final TextEditingController _countController = + TextEditingController(text: "1"); + SharedPreferences? preferences; + void najitNastaveni() 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)); + } + }); + } + + void zmenitNastaveni(String key, bool value) async { + preferences!.setBool(key, value); + } + + @override + void initState() { + super.initState(); + najitNastaveni(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + 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), + CupertinoSwitch( + value: _ukladatOffline, + onChanged: (value) { + setState(() { + _ukladatOffline = value; + cistit(value); + zmenitNastaveni("offline", value); + }); + }) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(Languages.of(context)!.saveCount), + SizedBox( + width: 35, + child: CupertinoTextField( + 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), + CupertinoSwitch( + value: _preskakovatVikend, + onChanged: (value) { + setState(() { + _preskakovatVikend = value; + zmenitNastaveni("skip", value); + }); + }) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(Languages.of(context)!.checkOrdered)), + CupertinoSwitch( + value: _kontrolovatTyden, + onChanged: (value) { + setState(() { + _kontrolovatTyden = value; + zmenitNastaveni("tyden", value); + }); + }) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Flexible(child: Text(Languages.of(context)!.notifyLunch)), + CupertinoSwitch( + value: _oznameniObed, + thumbColor: (Colors.grey), + onChanged: (value) { + if (!_zapamatovany) { + showDialog( + context: context, + builder: (bc) => CupertinoAlertDialog( + title: Text(Languages.of(context)!.error), + content: + Text(Languages.of(context)!.needRemember), + actions: [ + CupertinoButton( + child: Text(Languages.of(context)!.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + )); + } else { + setState(() { + _oznameniObed = value; + if (_oznameniObed) { + showDialog( + context: context, + builder: (context) => CupertinoAlertDialog( + title: + Text(Languages.of(context)!.warning), + content: Text( + Languages.of(context)!.notifyWarning), + actions: [ + CupertinoButton( + child: + Text(Languages.of(context)!.ok), + onPressed: () { + Navigator.of(context).pop(); + }, + ) + ], + )); + vytvoritOznameni(casNaDate(_oznameniCas)); + } + zmenitNastaveni("oznamit", value); + }); + } + }) + ], + ), + Text(Languages.of(context)!.notifyAt), + CupertinoButton( + onPressed: () async { + if (_oznameniObed) { + showCupertinoModalPopup( + context: context, + builder: (c) { + return Container( + height: 216, + padding: const EdgeInsets.only(top: 6), + margin: EdgeInsets.only( + bottom: MediaQuery.of(context).viewInsets.bottom, + ), + color: CupertinoColors.systemBackground + .resolveFrom(context), + child: SafeArea( + top: false, + child: CupertinoDatePicker( + mode: CupertinoDatePickerMode.time, + onDateTimeChanged: (cas) { + setState(() { + _oznameniCas = TimeOfDay.fromDateTime(cas); + }); + }, + ), + ), + ); + }, + ); + } + }, + child: Text( + "${(_oznameniCas.hour < 10 ? "0" : "") + _oznameniCas.hour.toString()}:${(_oznameniCas.minute < 10 ? "0" : "") + _oznameniCas.minute.toString()}", + style: TextStyle( + color: (!_oznameniObed) ? Colors.grey : Colors.purple), + ), + ), + ], + ), + )), + ); + } + + void cistit(bool value) async { + if (!value) { + Directory appDocDir = await getApplicationDocumentsDirectory(); + for (var f in appDocDir.listSync()) { + // Vymažeme obsah + if (f.path.contains("jidelnicek")) { + f.deleteSync(); + } + } + } + } + + void vytvoritOznameni(DateTime den) async { + await flutterLocalNotificationsPlugin.cancelAll(); + var d = await LoginManager.getDetails(); // získat údaje + if (d != null) { + // Nové oznámení + 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); + var l = + tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()); + if (!mounted) return; + await flutterLocalNotificationsPlugin.zonedSchedule( + // Vytvoří nové oznámení pro daný čas a datum + 0, + Languages.of(context)!.lunchNotif, + "${jidlo.varianta} - ${jidlo.nazev}", + tz.TZDateTime.from(den, l), + const NotificationDetails(), + androidAllowWhileIdle: true, + uiLocalNotificationDateInterpretation: + UILocalNotificationDateInterpretation.absoluteTime); + } on StateError catch (_) { + // nenalezeno + } + } + } + } +} diff --git a/lib/okna/ios/offline_jidelnicek.dart b/lib/okna/ios/offline_jidelnicek.dart new file mode 100644 index 0000000..2d036ac --- /dev/null +++ b/lib/okna/ios/offline_jidelnicek.dart @@ -0,0 +1,270 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:opencanteen/okna/ios/login.dart'; +import 'package:opencanteen/util.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 '../../lang/lang.dart'; + +class IOSOfflineJidelnicek extends StatefulWidget { + const IOSOfflineJidelnicek({Key? key}) : super(key: key); + @override + State createState() => _IOSOfflineJidelnicekState(); +} + +class _IOSOfflineJidelnicekState extends State { + List obsah = [const CircularProgressIndicator()]; + var _skipWeekend = false; + DateTime den = DateTime.now(); + String denTydne = ""; + List> 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 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 nactiJidlo() async { + var jidelnicek = data[jidloIndex]; + den = jidelnicek[0].den; + switch (den.weekday) { + case 2: + denTydne = Languages.of(context)!.tuesday; + break; + case 3: + denTydne = Languages.of(context)!.wednesday; + break; + case 4: + denTydne = Languages.of(context)!.thursday; + break; + case 5: + denTydne = Languages.of(context)!.friday; + break; + case 6: + denTydne = Languages.of(context)!.saturday; + break; + case 7: + denTydne = Languages.of(context)!.sunday; + break; + default: + denTydne = Languages.of(context)!.monday; + } + obsah = []; + for (OfflineJidlo j in jidelnicek) { + 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, + ), + ), + Text((j.naBurze) + ? Languages.of(context)!.inExchange + : "${j.cena} Kč"), + Checkbox( + value: j.objednano, + fillColor: MaterialStateProperty.all(Colors.grey), + onChanged: (v) async { + return; + }, + ) + ], + ), + ), + ), + ); + } + setState(() {}); + } + + void kliknuti(String value, BuildContext context) async { + if (value == Languages.of(context)!.signOut) { + const storage = FlutterSecureStorage(); + storage.deleteAll(); + Navigator.pushReplacement( + context, MaterialPageRoute(builder: (c) => const IOSLogin())); + } else if (value == Languages.of(context)!.review) { + (Platform.isAndroid) + ? launchUrl( + Uri.parse("market://details?id=cz.hernikplays.opencanteen"), + mode: LaunchMode.externalApplication) + : launchUrl( + Uri.parse( + "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"), + mode: LaunchMode.externalApplication); + } else if (value == Languages.of(context)!.about) { + 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: [ + CupertinoButton( + onPressed: (() => launchUrl( + Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))), + child: Text(Languages.of(context)!.source)) + ]); + } + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + nactiNastaveni(); + } + + void nactiNastaveni() async { + var prefs = await SharedPreferences.getInstance(); + _skipWeekend = prefs.getBool("skip") ?? false; + if (!mounted) return; + nactiZeSouboru(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(Languages.of(context)!.menu), + automaticallyImplyLeading: false, + actions: [ + PopupMenuButton( + onSelected: ((String value) => kliknuti(value, context)), + itemBuilder: (BuildContext context) { + return { + Languages.of(context)!.reportBugs, + Languages.of(context)!.review, + Languages.of(context)!.about, + Languages.of(context)!.signOut + }.map((String choice) { + return PopupMenuItem( + value: choice, + child: Text(choice), + ); + }).toList(); + }, + ), + ], + ), + body: RefreshIndicator( + child: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width - 50, + child: Column( + children: [ + const SizedBox(height: 10), + Text( + Languages.of(context)!.offline, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + Text(Languages.of(context)!.mustLogout), + const SizedBox(height: 10), + Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + IconButton( + 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)), + CupertinoButton( + 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( + physics: const AlwaysScrollableScrollPhysics(), + child: Column( + children: obsah, + ), + ), + ], + ), + ), + ), + onRefresh: () => Navigator.pushReplacement(context, + MaterialPageRoute(builder: ((context) => const IOSLogin()))), + ), + ); + } +} diff --git a/lib/okna/ios/welcome.dart b/lib/okna/ios/welcome.dart new file mode 100644 index 0000000..c2bef07 --- /dev/null +++ b/lib/okna/ios/welcome.dart @@ -0,0 +1,78 @@ +import 'package:canteenlib/canteenlib.dart'; +import 'package:flutter/material.dart'; +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/ios/jidelnicek.dart'; + +class IOSWelcome extends StatefulWidget { + const IOSWelcome({Key? key, required this.canteen}) : super(key: key); + + final Canteen canteen; + + @override + State createState() => _IOSWelcomeState(); +} + +class _IOSWelcomeState extends State { + @override + Widget build(BuildContext context) { + var listPagesViewModel = [ + PageViewModel( + title: Languages.of(context)!.welcome, + body: Languages.of(context)!.appDesc, + image: const Center( + child: Icon(Icons.waving_hand_outlined, size: 175), + ), + ), + PageViewModel( + title: Languages.of(context)!.aboutOrder, + body: Languages.of(context)!.howOrder, + image: Center( + child: Image.asset('assets/objednavam.png', + width: MediaQuery.of(context).size.width * 0.85), + ), + ), + PageViewModel( + title: Languages.of(context)!.aboutToExch, + body: Languages.of(context)!.howToExch, + image: Center( + child: Image.asset('assets/doburzy.png', + width: MediaQuery.of(context).size.width * 0.85), + ), + ), + PageViewModel( + title: Languages.of(context)!.aboutFromExch, + body: Languages.of(context)!.howFromExch, + image: Center( + child: Image.asset('assets/burza.png', + width: MediaQuery.of(context).size.width * 0.85), + ), + ), + PageViewModel( + title: Languages.of(context)!.warning, + body: Languages.of(context)!.notOfficial, + image: const Center( + child: Icon(Icons.warning_amber_outlined, size: 175), + ), + ), + ]; + return Scaffold( + body: IntroductionScreen( + pages: listPagesViewModel, + next: Text(Languages.of(context)!.next), + done: Text(Languages.of(context)!.ok, + style: const TextStyle(fontWeight: FontWeight.w600)), + onDone: () async { + const storage = FlutterSecureStorage(); + await storage.write(key: "oc_souhlas", value: "ano"); + if (!mounted) return; + Navigator.of(context).pushAndRemoveUntil( + MaterialPageRoute( + builder: (c) => IOSJidelnicek(canteen: widget.canteen)), + (route) => false); + }, + ), + ); + } +} diff --git a/lib/util.dart b/lib/util.dart index adc34fa..bce7f90 100644 --- a/lib/util.dart +++ b/lib/util.dart @@ -1,13 +1,14 @@ +import 'dart:io'; + import 'package:canteenlib/canteenlib.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; -import 'package:opencanteen/okna/burza.dart'; - +import 'package:opencanteen/okna/android/burza.dart'; +import 'package:opencanteen/okna/android/jidelnicek.dart'; +import 'package:opencanteen/okna/ios/burza.dart'; +import 'package:opencanteen/okna/ios/jidelnicek.dart'; import 'lang/lang.dart'; -import 'okna/jidelnicek.dart'; -Drawer drawerGenerator(BuildContext context, Canteen canteen, int p, - FlutterLocalNotificationsPlugin n) { +Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) { Drawer drawer = const Drawer(); switch (p) { case 1: @@ -30,7 +31,9 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p, onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (context) => BurzaPage(canteen: canteen, n: n), + builder: (context) => (Platform.isAndroid) + ? AndroidBurza(canteen: canteen) + : IOSBurza(canteen: canteen), ), ), ), @@ -52,7 +55,9 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p, onTap: () => Navigator.push( context, MaterialPageRoute( - builder: (c) => JidelnicekPage(canteen: canteen, n: n))), + builder: (c) => (Platform.isAndroid) + ? AndroidJidelnicek(canteen: canteen) + : IOSJidelnicek(canteen: canteen))), ), ListTile( leading: const Icon(Icons.store), diff --git a/metadata/cs-CZ/changelogs/25.txt b/metadata/cs-CZ/changelogs/25.txt new file mode 100644 index 0000000..e9c471d --- /dev/null +++ b/metadata/cs-CZ/changelogs/25.txt @@ -0,0 +1 @@ +- Optimalizace ze strany kodu \ No newline at end of file diff --git a/metadata/en-US/changelogs/25.txt b/metadata/en-US/changelogs/25.txt new file mode 100644 index 0000000..28edfaf --- /dev/null +++ b/metadata/en-US/changelogs/25.txt @@ -0,0 +1 @@ +- Code optimization \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index d258dce..d2938c4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -6,7 +6,7 @@ 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.5.1+24 +version: 1.6.0+25 environment: sdk: ">=2.16.1 <3.0.0"