Připravit první release

This commit is contained in:
Matyáš Caras 2022-04-05 19:48:14 +02:00
parent d0aa394009
commit bf7dc7a165
13 changed files with 423 additions and 118 deletions

20
PRIVACY.md Normal file
View file

@ -0,0 +1,20 @@
## Zásady používání a ochrany soukromí aplikace OpenCanteen
Platí od 5. 4. 2022
### Používání
- Tato aplikace není vyvíjena nebo podporována firmou Z-WARE s.r.o. a není oficiální klient pro systém iCanteen
- Uživatel aplikace sám zodpovídá za svůj účet a údaje
- Vývojář aplikace neručí za stabilitu aplikace ani za vzniklé škody, které mohly vzniknout používáním aplikace
- Použití aplikace se řídí případnými podmínkami k používání systému iCanteen
## Ochrana soukromí
- Aplikace neobsahuje žádné analytické nebo sledovací systémy
- Aplikace odesílá data pouze na URL instance systému iCanteen, kterou uživatel aplikace sám zadá
- V případě, že tato instance není zabezpečena protokolem HTTPS, neručí vývojář aplikace za vzniklá nebezpečí
- Aplikace neobsahuje reklamy
- Aplikace dlouhodobě ukládá údaje uživatele (přihlašovací jméno, heslo, URL k instanci) pouze v případě, že uživatel při přihlašování povolí možnost `Zapamatovat si mě`. V tom případě jsou data zašifrována a uložena na zařízení uživatele
- V opačném případě jsou údaje uložené v mezipaměti jen na dobu nezbytně nutnou a následně smazány operačním systémem
### Kontakt
Preferovaná forma komunikace je skrze Diskuze nebo Issues.
V případech nejvyšší nutnosti je můj e-mail `contact zavináč hernikplays.cz`

View file

@ -12,7 +12,8 @@
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize"> android:windowSoftInputMode="adjustResize"
android:screenOrientation="portrait">
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues while the Flutter UI initializes. After that, this theme continues

View file

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/loginmanager.dart'; import 'package:opencanteen/loginmanager.dart';
import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:connectivity_plus/connectivity_plus.dart';
@ -16,6 +17,9 @@ class MyApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: GlobalMaterialLocalizations.delegates,
supportedLocales: const [Locale("cs")],
title: 'OpenCanteen', title: 'OpenCanteen',
theme: ThemeData( theme: ThemeData(
primarySwatch: Colors.purple, primarySwatch: Colors.purple,
@ -58,6 +62,18 @@ class _LoginPageState extends State<LoginPage> {
} }
if (r != null) { if (r != null) {
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => Dialog(
child: SizedBox(
height: 100,
child: Row(children: const [
CircularProgressIndicator(),
Text("Přihlašuji vás")
]),
),
));
var canteen = Canteen(r["url"]!); var canteen = Canteen(r["url"]!);
var l = await canteen.login(r["user"]!, r["pass"]!); var l = await canteen.login(r["user"]!, r["pass"]!);
if (!l) { if (!l) {
@ -88,7 +104,7 @@ class _LoginPageState extends State<LoginPage> {
title: const Text("Přihlášení"), title: const Text("Přihlášení"),
), ),
body: Center( body: Center(
child: Container( child: SizedBox(
width: MediaQuery.of(context).size.width - 50, width: MediaQuery.of(context).size.width - 50,
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -120,6 +136,7 @@ class _LoginPageState extends State<LoginPage> {
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
controller: canteenControl, controller: canteenControl,
), ),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Switch( Switch(
value: rememberMe, value: rememberMe,
onChanged: (value) { onChanged: (value) {
@ -127,6 +144,8 @@ class _LoginPageState extends State<LoginPage> {
rememberMe = value; rememberMe = value;
}); });
}), }),
const Text("Zapamatovat si mě")
]),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
if (canteenControl.text.contains("http://")) { if (canteenControl.text.contains("http://")) {

106
lib/okna/burza.dart Normal file
View file

@ -0,0 +1,106 @@
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
import 'package:opencanteen/util.dart';
import '../main.dart';
class BurzaPage extends StatefulWidget {
const BurzaPage({Key? key, required this.canteen, required this.user})
: super(key: key);
final Canteen canteen;
final String user;
@override
State<BurzaPage> createState() => _BurzaPageState();
}
class _BurzaPageState extends State<BurzaPage> {
List<Widget> obsah = [];
double kredit = 0.0;
Future<void> nactiBurzu() async {
obsah = [const CircularProgressIndicator()];
widget.canteen.ziskejUzivatele().then((kr) {
kredit = kr.kredit;
widget.canteen.ziskatBurzu().then((burza) {
setState(() {
obsah = [];
if (burza.isEmpty) {
obsah = [
const Text(
"Žádné jídlo v burze.",
style: TextStyle(fontSize: 20),
),
const Text("Potáhněte zvrchu pro načtení.")
];
} else {
for (var b in burza) {
obsah.add(
Padding(
padding: const EdgeInsets.only(top: 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text("${b.den.day}. ${b.den.month}."),
const SizedBox(width: 10),
Flexible(
child: Text(
b.nazev,
),
),
Text("${b.pocet}x"),
TextButton(
onPressed: () {
widget.canteen.objednatZBurzy(b).then((_) {
nactiBurzu();
});
},
child: const Text("Objednat")),
],
),
),
);
}
}
});
});
}).catchError((o) {
if (!widget.canteen.prihlasen) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (c) => const LoginPage()));
}
});
}
@override
void initState() {
super.initState();
nactiBurzu();
}
@override
Widget build(BuildContext context) {
return Scaffold(
drawer: drawerGenerator(context, widget.canteen, widget.user, 3),
appBar: AppBar(
title: const Text('Burza'),
),
body: RefreshIndicator(
child: Center(
child: Column(
children: [
const SizedBox(height: 10),
Text("Kredit: $kredit"),
const SizedBox(height: 10),
SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox(
height: MediaQuery.of(context).size.height - 120,
child: Column(children: obsah),
),
)
],
),
),
onRefresh: () => nactiBurzu()),
);
}
}

