From 423cab90f175ae635f4670874731f72bc21a3244 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maty=C3=A1=C5=A1=20Caras?= Date: Thu, 1 Dec 2022 20:32:52 +0100 Subject: [PATCH] =?UTF-8?q?feat(nahravani):=20pr=C3=A1ce=20na=20stahov?= =?UTF-8?q?=C3=A1n=C3=AD=20j=C3=ADzdenek?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 7 +- android/app/proguard-rules.pro | 1 + android/app/src/main/AndroidManifest.xml | 7 +- .../src/main/res/values-cs-rCZ/strings.xml | 9 + lib/api/ceskedrahy.dart | 50 ++- lib/api/ceskedrahy.g.dart | 14 +- lib/api/jizdenka.dart | 33 +- lib/helper/bar.dart | 92 ++++++ lib/helper/manager.dart | 54 ++++ lib/helper/theme.dart | 10 + lib/main.dart | 25 +- lib/okna/domu.dart | 102 +++++- lib/okna/login.dart | 69 ---- lib/okna/nahrat.dart | 241 ++++++++++++++ lib/okna/nastaveni.dart | 88 ++++++ pubspec.lock | 295 +++++++++++++++++- pubspec.yaml | 8 + 17 files changed, 1015 insertions(+), 90 deletions(-) create mode 100644 android/app/proguard-rules.pro create mode 100644 android/app/src/main/res/values-cs-rCZ/strings.xml create mode 100644 lib/helper/bar.dart create mode 100644 lib/helper/manager.dart create mode 100644 lib/helper/theme.dart delete mode 100644 lib/okna/login.dart create mode 100644 lib/okna/nahrat.dart create mode 100644 lib/okna/nastaveni.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 9f5a8dc..1cecbcf 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -26,7 +26,7 @@ apply plugin: 'kotlin-android' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" android { - compileSdkVersion flutter.compileSdkVersion + compileSdkVersion 33 ndkVersion flutter.ndkVersion compileOptions { @@ -43,12 +43,11 @@ android { } defaultConfig { - // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "cz.hernikplays.cvak" // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. - minSdkVersion flutter.minSdkVersion - targetSdkVersion flutter.targetSdkVersion + minSdkVersion 19 + targetSdkVersion 33 versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro new file mode 100644 index 0000000..116bc22 --- /dev/null +++ b/android/app/proguard-rules.pro @@ -0,0 +1 @@ +-keep class androidx.lifecycle.DefaultLifecycleObserver \ No newline at end of file diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 05c4f1d..391c0c3 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,10 @@ - + + + + + Stahování začalo + Stahování probíhá + Stahování zrušeno + Stahování selhalo + Stahování dokončeno + Stahování pozastaveno + \ No newline at end of file diff --git a/lib/api/ceskedrahy.dart b/lib/api/ceskedrahy.dart index 091d7eb..83c0a4b 100644 --- a/lib/api/ceskedrahy.dart +++ b/lib/api/ceskedrahy.dart @@ -1,10 +1,28 @@ import 'dart:convert'; - +import 'dart:io'; import 'package:http/http.dart' as http; import 'package:json_annotation/json_annotation.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'jizdenka.dart'; part 'ceskedrahy.g.dart'; +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + class CeskeDrahy { final Map _cookie = {}; @@ -88,8 +106,24 @@ class CeskeDrahy { }); var data = jsonDecode(r.body); var jizdenky = []; + SharedPreferences p = await SharedPreferences.getInstance(); + var path = p.getString("dlpath")!; for (var j in data["items"]) { - jizdenky.add(CDJizdenka.fromJson(j)); + var cd = CDJizdenka.fromJson(j); + // stáhnout obrázek + var imgData = await http.get(Uri.parse("https://cd.cz${cd.qrUrl}"), + headers: {"Cookie": _cookieString()}); + // TODO FIX + if (imgData.statusCode < 400) { + var b64Data = + RegExp(r'src="data:image;base64,(.+?)"').firstMatch(imgData.body); + if (b64Data != null) { + print(b64Data.group(1)); + File("$path/${j.id}.png") + .writeAsBytesSync(base64Decode(b64Data.group(1)!)); + } + } + jizdenky.add(cd); } return jizdenky; } @@ -138,10 +172,18 @@ class CDJizdenka implements Jizdenka { @JsonKey(name: "serviceClass") final Class trida; + @JsonKey(name: "aztecImageUrl") + final String qrUrl; + + @override + @JsonKey(name: "transactionCode") + final String id; + factory CDJizdenka.fromJson(Map json) => _$CDJizdenkaFromJson(json); /// Connect the generated [_$PersonToJson] function to the `toJson` method. + @override Map toJson() => _$CDJizdenkaToJson(this); const CDJizdenka( @@ -153,7 +195,9 @@ class CDJizdenka implements Jizdenka { required this.zeStanice, required this.vracena, required this.cena, - required this.trida}); + required this.trida, + required this.qrUrl, + required this.id}); } @JsonSerializable() diff --git a/lib/api/ceskedrahy.g.dart b/lib/api/ceskedrahy.g.dart index 1adbdc6..f2b4f12 100644 --- a/lib/api/ceskedrahy.g.dart +++ b/lib/api/ceskedrahy.g.dart @@ -19,7 +19,9 @@ CDJizdenka _$CDJizdenkaFromJson(Map json) => CDJizdenka( zeStanice: json['stationFrom'] as String, vracena: json['isRefunded'] as bool, cena: const _CenaPrevodnik().fromJson(json['price'] as int), - trida: json['serviceClass'], + trida: $enumDecode(_$ClassEnumMap, json['serviceClass']), + qrUrl: json['aztecImageUrl'] as String, + id: json['transactionCode'] as String, ); Map _$CDJizdenkaToJson(CDJizdenka instance) => @@ -32,9 +34,17 @@ Map _$CDJizdenkaToJson(CDJizdenka instance) => 'stationFrom': instance.zeStanice, 'isRefunded': instance.vracena, 'price': const _CenaPrevodnik().toJson(instance.cena), - 'serviceClass': instance.trida, + 'serviceClass': _$ClassEnumMap[instance.trida]!, + 'aztecImageUrl': instance.qrUrl, + 'transactionCode': instance.id, }; +const _$ClassEnumMap = { + Class.class2: 'Class2', + Class.class1: 'Class1', + Class.business: 'Business', +}; + CDSpoj _$CDSpojFromJson(Map json) => CDSpoj( nazev: json['train'] as String, zeStanice: json['stationFrom'] as String, diff --git a/lib/api/jizdenka.dart b/lib/api/jizdenka.dart index 0e00477..4b60bf2 100644 --- a/lib/api/jizdenka.dart +++ b/lib/api/jizdenka.dart @@ -1,6 +1,28 @@ import 'package:json_annotation/json_annotation.dart'; -enum Dopravce { ceskeDrahy, regioJet } +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +enum Dopravce { + @JsonValue("CESKEDRAHY") + ceskeDrahy, + @JsonValue("REGIOJET") + regioJet +} abstract class Jizdenka { final Dopravce dopravce; @@ -12,6 +34,7 @@ abstract class Jizdenka { final String jmeno; final double cena; final Class trida; + final String id; const Jizdenka( {required this.jmeno, required this.dopravce, @@ -21,7 +44,13 @@ abstract class Jizdenka { required this.zeStanice, required this.doStanice, required this.cena, - required this.trida}); + required this.trida, + required this.id}); + + factory Jizdenka.fromJson(Map json) => + throw UnimplementedError(); + + Map toJson() => throw UnimplementedError(); } abstract class Spoj { diff --git a/lib/helper/bar.dart b/lib/helper/bar.dart new file mode 100644 index 0000000..00ede7b --- /dev/null +++ b/lib/helper/bar.dart @@ -0,0 +1,92 @@ +import 'package:cvak/okna/domu.dart'; +import 'package:cvak/okna/nahrat.dart'; +import 'package:cvak/okna/nastaveni.dart'; +import 'package:flutter/material.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +AppBar bar(context, {String nadpis = "Domů"}) => AppBar( + title: Text(nadpis), + actions: [ + PopupMenuButton( + itemBuilder: (c) => {'Nastavení', 'O Aplikaci'} + .map((e) => PopupMenuItem(value: e, child: Text(e))) + .toList(), + onSelected: ((value) async { + PackageInfo packageInfo = await PackageInfo.fromPlatform(); + switch (value) { + case "Nastavení": + Navigator.of(context) + .push(MaterialPageRoute(builder: (c) => const Nastaveni())); + break; + case "O Aplikaci": + showAboutDialog( + context: context, + applicationName: "Cvak", + applicationLegalese: + "Copyright ©️ 2022 Matyáš Caras\nVydáno pod licencí GNU GPLv3", + applicationVersion: packageInfo.version, + children: [ + TextButton( + child: const Text("Zdrojový kód"), + onPressed: () => launchUrl( + Uri.parse("https://git.mnau.xyz/hernik/cvak"), + mode: LaunchMode.externalApplication), + ) + ]); + break; + } + }), + ) + ], + ); + +Drawer drawer(int selected, BuildContext context) => Drawer( + child: ListView( + padding: EdgeInsets.zero, + children: [ + const DrawerHeader(child: Text("Cvak")), + ListTile( + title: const Text("Domů"), + leading: const Icon(Icons.home), + selected: selected == 1, + onTap: () => (selected == 1) + ? Navigator.of(context).pop() + : Navigator.of(context).pushReplacement(MaterialPageRoute( + builder: (c) => const DomovskaObrazovka())), + ), + ListTile( + title: const Text("Nová jízdenka"), + leading: const Icon(Icons.new_label), + selected: selected == 2, + onTap: () => Navigator.of(context).pop(), + ), + ListTile( + title: const Text("Nahrát jízdenky"), + leading: const Icon(Icons.download), + selected: selected == 3, + onTap: () => (selected == 3) + ? Navigator.of(context).pop() + : Navigator.of(context).push( + MaterialPageRoute(builder: (c) => const NahratJizdenku())), + ), + ], + ), + ); diff --git a/lib/helper/manager.dart b/lib/helper/manager.dart new file mode 100644 index 0000000..4a7133a --- /dev/null +++ b/lib/helper/manager.dart @@ -0,0 +1,54 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:cvak/api/ceskedrahy.dart'; +import 'package:cvak/api/jizdenka.dart'; +import 'package:path_provider/path_provider.dart'; + +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +class ManazerJizdenek { + static void ulozitJizdenku(Jizdenka j) { + var typ = ""; + if (j is CDJizdenka) { + typ = "cd"; + } else { + typ = "local"; + } + var data = { + "type": typ, + "data": {...j.toJson()} + }; + getApplicationDocumentsDirectory().then((d) => + File("${d.path}/j_${j.id}.json").writeAsStringSync(jsonEncode(data))); + } + + static List nahratJizdenky() { + var jizdenky = []; + getApplicationDocumentsDirectory().then((d) { + for (var file in Directory(d.path).listSync()) { + if (!file.path.endsWith(".json")) return; + var data = jsonDecode((file as File).readAsStringSync()); + if (data["type"] == "cd") { + jizdenky.add(CDJizdenka.fromJson(data["data"])); + } + } + }); + return jizdenky; + } +} diff --git a/lib/helper/theme.dart b/lib/helper/theme.dart new file mode 100644 index 0000000..dae90af --- /dev/null +++ b/lib/helper/theme.dart @@ -0,0 +1,10 @@ +import 'package:flutter/rendering.dart'; + +class Vzhled { + static const TextStyle nadpis = + TextStyle(fontSize: 28, fontWeight: FontWeight.bold); + static const TextStyle nadpisJizdenka = + TextStyle(fontSize: 20, fontWeight: FontWeight.bold); + static const Color okColor = Color.fromARGB(255, 106, 207, 110); + static const Color errorColor = Color.fromARGB(255, 207, 106, 106); +} diff --git a/lib/main.dart b/lib/main.dart index c65778c..481a2ca 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,30 @@ import 'package:cvak/okna/domu.dart'; import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; -void main() { +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + var p = await SharedPreferences.getInstance(); + if (p.getString("dlpath") == null) { + p.setString("dlpath", (await getApplicationDocumentsDirectory()).path); + } runApp(const MyApp()); } diff --git a/lib/okna/domu.dart b/lib/okna/domu.dart index 29aaf8a..10800ff 100644 --- a/lib/okna/domu.dart +++ b/lib/okna/domu.dart @@ -1,5 +1,27 @@ -import 'package:cvak/okna/login.dart'; +import 'package:cvak/helper/bar.dart'; +import 'package:cvak/helper/manager.dart'; +import 'package:cvak/helper/theme.dart'; +import 'package:device_info_plus/device_info_plus.dart'; +import 'package:flash/flash.dart'; import 'package:flutter/material.dart'; +import 'package:permission_handler/permission_handler.dart'; + +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ class DomovskaObrazovka extends StatefulWidget { const DomovskaObrazovka({super.key}); @@ -9,19 +31,87 @@ class DomovskaObrazovka extends StatefulWidget { } class _DomovskaObrazovkaState extends State { + var content = [const CircularProgressIndicator()]; + @override + void initState() { + super.initState(); + nacistJizdenky(); + } + @override Widget build(BuildContext context) { return Scaffold( + appBar: bar(context), + drawer: drawer(1, context), body: Center( child: SizedBox( - child: Column(children: [ - TextButton( - onPressed: (() => Navigator.of(context).push( - MaterialPageRoute(builder: (c) => const LoginStranka()))), - child: const Text("Přihlásit")) + child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ + const Text( + "Vaše jízdenky", + textAlign: TextAlign.center, + style: Vzhled.nadpis, + ), + const SizedBox( + height: 15, + ), + ...content ]), ), ), ); } + + void nacistJizdenky() async { + // permissions + var androidInfo = await DeviceInfoPlugin().androidInfo; + Map status; + if (androidInfo.version.sdkInt <= 32) { + status = await [ + Permission.storage, + ].request(); + } else { + status = await [Permission.photos].request(); + } + + var allAccepted = true; + status.forEach((permission, status) { + if (status != PermissionStatus.granted) { + allAccepted = false; + } + }); + + if (!allAccepted) { + showFlash( + context: context, + duration: const Duration(seconds: 5), + builder: (context, controller) => Flash.dialog( + controller: controller, + backgroundColor: Vzhled.errorColor, + child: const Text( + "Aplikace nedokáže bez oprávnění k úložišti pracovat!"))); + setState(() { + content = [const Text("Chybí oprávnění!")]; + }); + return; + } + // permissions end + + var j = ManazerJizdenek.nahratJizdenky(); + if (j.isEmpty) { + setState(() { + content = [const Text("Žádné uložené jízdenky :(")]; + }); + } else { + content = []; + for (var jizdenka in j) { + content.add(Column( + children: [ + Text("${jizdenka.zeStanice} - ${jizdenka.doStanice}", + style: Vzhled.nadpisJizdenka) + ], + )); + } + setState(() {}); + } + } } diff --git a/lib/okna/login.dart b/lib/okna/login.dart deleted file mode 100644 index 164579d..0000000 --- a/lib/okna/login.dart +++ /dev/null @@ -1,69 +0,0 @@ -import 'package:cvak/api/ceskedrahy.dart'; -import 'package:flutter/material.dart'; - -class LoginStranka extends StatefulWidget { - const LoginStranka({super.key}); - - @override - State createState() => _LoginStrankaState(); -} - -class _LoginStrankaState extends State { - final _mailManager = TextEditingController(); - final _passManager = TextEditingController(); - var rememberMe = false; - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Form( - child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [ - TextFormField( - decoration: const InputDecoration(hintText: "E-mail"), - controller: _mailManager, - ), - const SizedBox( - height: 15, - ), - TextFormField( - decoration: const InputDecoration(hintText: "Heslo"), - controller: _passManager, - ), - const SizedBox( - height: 10, - ), - Row(children: [ - Checkbox( - value: rememberMe, - onChanged: (v) { - rememberMe = v ?? false; - }, - ), - const SizedBox( - width: 10, - ), - const Text("Zapamatovat si mě") - ]), - const SizedBox(height: 10), - TextButton( - onPressed: () async { - var d = CeskeDrahy(); - // otestovat přihlášení - try { - await d.logIn(_mailManager.text, _passManager.text); - } catch (e) { - ScaffoldMessenger.of(context).clearSnackBars(); - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: Text( - "Nepodařilo se přihlásit, zkontrolujte údaje a připojení."), - duration: Duration(seconds: 5), - )); - } - }, - child: const Text("Přihlásit se")) - ]), - ), - ), - ); - } -} diff --git a/lib/okna/nahrat.dart b/lib/okna/nahrat.dart new file mode 100644 index 0000000..e4488d8 --- /dev/null +++ b/lib/okna/nahrat.dart @@ -0,0 +1,241 @@ +import 'package:cvak/helper/bar.dart'; +import 'package:cvak/helper/manager.dart'; +import 'package:cvak/helper/theme.dart'; +import 'package:flash/flash.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../api/ceskedrahy.dart'; + +/* + Copyright (C) 2022 Matyáš Caras + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +class NahratJizdenku extends StatefulWidget { + const NahratJizdenku({super.key}); + + @override + State createState() => _NahratJizdenkuState(); +} + +class _NahratJizdenkuState extends State { + final supported = ["České dráhy"]; + var selected = "České dráhy"; + var content = []; + + final _mailManager = TextEditingController(); + final _passManager = TextEditingController(); + var rememberMe = false; + @override + void initState() { + super.initState(); + content = [ + Form( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextFormField( + decoration: const InputDecoration(hintText: "E-mail"), + controller: _mailManager, + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'[a-zA-Z\-_0-9\.]+@{0,1}[a-zA-Z\-_0-9\.]{0,}')) + ], + keyboardType: TextInputType.emailAddress, + ), + const SizedBox( + height: 15, + ), + TextFormField( + decoration: const InputDecoration(hintText: "Heslo"), + obscureText: true, + controller: _passManager, + ), + const SizedBox( + height: 10, + ), + Row(children: [ + Checkbox( + value: rememberMe, + onChanged: (v) { + rememberMe = v ?? false; + }, + ), + const SizedBox( + width: 10, + ), + const Text("Zapamatovat si mě") + ]), + const SizedBox(height: 10), + TextButton( + onPressed: () async { + var d = CeskeDrahy(); + // otestovat přihlášení + try { + await d.logIn(_mailManager.text, _passManager.text); + for (var jizdenka in await d.ziskatJizdenky()) { + ManazerJizdenek.ulozitJizdenku(jizdenka); + } + if (!mounted) return; + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text("Jízdenky úspěšně uloženy."), + duration: Duration(seconds: 5), + )); + } catch (e) { + ScaffoldMessenger.of(context).clearSnackBars(); + ScaffoldMessenger.of(context).showSnackBar(const SnackBar( + content: Text( + "Nepodařilo se přihlásit, zkontrolujte údaje a připojení."), + duration: Duration(seconds: 5), + )); + } + }, + child: const Text("Přihlásit se")) + ], + ), + ), + ]; + setState(() {}); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + drawer: drawer(3, context), + appBar: bar(context, nadpis: "Nahrát jízdenku"), + body: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Nahrát jízdenku z externího zdroje", + textAlign: TextAlign.center, + style: Vzhled.nadpis, + ), + const SizedBox( + height: 10, + ), + DropdownButton( + value: selected, + items: List.generate( + supported.length, + (i) => DropdownMenuItem( + value: supported[i], + child: Text(supported[i]), + )), + onChanged: (v) { + if (v == null) return; + selected = v; + if (v == "České dráhý") { + content = [ + Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + TextField( + controller: _mailManager, + decoration: + const InputDecoration(hintText: "E-mail"), + inputFormatters: [ + FilteringTextInputFormatter.allow(RegExp( + r'[a-zA-Z\-_0-9\.]+@{0,1}[a-zA-Z\-_0-9\.]{0,}')) + ], + keyboardType: TextInputType.emailAddress, + ), + const SizedBox( + height: 15, + ), + TextField( + controller: _passManager, + decoration: const InputDecoration( + hintText: "Heslo", + ), + obscureText: true, + ), + const SizedBox( + height: 10, + ), + Row(children: [ + Checkbox( + value: rememberMe, + onChanged: (v) { + rememberMe = v ?? false; + }, + ), + const SizedBox( + width: 10, + ), + const Text("Zapamatovat si mě") + ]), + const SizedBox(height: 10), + TextButton( + onPressed: () async { + var d = CeskeDrahy(); + // otestovat přihlášení + try { + await d.logIn( + _mailManager.text, _passManager.text); + for (var jizdenka + in await d.ziskatJizdenky()) { + ManazerJizdenek.ulozitJizdenku(jizdenka); + } + if (!mounted) return; + showFlash( + duration: const Duration(seconds: 3), + context: context, + builder: ((context, controller) => + Flash.bar( + controller: controller, + backgroundColor: Vzhled.okColor, + child: const Text( + "Jízdenky úspěšně staženy"), + )), + ); + } catch (e) { + showFlash( + duration: const Duration(seconds: 5), + context: context, + builder: ((context, controller) => + Flash.bar( + controller: controller, + backgroundColor: Vzhled.errorColor, + child: const Text( + "Chyba při stahování, zkontrolujte údaje a připojení"), + )), + ); + } + }, + child: const Text("Přihlásit se")) + ], + ), + ]; + } + setState(() {}); + }, + ), + const SizedBox( + height: 10, + ), + ...content + ], + ), + ), + ), + ); + } +} diff --git a/lib/okna/nastaveni.dart b/lib/okna/nastaveni.dart new file mode 100644 index 0000000..5634d96 --- /dev/null +++ b/lib/okna/nastaveni.dart @@ -0,0 +1,88 @@ +import 'dart:io'; + +import 'package:cvak/helper/bar.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class Nastaveni extends StatefulWidget { + const Nastaveni({super.key}); + + @override + State createState() => _NastaveniState(); +} + +class _NastaveniState extends State { + @override + void initState() { + super.initState(); + nacistNastaveni(); + } + + var content = [const CircularProgressIndicator()]; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: bar(context, nadpis: "Nastavení"), + body: Center( + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.9, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: content, + ), + ), + ), + ); + } + + void nacistNastaveni() async { + content = []; + SharedPreferences p = await SharedPreferences.getInstance(); + var dd = (p.getString("dlpath")!).split("/"); + content.add(Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Složka s uloženými QR kódy jízdenek:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox( + width: 10, + ), + Text(dd[dd.length - 1]), + const SizedBox( + width: 10, + ), + TextButton( + onPressed: () async { + String? e = await FilePicker.platform.getDirectoryPath(); + if (e != null && e != "/") { + p.setString("dlpath", e); + nacistNastaveni(); + } + }, + child: const Text("Změnit")), + const SizedBox( + width: 5, + ), + TextButton( + onPressed: () async { + p.setString( + "dlpath", (await getApplicationDocumentsDirectory()).path); + nacistNastaveni(); + }, + child: const Text("Výchozí"), + ), + const Divider( + color: Colors.black, + /*TODO: dark theme */ + height: 4, + ) + ], + )); + setState(() {}); + } +} diff --git a/pubspec.lock b/pubspec.lock index 56dc7e9..b4f771f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -155,6 +155,27 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.4" + device_info_plus: + dependency: "direct main" + description: + name: device_info_plus + url: "https://pub.dartlang.org" + source: hosted + version: "8.0.0" + device_info_plus_platform_interface: + dependency: transitive + description: + name: device_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "7.0.0" + ffi: + dependency: transitive + description: + name: ffi + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" file: dependency: transitive description: @@ -162,6 +183,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.1.4" + file_picker: + dependency: "direct main" + description: + name: file_picker + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.3" fixnum: dependency: transitive description: @@ -169,6 +197,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.1" + flash: + dependency: "direct main" + description: + name: flash + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" flutter: dependency: "direct main" description: flutter @@ -188,6 +223,18 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.0.1" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" frontend_server_client: dependency: transitive description: @@ -250,7 +297,7 @@ packages: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.5" + version: "0.6.4" json_annotation: dependency: "direct main" description: @@ -314,6 +361,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.0" + package_info_plus: + dependency: "direct main" + description: + name: package_info_plus + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.2" + package_info_plus_platform_interface: + dependency: transitive + description: + name: package_info_plus_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.1" path: dependency: transitive description: @@ -321,6 +382,90 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.8.2" + path_provider: + dependency: "direct main" + description: + name: path_provider + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.22" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.11" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.7" + path_provider_macos: + dependency: transitive + description: + name: path_provider_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + url: "https://pub.dartlang.org" + source: hosted + version: "10.2.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + url: "https://pub.dartlang.org" + source: hosted + version: "9.0.7" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "3.9.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.2" petitparser: dependency: transitive description: @@ -328,6 +473,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "5.1.0" + platform: + dependency: transitive + description: + name: platform + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.0" + plugin_platform_interface: + dependency: transitive + description: + name: plugin_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" pointycastle: dependency: transitive description: @@ -342,6 +501,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.5.1" + process: + dependency: transitive + description: + name: process + url: "https://pub.dartlang.org" + source: hosted + version: "4.2.4" pub_semver: dependency: transitive description: @@ -356,6 +522,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.2.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.15" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.14" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + shared_preferences_macos: + dependency: transitive + description: + name: shared_preferences_macos + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.4" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" shelf: dependency: transitive description: @@ -445,6 +667,62 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.7" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.22" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + url: "https://pub.dartlang.org" + source: hosted + version: "6.0.17" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.13" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" vector_math: dependency: transitive description: @@ -466,6 +744,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.2.0" + win32: + dependency: transitive + description: + name: win32 + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.2" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+2" xml: dependency: transitive description: @@ -482,3 +774,4 @@ packages: version: "3.1.1" sdks: dart: ">=2.18.4 <3.0.0" + flutter: ">=3.0.0" diff --git a/pubspec.yaml b/pubspec.yaml index 18fd631..d46db24 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,6 +15,14 @@ dependencies: json_serializable: ^6.5.4 cupertino_icons: ^1.0.2 http: ^0.13.5 + path_provider: ^2.0.11 + file_picker: ^5.2.3 + shared_preferences: ^2.0.15 + flash: ^2.0.5 + package_info_plus: ^3.0.2 + url_launcher: ^6.1.7 + permission_handler: ^10.2.0 + device_info_plus: ^8.0.0 dev_dependencies: build_runner: ^2.3.2