feat(nahravani): práce na stahování jízdenek

This commit is contained in:
Matyáš Caras 2022-12-01 20:32:52 +01:00
parent d0273d5be4
commit 423cab90f1
17 changed files with 1015 additions and 90 deletions

View file

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android { android {
compileSdkVersion flutter.compileSdkVersion compileSdkVersion 33
ndkVersion flutter.ndkVersion ndkVersion flutter.ndkVersion
compileOptions { compileOptions {
@ -43,12 +43,11 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "cz.hernikplays.cvak" applicationId "cz.hernikplays.cvak"
// You can update the following values to match your application needs. // 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. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion minSdkVersion 19
targetSdkVersion flutter.targetSdkVersion targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
} }

1
android/app/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1 @@
-keep class androidx.lifecycle.DefaultLifecycleObserver

View file

@ -1,7 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
package="cz.hernikplays.cvak"> package="cz.hernikplays.cvak">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.INTERNET"/>
<application <application
android:label="cvak" android:label="Cvak"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"> android:icon="@mipmap/ic_launcher">
<activity <activity

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="flutter_downloader_notification_started">Stahování začalo</string>
<string name="flutter_downloader_notification_in_progress">Stahování probíhá</string>
<string name="flutter_downloader_notification_canceled">Stahování zrušeno</string>
<string name="flutter_downloader_notification_failed">Stahování selhalo</string>
<string name="flutter_downloader_notification_complete">Stahování dokončeno</string>
<string name="flutter_downloader_notification_paused">Stahování pozastaveno</string>
</resources>

View file

@ -1,10 +1,28 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http; import 'package:http/http.dart' as http;
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'jizdenka.dart'; import 'jizdenka.dart';
part 'ceskedrahy.g.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 <https://www.gnu.org/licenses/>.
*/
class CeskeDrahy { class CeskeDrahy {
final Map<String, String> _cookie = {}; final Map<String, String> _cookie = {};
@ -88,8 +106,24 @@ class CeskeDrahy {
}); });
var data = jsonDecode(r.body); var data = jsonDecode(r.body);
var jizdenky = <CDJizdenka>[]; var jizdenky = <CDJizdenka>[];
SharedPreferences p = await SharedPreferences.getInstance();
var path = p.getString("dlpath")!;
for (var j in data["items"]) { 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; return jizdenky;
} }
@ -138,10 +172,18 @@ class CDJizdenka implements Jizdenka {
@JsonKey(name: "serviceClass") @JsonKey(name: "serviceClass")
final Class trida; final Class trida;
@JsonKey(name: "aztecImageUrl")
final String qrUrl;
@override
@JsonKey(name: "transactionCode")
final String id;
factory CDJizdenka.fromJson(Map<String, dynamic> json) => factory CDJizdenka.fromJson(Map<String, dynamic> json) =>
_$CDJizdenkaFromJson(json); _$CDJizdenkaFromJson(json);
/// Connect the generated [_$PersonToJson] function to the `toJson` method. /// Connect the generated [_$PersonToJson] function to the `toJson` method.
@override
Map<String, dynamic> toJson() => _$CDJizdenkaToJson(this); Map<String, dynamic> toJson() => _$CDJizdenkaToJson(this);
const CDJizdenka( const CDJizdenka(
@ -153,7 +195,9 @@ class CDJizdenka implements Jizdenka {
required this.zeStanice, required this.zeStanice,
required this.vracena, required this.vracena,
required this.cena, required this.cena,
required this.trida}); required this.trida,
required this.qrUrl,
required this.id});
} }
@JsonSerializable() @JsonSerializable()

View file

@ -19,7 +19,9 @@ CDJizdenka _$CDJizdenkaFromJson(Map<String, dynamic> json) => CDJizdenka(
zeStanice: json['stationFrom'] as String, zeStanice: json['stationFrom'] as String,
vracena: json['isRefunded'] as bool, vracena: json['isRefunded'] as bool,
cena: const _CenaPrevodnik().fromJson(json['price'] as int), 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<String, dynamic> _$CDJizdenkaToJson(CDJizdenka instance) => Map<String, dynamic> _$CDJizdenkaToJson(CDJizdenka instance) =>
@ -32,7 +34,15 @@ Map<String, dynamic> _$CDJizdenkaToJson(CDJizdenka instance) =>
'stationFrom': instance.zeStanice, 'stationFrom': instance.zeStanice,
'isRefunded': instance.vracena, 'isRefunded': instance.vracena,
'price': const _CenaPrevodnik().toJson(instance.cena), '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<String, dynamic> json) => CDSpoj( CDSpoj _$CDSpojFromJson(Map<String, dynamic> json) => CDSpoj(

View file

@ -1,6 +1,28 @@
import 'package:json_annotation/json_annotation.dart'; 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 <https://www.gnu.org/licenses/>.
*/
enum Dopravce {
@JsonValue("CESKEDRAHY")
ceskeDrahy,
@JsonValue("REGIOJET")
regioJet
}
abstract class Jizdenka { abstract class Jizdenka {
final Dopravce dopravce; final Dopravce dopravce;
@ -12,6 +34,7 @@ abstract class Jizdenka {
final String jmeno; final String jmeno;
final double cena; final double cena;
final Class trida; final Class trida;
final String id;
const Jizdenka( const Jizdenka(
{required this.jmeno, {required this.jmeno,
required this.dopravce, required this.dopravce,
@ -21,7 +44,13 @@ abstract class Jizdenka {
required this.zeStanice, required this.zeStanice,
required this.doStanice, required this.doStanice,
required this.cena, required this.cena,
required this.trida}); required this.trida,
required this.id});
factory Jizdenka.fromJson(Map<String, dynamic> json) =>
throw UnimplementedError();
Map<String, dynamic> toJson() => throw UnimplementedError();
} }
abstract class Spoj { abstract class Spoj {

92
lib/helper/bar.dart Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
AppBar bar(context, {String nadpis = "Domů"}) => AppBar(
title: Text(nadpis),
actions: [
PopupMenuButton<String>(
itemBuilder: (c) => {'Nastavení', 'O Aplikaci'}
.map((e) => PopupMenuItem<String>(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())),
),
],
),
);

54
lib/helper/manager.dart Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
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<Jizdenka> nahratJizdenky() {
var jizdenky = <Jizdenka>[];
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;
}
}

10
lib/helper/theme.dart Normal file
View file

@ -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);
}

View file

@ -1,7 +1,30 @@
import 'package:cvak/okna/domu.dart'; import 'package:cvak/okna/domu.dart';
import 'package:flutter/material.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 <https://www.gnu.org/licenses/>.
*/
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var p = await SharedPreferences.getInstance();
if (p.getString("dlpath") == null) {
p.setString("dlpath", (await getApplicationDocumentsDirectory()).path);
}
runApp(const MyApp()); runApp(const MyApp());
} }