View file

@ -34,6 +34,7 @@ class _HomePageState extends State<HomePage> {
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(j.varianta), Text(j.varianta),
const SizedBox(width: 10), const SizedBox(width: 10),

View file

@ -1,7 +1,6 @@
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:flutter_holo_date_picker/flutter_holo_date_picker.dart';
import '../main.dart'; import '../main.dart';
@ -15,21 +14,52 @@ class JidelnicekPage extends StatefulWidget {
} }
class _JidelnicekPageState extends State<JidelnicekPage> { class _JidelnicekPageState extends State<JidelnicekPage> {
List<Widget> obsah = []; List<Widget> obsah = [const Text("Načítám...")];
DateTime den = DateTime.now(); DateTime den = DateTime.now();
String denTydne = ""; String denTydne = "";
double kredit = 0.0; double kredit = 0.0;
Future<void> nactiJidlo() async { Future<void> nactiJidlo() async {
obsah = []; obsah = [const CircularProgressIndicator()];
switch (den.weekday) {
case 2:
denTydne = "Úterý";
break;
case 3:
denTydne = "Středa";
break;
case 4:
denTydne = "Čtvrtek";
break;
case 5:
denTydne = "Pátek";
break;
case 6:
denTydne = "Sobota";
break;
case 7:
denTydne = "Neděle";
break;
default:
denTydne = "Pondělí";
}
widget.canteen.ziskejUzivatele().then((kr) { widget.canteen.ziskejUzivatele().then((kr) {
kredit = kr.kredit; kredit = kr.kredit;
widget.canteen.jidelnicekDen(den: den).then((jd) { widget.canteen.jidelnicekDen(den: den).then((jd) {
setState(() { setState(() {
obsah = [];
if (jd.jidla.isEmpty) {
obsah.add(const Text(
"Žádné jídlo pro tento den",
style: TextStyle(fontSize: 15),
));
} else {
for (var j in jd.jidla) { for (var j in jd.jidla) {
obsah.add( obsah.add(
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: InkWell(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(j.varianta), Text(j.varianta),
const SizedBox(width: 10), const SizedBox(width: 10),
@ -38,24 +68,97 @@ class _JidelnicekPageState extends State<JidelnicekPage> {
j.nazev, j.nazev,
), ),
), ),
Text("${j.cena}"), Text((j.naBurze) ? "V BURZE" : "${j.cena}"),
Checkbox( Checkbox(
value: j.objednano, value: j.objednano,
fillColor: (j.lzeObjednat) fillColor: (j.lzeObjednat)
? MaterialStateProperty.all(Colors.blue) ? MaterialStateProperty.all(Colors.blue)
: MaterialStateProperty.all(Colors.grey), : MaterialStateProperty.all(Colors.grey),
onChanged: (v) async { onChanged: (v) async {
return;
})
],
),
onTap: () async {
if (!j.lzeObjednat) return; if (!j.lzeObjednat) return;
widget.canteen widget.canteen
.objednat(j) .objednat(j)
.then((value) => nactiJidlo); .then((value) => nactiJidlo())
}) .catchError((o) {
showDialog(
context: context,
builder: (bc) => AlertDialog(
title: const Text(
"Nepodařilo se vložit jídlo na burzu"),
content: Text(o.toString()),
actions: [
TextButton(
child: const Text("Zavřít"),
onPressed: () {
Navigator.pop(bc);
},
)
], ],
));
});
},
onLongPress: () async {
if (!j.objednano) return;
if (!j.naBurze) {
// pokud není na burze, radši se zeptáme
var d = await showDialog(
context: context,
builder: (bc) => SimpleDialog(
title: const Text(
"Opravdu chcete vložit jídlo na burzu?"),
children: [
SimpleDialogOption(
onPressed: () {
Navigator.pop(bc, true);
},
child: const Text("Ano"),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(bc, false);
},
child: const Text("Ne"),
),
],
));
if (d) {
widget.canteen
.doBurzy(j)
.then((_) => nactiJidlo())
.catchError((o) {
showDialog(
context: context,
builder: (bc) => AlertDialog(
title: const Text(
"Nepodařilo se vložit jídlo na burzu"),
content: Text(o.toString()),
actions: [
TextButton(
child: const Text("Zavřít"),
onPressed: () {
Navigator.pop(bc);
},
)
],
));
});
}
} else {
// jinak ne
widget.canteen.doBurzy(j).then((_) => nactiJidlo());
}
},
), ),
), ),
); );
} }
}
}); });
}); });
}).catchError((o) { }).catchError((o) {
@ -69,28 +172,6 @@ class _JidelnicekPageState extends State<JidelnicekPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
switch (den.weekday) {
case 2:
denTydne = "Úterý";
break;
case 3:
denTydne = "Středa";
break;
case 4:
denTydne = "Čtvrtek";
break;
case 5:
denTydne = "Pátek";
break;
case 6:
denTydne = "Sobota";
break;
case 7:
denTydne = "Neděle";
break;
default:
denTydne = "Pondělí";
}
nactiJidlo(); nactiJidlo();
} }
@ -105,47 +186,60 @@ class _JidelnicekPageState extends State<JidelnicekPage> {
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 10), const SizedBox(height: 10),
Text("Kredit: $kredit"), Text("Kredit: $kredit"),
TextButton( Row(mainAxisAlignment: MainAxisAlignment.center, children: [
onPressed: () async { IconButton(
var datePicked = await DatePicker.showSimpleDatePicker( onPressed: () {
context,
initialDate: den,
dateFormat: "dd-MMMM-yyyy",
locale: DateTimePickerLocale.en_us,
looping: true,
);
if (datePicked == null) return;
setState(() { setState(() {
den = datePicked; den = den.subtract(const Duration(days: 1));
switch (den.weekday) {
case 2:
denTydne = "Úterý";
break;
case 3:
denTydne = "Středa";
break;
case 4:
denTydne = "Čtvrtek";
break;
case 5:
denTydne = "Pátek";
break;
case 6:
denTydne = "Sobota";
break;
case 7:
denTydne = "Neděle";
break;
default:
denTydne = "Pondělí";
}
nactiJidlo(); nactiJidlo();
}); });
}, },
child: icon: const Icon(Icons.arrow_left)),
Text("${den.day}. ${den.month}. ${den.year} - $denTydne")), TextButton(
...obsah onPressed: () async {
var datePicked = await showDatePicker(
context: context,
initialDate: den,
currentDate: den,
firstDate: DateTime(2019, 1, 1),
lastDate: DateTime(den.year + 1, 12, 31),
locale: const Locale("cs"));
if (datePicked == null) return;
setState(() {
den = datePicked;
nactiJidlo();
});
},
child: Text(
"${den.day}. ${den.month}. ${den.year} - $denTydne")),
IconButton(
onPressed: () {
setState(() {
den = den.add(const Duration(days: 1));
nactiJidlo();
});
},
icon: const Icon(Icons.arrow_right)),
]),
SingleChildScrollView(
child: GestureDetector(
child: Column(children: obsah),
onHorizontalDragEnd: (details) {
if (details.primaryVelocity?.compareTo(0) == -1) {
setState(() {
den = den.subtract(const Duration(days: 1));
nactiJidlo();
});
} else {
setState(() {
den = den.add(const Duration(days: 1));
nactiJidlo();
});
}
},
),
)
], ],
), ),
), ),

View file

@ -1,5 +1,6 @@
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:opencanteen/okna/burza.dart';
import 'okna/home.dart'; import 'okna/home.dart';
import 'okna/jidelnicek.dart'; import 'okna/jidelnicek.dart';
@ -18,6 +19,7 @@ Drawer drawerGenerator(
), ),
ListTile( ListTile(
selected: true, selected: true,
title: const Text("Domů"),
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
), ),
@ -32,6 +34,16 @@ Drawer drawerGenerator(
), ),
), ),
), ),
ListTile(
leading: const Icon(Icons.store),
title: const Text('Burza'),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BurzaPage(canteen: canteen, user: user),
),
),
),
], ],
), ),
); );
@ -46,6 +58,7 @@ Drawer drawerGenerator(
child: Text("OpenCanteen"), child: Text("OpenCanteen"),
), ),
ListTile( ListTile(
title: const Text("Domů"),
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
@ -58,10 +71,55 @@ Drawer drawerGenerator(
title: const Text('Jídelníček'), title: const Text('Jídelníček'),
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
), ),
ListTile(
leading: const Icon(Icons.store),
title: const Text('Burza'),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => BurzaPage(canteen: canteen, user: user),
),
),
),
], ],
), ),
); );
break; break;
case 3:
drawer = Drawer(
child: ListView(
children: [
const DrawerHeader(
child: Text("OpenCanteen"),
),
ListTile(
leading: const Icon(Icons.home),
title: const Text("Domů"),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (c) => HomePage(canteen: canteen, user: user))),
),
ListTile(
leading: const Icon(Icons.restaurant),
title: const Text('Jídelníček'),
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
JidelnicekPage(canteen: canteen, user: user),
),
),
),
ListTile(
leading: const Icon(Icons.store),
selected: true,
title: const Text('Burza'),
onTap: () => Navigator.pop(context),
),
],
),
);
} }
return drawer; return drawer;
} }