View file

@ -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: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 <https://www.gnu.org/licenses/>.
*/
class DomovskaObrazovka extends StatefulWidget { class DomovskaObrazovka extends StatefulWidget {
const DomovskaObrazovka({super.key}); const DomovskaObrazovka({super.key});
@ -9,19 +31,87 @@ class DomovskaObrazovka extends StatefulWidget {
} }
class _DomovskaObrazovkaState extends State<DomovskaObrazovka> { class _DomovskaObrazovkaState extends State<DomovskaObrazovka> {
var content = <Widget>[const CircularProgressIndicator()];
@override
void initState() {
super.initState();
nacistJizdenky();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: bar(context),
drawer: drawer(1, context),
body: Center( body: Center(
child: SizedBox( child: SizedBox(
child: Column(children: [ child: Column(mainAxisAlignment: MainAxisAlignment.center, children: [
TextButton( const Text(
onPressed: (() => Navigator.of(context).push( "Vaše jízdenky",
MaterialPageRoute(builder: (c) => const LoginStranka()))), textAlign: TextAlign.center,
child: const Text("Přihlásit")) style: Vzhled.nadpis,
),
const SizedBox(
height: 15,
),
...content
]), ]),
), ),
), ),
); );
} }
void nacistJizdenky() async {
// permissions
var androidInfo = await DeviceInfoPlugin().androidInfo;
Map<Permission, PermissionStatus> 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(() {});
}
}
} }

View file

@ -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<LoginStranka> createState() => _LoginStrankaState();
}
class _LoginStrankaState extends State<LoginStranka> {
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"))
]),
),
),
);
}
}

241
lib/okna/nahrat.dart Normal file
View file

@ -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 <https://www.gnu.org/licenses/>.
*/
class NahratJizdenku extends StatefulWidget {
const NahratJizdenku({super.key});
@override
State<NahratJizdenku> createState() => _NahratJizdenkuState();
}
class _NahratJizdenkuState extends State<NahratJizdenku> {
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
],
),
),
),
);
}
}

88
lib/okna/nastaveni.dart Normal file
View file

@ -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<Nastaveni> createState() => _NastaveniState();
}
class _NastaveniState extends State<Nastaveni> {
@override
void initState() {
super.initState();
nacistNastaveni();
}
var content = <Widget>[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(() {});
}
}

View file

@ -155,6 +155,27 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.4" 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: file:
dependency: transitive dependency: transitive
description: description:
@ -162,6 +183,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.1.4" 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: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -169,6 +197,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.1" version: "1.0.1"
flash:
dependency: "direct main"
description:
name: flash
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.5"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -188,6 +223,18 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.1" 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: frontend_server_client:
dependency: transitive dependency: transitive
description: description:
@ -250,7 +297,7 @@ packages:
name: js name: js
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.6.5" version: "0.6.4"
json_annotation: json_annotation:
dependency: "direct main" dependency: "direct main"
description: description:
@ -314,6 +361,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.0" 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: path:
dependency: transitive dependency: transitive
description: description:
@ -321,6 +382,90 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.8.2" 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: petitparser:
dependency: transitive dependency: transitive
description: description:
@ -328,6 +473,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.1.0" 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: pointycastle:
dependency: transitive dependency: transitive
description: description:
@ -342,6 +501,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.5.1" version: "1.5.1"
process:
dependency: transitive
description:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.4"
pub_semver: pub_semver:
dependency: transitive dependency: transitive
description: description:
@ -356,6 +522,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.2.1" 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: shelf:
dependency: transitive dependency: transitive
description: description:
@ -445,6 +667,62 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" 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: vector_math:
dependency: transitive dependency: transitive
description: description:
@ -466,6 +744,20 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.0" 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: xml:
dependency: transitive dependency: transitive
description: description:
@ -482,3 +774,4 @@ packages:
version: "3.1.1" version: "3.1.1"
sdks: sdks:
dart: ">=2.18.4 <3.0.0" dart: ">=2.18.4 <3.0.0"
flutter: ">=3.0.0"

View file

@ -15,6 +15,14 @@ dependencies:
json_serializable: ^6.5.4 json_serializable: ^6.5.4
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
http: ^0.13.5 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: dev_dependencies:
build_runner: ^2.3.2 build_runner: ^2.3.2