View file

@ -22,20 +22,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.8.2" version: "2.8.2"
auto_size_text:
dependency: transitive
description:
name: auto_size_text
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
canteenlib: canteenlib:
dependency: "direct main" dependency: "direct main"
description: description:
name: canteenlib name: canteenlib
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.1.0-alpha.7" version: "0.1.0-alpha.13"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -50,6 +43,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
clock:
dependency: transitive
description:
name: clock
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -132,13 +132,6 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_holo_date_picker:
dependency: "direct main"
description:
name: flutter_holo_date_picker
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
flutter_launcher_icons: flutter_launcher_icons:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -153,6 +146,11 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_secure_storage: flutter_secure_storage:
dependency: "direct main" dependency: "direct main"
description: description:
@ -221,6 +219,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.3" version: "3.1.3"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
js: js:
dependency: transitive dependency: transitive
description: description:

View file

@ -6,7 +6,7 @@ publish_to: 'none'
# The following defines the version and build number for your application. # The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43 # A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +. # followed by an optional build number separated by a +.
version: 1.0.0+1 version: 0.1.0+1
environment: environment:
sdk: ">=2.16.1 <3.0.0" sdk: ">=2.16.1 <3.0.0"
@ -14,11 +14,12 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
canteenlib: ^0.1.0-alpha.7 flutter_localizations:
sdk: flutter
canteenlib: ^0.1.0-alpha.13
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
connectivity_plus: ^2.2.1 connectivity_plus: ^2.2.1
flutter_secure_storage: ^5.0.2 flutter_secure_storage: ^5.0.2
flutter_holo_date_picker: ^1.0.3
dev_dependencies: dev_dependencies:
flutter_lints: ^1.0.0 flutter_lints: ^1.0.0

BIN
screenshots/01.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
screenshots/02.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

BIN
screenshots/03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

BIN
screenshots/04.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB