Compare commits

..

1 commit

Author SHA1 Message Date
Matyáš Caras
02078987c6 feat: práce na implementaci výběru více účtů 2022-09-12 16:24:06 +02:00
103 changed files with 3616 additions and 2599 deletions

View file

@ -0,0 +1,5 @@
{
"name": "Flutter",
"image": "matspfeiffer/flutter:beta",
"extensions": ["dart-code.dart-code", "dart-code.flutter"]
}

@ -1 +0,0 @@
Subproject commit 7f20e5d18ce4cb80c621533090a7c5113f5bdc52

13
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1,13 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
patreon: # Replace with a single Patreon username
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: hernikplays
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']

View file

@ -0,0 +1,28 @@
---
name: Chybové hlášení
about: Použijte tuto předlohu, pokud se něco rozbilo
title: ''
labels: bug
assignees: hernikplays
---
**Popis chyby**
Zde popište co se stalo, mělo stát apod.
**Kroky pro replikaci**
Kroky ke spuštění chyby:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Snímky obrazovky**
Chcete-li, přiložte snímky
**Info o zařízení**
- Verze systému zařízení: [např. Android 12, iOS 9]
- Verze aplikace [např. 0.1.0]
**Ostatní**
Sem vepište doplňující informace

View file

@ -0,0 +1,11 @@
---
name: Nápad na vylepšení
about: Navrhněte co změnit nebo přidat do aplikace
title: ''
labels: vylepšení
assignees: hernikplays
---
**Souvisí váš navrh s nějakým problémem?**
Popište váš problém a co čekáte za řešení.

8
.github/labeler.yml vendored Normal file
View file

@ -0,0 +1,8 @@
'pr: pubspec':
- pubspec.yaml
'pr: funkce':
- lib/*
'pr: okno':
- lib/okna/*

14
.github/pull_request_template.md vendored Normal file
View file

@ -0,0 +1,14 @@
Opravuje/implementuje # .
Změny:
- 1
- 2
```
Text výše slouží jen jako předloha, není nutné se ho držet, ale šetříte ostatním čas
```
- [ ] Otestoval jsem funkčnost celé aplikace po přidání svého kódu
- [ ] Přidaný kód nebo knihovny neobsahují žádný kód pod nekompatibilní licencí
Doplňující/ostatní informace:

30
.github/workflows/main.yml vendored Normal file
View file

@ -0,0 +1,30 @@
# This is a basic workflow to help you get started with Actions
name: Analyze
# Controls when the workflow will run
on:
# Triggers the workflow on push or pull request events but only for the main branch
push:
branches: [ main ]
pull_request:
branches: [ main ]
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
fossa-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: fossas/fossa-action@main
with:
api-key: ${{secrets.fossaApiKey}}
analyze:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- run: flutter pub get
- run: flutter analyze

33
.github/workflows/pr.yml vendored Normal file
View file

@ -0,0 +1,33 @@
name: "Lint PR"
on:
pull_request_target:
types:
- opened
- edited
- synchronize
jobs:
main:
name: Validate PR
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: amannn/action-semantic-pull-request@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- uses: wagoid/commitlint-github-action@v4
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
branch:
name: Uzavřít PRy mimo dev větev
needs: main
if: github.base_ref == 'main' && github.actor != 'hernikplays'
runs-on: ubuntu-latest
steps:
- uses: superbrothers/close-pull-request@v3
with:
comment: "Všechny pull requesty by měly směřovat na jinou větev, než `main`. Pokud je tohle chyba, napište správci repozitáře."

4
.gitmodules vendored
View file

@ -1,4 +0,0 @@
[submodule ".flutter"]
path = .flutter
url = https://github.com/flutter/flutter.git
branch = stable

4
.husky/commit-msg Executable file
View file

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no -- commitlint --edit "${1}"

4
.husky/pre-commit Executable file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
flutter analyze

View file

@ -1,45 +1,10 @@
# This file tracks properties of this Flutter project. # This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc. # Used by Flutter tool to assess capabilities and perform upgrades etc.
# #
# This file should be version controlled. # This file should be version controlled and should not be manually edited.
version: version:
revision: 52b3dc25f6471c27b2144594abb11c741cb88f57 revision: 097d3313d8e2c7f901932d63e537c1acefb87800
channel: stable channel: stable
project_type: app project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: android
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: ios
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: linux
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: macos
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: web
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
- platform: windows
create_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
base_revision: 52b3dc25f6471c27b2144594abb11c741cb88f57
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -1,7 +0,0 @@
{
"dart.flutterSdkPath": ".flutter",
"conventionalCommits.scopes": [
"dependencies",
"nastaveni"
],
}

View file

@ -1,52 +1,3 @@
# 1.10.0
- Aktualizovat závislosti
- Změnit minSdk na 21
- Přidat možnost zobrazit alergeny
- Změnit fungování oznámení s aktualizací knihovny
- Žádost o oprávnění k posílání oznámení by se mělo posílat už jen v případě, že uživatel bude chtít používat funkci oznámení
# 1.9.1
- Opravit chybu s propisováním HTML do názvu obědů
# 1.9.0
- Opravit vzhled na iOS
- Opravit chybu s burzou (update canteenlib)
# 1.8.1
- aktualizace závislostí
# 1.8.0
- aktualizace závislostí
- předělání nastavení
- změna nastavení nyní nevyžaduje restart aplikace
# 1.7.0
- Implementovat Material 3 (Android)
- Upravit chování dle platformy
- Předělat jazykový systém na ARB
- Aktualizovat flutter_secure_storage
# 1.6.1
- opravit chybu s přidáváním do burzy aktualizací knihovny
# 1.6.0
- rozdělit iOS a Android UI zvlášť pro možnost využití Cupertino knihovny
- opravit chybu s FlutterLocalNotifications na iOS
- upravit vzhled
# 1.5.1
- aktualizovat knihovnu canteenlib
- přidat podporu pro splitování APK podle ABI
# 1.5.0
- umožnit ukládat více dnů offline
- chyba při ukládání offline vás nyní již nevyhodí ale zobrazí pouze zprávu
- "Přihlašování" pop-up zmizí, když není přihlášení úspěšné
# 1.4.2
- aktualizace knihovny flutter_local_notifications
- lepší podpora pro Android 13
- změna na adaptivní ikony na Androidu
# 1.4.1
- aktualizovat knihovnu canteenlib
- změnit odkaz na odeslání zpětné vazby
- přidat odkaz na hodnocení v obchodu s aplikacemi
- opravit chybu s vytvářením notifikace
# 1.4.0
- Opravit chybu, kdy po stisknutí tlačítka zpět na hlavní stránce byl uživatel vrácen na přihlašovací obrazovku
- Přidat výběr z instancí (aktuálně pouze SŠTE Olomoucká)
- Vylepšit oznámení o neobjednaném jídle
- Nahradit info stránku info dialogem
# 1.3.1 # 1.3.1
- Odstranit zbytečné podmínky - Odstranit zbytečné podmínky
- Přidat oznámení o optimalizaci baterie - Přidat oznámení o optimalizaci baterie

View file

@ -2,16 +2,6 @@
OpenCanteen je **aplikace** pro přístup do iCanteen. Pokud chcete přispět kód související s komunikací s iCanteen, podívejte se na [canteenlib](https://github.com/hernikplays/canteenlib). OpenCanteen je **aplikace** pro přístup do iCanteen. Pokud chcete přispět kód související s komunikací s iCanteen, podívejte se na [canteenlib](https://github.com/hernikplays/canteenlib).
## Jak přispět do vývoje ## Jak přispět do vývoje
### Používám GitHub pod protestem
Tento projekt je aktuálně hostován i na GitHubu. To není ideální; GitHub je uzavřený systém plný obchodních tajemství. Doporučuji přečíst si o kampani
[Give up GitHub](https://GiveUpGitHub.org) od
[Software Freedom Conservancy](https://sfconservancy.org) pro pochopení proč GitHub není ideální místo pro FOSS projekty.
**Příspěvky do kódu přijímám **pouze** skrz mou Forgejo instanci na adrese https://git.mnau.xyz/hernik/opencanteen**
Jakékoli použití kódu pro učení umělých inteligencí, které neumožňuje samotná licence GNU GPL 3.0 či novější, je děláno bez mého souhlasu a vědomí.
### Nahlašování chyb ### Nahlašování chyb
Prosté vyhledání a nahlášení chyby je asi nejjednodušší a zároveň nejpřínosnější způsob přispívání. Stačí vám běžné zařízení a stažená aplikace, pokud objevíte jakoukoliv chybu nebo nesrovnalost, nahlaste ji v [Issues](https://github.com/hernikplays/opencanteen/issues/new/choose). Prosté vyhledání a nahlášení chyby je asi nejjednodušší a zároveň nejpřínosnější způsob přispívání. Stačí vám běžné zařízení a stažená aplikace, pokud objevíte jakoukoliv chybu nebo nesrovnalost, nahlaste ji v [Issues](https://github.com/hernikplays/opencanteen/issues/new/choose).

View file

@ -24,7 +24,7 @@ Některé informace se mohou vztahovat pro oficiálně nevydané funkce.
### Kontakt ### Kontakt
Preferovaná forma komunikace je skrze Diskuze nebo Issues. Preferovaná forma komunikace je skrze Diskuze nebo Issues.
V případech nejvyšší nutnosti je můj e-mail `matyas zavináč caras.cafe` V případech nejvyšší nutnosti je můj e-mail `contact zavináč hernikplays.cz`
## Terms of use and privacy policy of OpenCanteen ## Terms of use and privacy policy of OpenCanteen
In effect from 15. 5. 2022 In effect from 15. 5. 2022
@ -51,4 +51,4 @@ Some information may refer to unreleased functions of the app.
### Contact ### Contact
Prefered way of communication is through the Issues system Prefered way of communication is through the Issues system
If it is important, my e-mail address is `matyas AT caras.cafe` If it is important, my e-mail address is `contact AT hernikplays.cz`

View file

@ -2,12 +2,15 @@
Open-Source **neoficiální** aplikace pro přístup do iCanteen Open-Source **neoficiální** aplikace pro přístup do iCanteen
[![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b) [![wakatime](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b.svg)](https://wakatime.com/badge/user/17178fab-a33c-430f-a764-7b3f26c7b966/project/e3ff9994-0026-4041-a529-1cb2041bdf4b)
[![Codemagic build status](https://api.codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/status_badge.svg)](https://codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/latest_build) [![Commit Style: Conventional Commits](https://img.shields.io/badge/commit%20style-conventional%20commits-pink)](https://www.conventionalcommits.org/en/v1.0.0/) [![FOSSA Status](https://app.fossa.com/api/projects/git%2Bgithub.com%2Fhernikplays%2Fopencanteen.svg?type=shield)](https://app.fossa.com/projects/git%2Bgithub.com%2Fhernikplays%2Fopencanteen?ref=badge_shield) [![Codemagic build status](https://api.codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/status_badge.svg)](https://codemagic.io/apps/62863e4c96304ce0518a1694/62863e4c96304ce0518a1693/latest_build) [![Commit Style: Conventional Commits](https://img.shields.io/badge/commit%20style-conventional%20commits-pink)](https://www.conventionalcommits.org/en/v1.0.0/)
### Upstream repozitář je nyní na adrese [https://git.mnau.xyz/hernik/opencanteen](https://git.mnau.xyz/hernik/opencanteen), všechny problémy hlaste tam.
## Co umí ## Co umí
Aplikace vás přihlásí do vaší iCanteen ***pokud ji podporuje [canteenlib](https://github.com/hernikplays/canteenlib/blob/main/COMPATIBILITY.md), jinak experimentálně***, a umožní vám zobrazit, objednávat obědy, objednávat nebo přidávat jídlo na burzu. Aplikace vás přihlásí do vaší iCanteen ***pokud ji podporuje [canteenlib](https://github.com/hernikplays/canteenlib/blob/main/COMPATIBILITY.md), jinak experimentálně***, a umožní vám zobrazit, objednávat obědy, objednávat nebo přidávat jídlo na burzu<sup>beta</sup>.
## Co se plánuje
- Offline ukládání jídelníčku a zakoupených jídel
- Burza
- Sledovač burzy
## Jak používat ## Jak používat
Jednoduchý návod k použití naleznete [zde](https://github.com/hernikplays/opencanteen/wiki/Pou%C5%BE%C3%ADv%C3%A1n%C3%AD-aplikace) Jednoduchý návod k použití naleznete [zde](https://github.com/hernikplays/opencanteen/wiki/Pou%C5%BE%C3%ADv%C3%A1n%C3%AD-aplikace)
@ -22,28 +25,5 @@ Jednoduchý návod k použití naleznete [zde](https://github.com/hernikplays/op
<a href="https://play.google.com/apps/testing/cz.hernikplays.opencanteen" target="_blank">Google Play</a> <a href="https://play.google.com/apps/testing/cz.hernikplays.opencanteen" target="_blank">Google Play</a>
</div> </div>
## Statistiky ## License
<div align="center"> [![FOSSA Status](https://app.fossa.com/api/projects/custom%2B12838%2Fgithub.com%2Fhernikplays%2Fopencanteen.svg?type=large)](https://app.fossa.com/projects/custom%2B12838%2Fgithub.com%2Fhernikplays%2Fopencanteen?ref=badge_large)
<img src="https://repobeats.axiom.co/api/embed/ce91f6018ce5523dc8d655df771f4f504c2c6664.svg" alt="repobeats stats">
</div>
## Přispět
Přispět na vývoj můžete skrz
- Monero: `49uzFSxAsT92sXUsCEgh11VX7pmNZcoeKi4fEWbLGc2oWGMUVNcDDqGVB97ak96LqEBqsMXLS752bjgyVzbFcVwLNke4pNd`
- Bitcoin: `3NzqkBZgWgjj1NYj2JwmBBptPWw6wt7dHS`
- Kód: viz CONTRIBUTING
## Používám GitHub pod protestem
Tento projekt je aktuálně hostován i na GitHubu. To není ideální; GitHub je uzavřený systém plný obchodních tajemství. Doporučuji přečíst si o kampani
[Give up GitHub](https://GiveUpGitHub.org) od
[Software Freedom Conservancy](https://sfconservancy.org) pro pochopení proč GitHub není ideální místo pro FOSS projekty.
Příspěvky do kódu přijímám **pouze** skrz mou Forgejo instanci na adrese https://git.mnau.xyz/hernik/opencanteen
Jakékoli použití kódu pro učení umělých inteligencí, které neumožňuje samotná licence GNU GPL 3.0 či novější, je děláno bez mého souhlasu a vědomí.
## Licence
**Copyright (c) 2022 Matyáš Caras a přispěvatelé**
Tento program je svobodný software. Kde není uvedeno jinak je tento program a zdrojový kód šířen pod licencí GNU General Public License verze 3 nebo pozdější. Všechny detaily licence najdete [zde](https://github.com/hernikplays/opencanteen/blob/main/LICENSE)

View file

@ -31,12 +31,9 @@ apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
} }
android { android {
compileSdkVersion 33 compileSdkVersion flutter.compileSdkVersion
compileOptions { compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
// Sets Java compatibility to Java 8
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
} }
@ -51,8 +48,8 @@ android {
defaultConfig { defaultConfig {
applicationId "cz.hernikplays.opencanteen" applicationId "cz.hernikplays.opencanteen"
minSdkVersion 21 minSdkVersion 18
targetSdkVersion 33 targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
multiDexEnabled true multiDexEnabled true
@ -81,16 +78,4 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3' implementation 'com.android.support:multidex:1.0.3'
implementation 'androidx.appcompat:appcompat:1.3.1' implementation 'androidx.appcompat:appcompat:1.3.1'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
}
ext.abiCodes = ["x86_64": 1, "armeabi-v7a": 2, "arm64-v8a": 3]
import com.android.build.OutputFile
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def abiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI))
if (abiVersionCode != null) {
output.versionCodeOverride = variant.versionCode * 10 + abiVersionCode
}
}
} }

View file

@ -1,11 +1,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cz.hernikplays.opencanteen"> package="cz.hernikplays.opencanteen">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
<application <application
android:label="OpenCanteen" android:label="OpenCanteen"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/launcher_icon"
android:allowBackup="false" android:allowBackup="false"
android:fullBackupContent="false"> android:fullBackupContent="false">
<activity <activity

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View file

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#4f4685</color>
</resources>

View file

@ -1,12 +1,12 @@
buildscript { buildscript {
ext.kotlin_version = '1.9.10' ext.kotlin_version = '1.6.10'
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:7.1.2' classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
} }
} }
@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
tasks.register("clean", Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

View file

@ -1,5 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

1
commitlint.config.js Normal file
View file

@ -0,0 +1 @@
module.exports = {extends: ['@commitlint/config-conventional']}

113
flutterw
View file

@ -1,113 +0,0 @@
#!/usr/bin/env sh
##############################################################################
##
## Flutter start up script for UN*X
## Version: v1.3.1
## Date: 2022-11-21 17:25:38
##
## Use this flutter wrapper to bundle Flutter within your project to make
## sure everybody builds with the same version.
##
## Read about the install and uninstall process in the README on GitHub
## https://github.com/passsy/flutter_wrapper
##
## Inspired by gradle-wrapper.
##
##############################################################################
echoerr() { echo "$@" 1>&2; }
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ]; do
ls=$(ls -ld "$PRG")
link=$(expr "$ls" : '.*-> \(.*\)$')
if expr "$link" : '/.*' >/dev/null; then
PRG="$link"
else
PRG=$(dirname "$PRG")"/$link"
fi
done
SAVED="$(pwd)"
cd "$(dirname "$PRG")/" >/dev/null
APP_HOME="$(pwd -P)"
cd "$SAVED" >/dev/null
FLUTTER_SUBMODULE_NAME='.flutter'
GIT_HOME=$(git -C "${APP_HOME}" rev-parse --show-toplevel)
FLUTTER_DIR="${GIT_HOME}/${FLUTTER_SUBMODULE_NAME}"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
# Fix not initialized flutter submodule
if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then
echoerr "$FLUTTER_SUBMODULE_NAME submodule not initialized. Initializing..."
git submodule update --init "${FLUTTER_DIR}"
fi
# Detect detach HEAD and fix it. commands like upgrade expect a valid branch, not a detached HEAD
FLUTTER_SYMBOLIC_REF=$(git -C "${FLUTTER_DIR}" symbolic-ref -q HEAD)
if [ -z "${FLUTTER_SYMBOLIC_REF}" ]; then
FLUTTER_REV=$(git -C "${FLUTTER_DIR}" rev-parse HEAD)
FLUTTER_CHANNEL=$(git -C "${GIT_HOME}" config -f .gitmodules submodule.${FLUTTER_SUBMODULE_NAME}.branch)
if [ -z "${FLUTTER_CHANNEL}" ]; then
echoerr "Warning: Submodule '$FLUTTER_SUBMODULE_NAME' doesn't point to an official Flutter channel \
(one of stable|beta|dev|master). './flutterw upgrade' will fail without a channel."
echoerr "Fix this by adding a specific channel with:"
echoerr " - './flutterw channel <channel>' or"
echoerr " - Add 'branch = <channel>' to '$FLUTTER_SUBMODULE_NAME' submodule in .gitmodules"
else
echoerr "Fixing detached HEAD: '$FLUTTER_SUBMODULE_NAME' submodule points to a specific commit $FLUTTER_REV, not channel '$FLUTTER_CHANNEL' (as defined in .gitmodules)."
# Make sure channel is fetched
# Remove old channel branch because it might be moved to an unrelated commit where fast-forward pull isn't possible
git -C "${FLUTTER_DIR}" branch -q -D "${FLUTTER_CHANNEL}" 2> /dev/null || true
git -C "${FLUTTER_DIR}" fetch -q origin
# bind current HEAD to channel defined in .gitmodules
git -C "${FLUTTER_DIR}" checkout -q -b "${FLUTTER_CHANNEL}" "${FLUTTER_REV}"
git -C "${FLUTTER_DIR}" branch -q -u "origin/${FLUTTER_CHANNEL}" "${FLUTTER_CHANNEL}"
echoerr "Fixed! Migrated to channel '$FLUTTER_CHANNEL' while staying at commit $FLUTTER_REV. './flutterw upgrade' now works without problems!"
git -C "${FLUTTER_DIR}" status -bs
fi
fi
# Wrapper tasks done, call flutter binary with all args
set -e
"$FLUTTER_DIR/bin/flutter" "$@"
set +e
# Post flutterw tasks. exit code from /bin/flutterw will be used as final exit
FLUTTER_EXIT_STATUS=$?
if [ ${FLUTTER_EXIT_STATUS} -eq 0 ]; then
# ./flutterw channel CHANNEL
if echo "$@" | grep -q "channel"; then
if [ -n "$2" ]; then
# make sure .gitmodules is updated as well
CHANNEL=${2} # second arg
git config -f "${GIT_HOME}/.gitmodules" "submodule.${FLUTTER_SUBMODULE_NAME}.branch" "${CHANNEL}"
# makes sure nobody forgets to do commit all changed files
git add "${GIT_HOME}/.gitmodules"
git add "${FLUTTER_DIR}"
fi
fi
# ./flutterw upgrade
if echo "$@" | grep -q "upgrade"; then
# makes sure nobody forgets to do commit the changed submodule
git add "${FLUTTER_DIR}"
# flutter packages get runs automatically. Stage those changes as well
if [ -f pubspec.lock ]; then
git add pubspec.lock
fi
fi
fi
exit ${FLUTTER_EXIT_STATUS}

View file

@ -21,6 +21,6 @@
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>1.0</string> <string>1.0</string>
<key>MinimumOSVersion</key> <key>MinimumOSVersion</key>
<string>11.0</string> <string>9.0</string>
</dict> </dict>
</plist> </plist>

View file

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # Uncomment this line to define a global platform for your project
# platform :ios, '11.0' # platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View file

@ -1,79 +1,53 @@
PODS: PODS:
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
- Flutter (1.0.0) - Flutter (1.0.0)
- flutter_keyboard_visibility (0.0.1): - flutter_secure_storage (3.3.1):
- Flutter - Flutter
- flutter_local_notifications (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- flutter_native_timezone (0.0.1): - ReachabilitySwift (5.0.0)
- shared_preferences_ios (0.0.1):
- Flutter - Flutter
- flutter_secure_storage (6.0.0):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- Toast
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- Toast (4.0.0)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_native_timezone (from `.symlinks/plugins/flutter_native_timezone/ios`)
- flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - shared_preferences_ios (from `.symlinks/plugins/shared_preferences_ios/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- Toast - ReachabilitySwift
EXTERNAL SOURCES: EXTERNAL SOURCES:
connectivity_plus:
:path: ".symlinks/plugins/connectivity_plus/ios"
Flutter: Flutter:
:path: Flutter :path: Flutter
flutter_keyboard_visibility:
:path: ".symlinks/plugins/flutter_keyboard_visibility/ios"
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_native_timezone:
:path: ".symlinks/plugins/flutter_native_timezone/ios"
flutter_secure_storage: flutter_secure_storage:
:path: ".symlinks/plugins/flutter_secure_storage/ios" :path: ".symlinks/plugins/flutter_secure_storage/ios"
fluttertoast: path_provider_ios:
:path: ".symlinks/plugins/fluttertoast/ios" :path: ".symlinks/plugins/path_provider_ios/ios"
package_info_plus: shared_preferences_ios:
:path: ".symlinks/plugins/package_info_plus/ios" :path: ".symlinks/plugins/shared_preferences_ios/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 connectivity_plus: 413a8857dd5d9f1c399a39130850d02fe0feaf7e
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069 Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_local_notifications: 0c0b1ae97e741e1521e4c1629a459d04b9aec743 flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
flutter_native_timezone: 5f05b2de06c9776b4cc70e1839f03de178394d22 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
fluttertoast: fafc4fa4d01a6a9e4f772ecd190ffa525e9e2d9c shared_preferences_ios: 548a61f8053b9b8a49ac19c1ffbc8b92c50d68ad
package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943
shared_preferences_foundation: 5b919d13b803cadd15ed2dc053125c68730e5126
Toast: 91b396c56ee72a5790816f40d3a94dd357abc196
url_launcher_ios: 08a3dfac5fb39e8759aeb0abbd5d9480f30fc8b4
PODFILE CHECKSUM: ef19549a9bc3046e7bb7d2fab4d021637c0c58a3 PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
COCOAPODS: 1.12.1 COCOAPODS: 1.11.3

View file

@ -3,7 +3,7 @@
archiveVersion = 1; archiveVersion = 1;
classes = { classes = {
}; };
objectVersion = 54; objectVersion = 50;
objects = { objects = {
/* Begin PBXBuildFile section */ /* Begin PBXBuildFile section */
@ -156,7 +156,7 @@
97C146E61CF9000F007C117D /* Project object */ = { 97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 1430; LastUpgradeCheck = 1300;
ORGANIZATIONNAME = ""; ORGANIZATIONNAME = "";
TargetAttributes = { TargetAttributes = {
97C146ED1CF9000F007C117D = { 97C146ED1CF9000F007C117D = {
@ -222,12 +222,10 @@
}; };
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
inputPaths = ( inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
); );
name = "Thin Binary"; name = "Thin Binary";
outputPaths = ( outputPaths = (
@ -238,7 +236,6 @@
}; };
9740EEB61CF901F6004384FC /* Run Script */ = { 9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase; isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
); );
@ -343,7 +340,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;
@ -420,7 +417,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@ -469,7 +466,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 11.0; IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos; SUPPORTED_PLATFORMS = iphoneos;

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1430" LastUpgradeVersion = "1300"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"

View file

@ -1,6 +1,5 @@
import UIKit import UIKit
import Flutter import Flutter
import flutter_local_notifications
@UIApplicationMain @UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate { @objc class AppDelegate: FlutterAppDelegate {
@ -8,14 +7,6 @@ import flutter_local_notifications
_ application: UIApplication, _ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool { ) -> Bool {
// This is required to make any communication available in the action isolate.
FlutterLocalNotificationsPlugin.setPluginRegistrantCallback { (registry) in
GeneratedPluginRegistrant.register(with: registry)
}
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
}
GeneratedPluginRegistrant.register(with: self) GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions) return super.application(application, didFinishLaunchingWithOptions: launchOptions)
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

View file

@ -4,7 +4,7 @@
<dict> <dict>
<key>CFBundleLocalizations</key> <key>CFBundleLocalizations</key>
<array> <array>
<string>cs</string> <string>Czech</string>
<string>en</string> <string>en</string>
</array> </array>
<key>CFBundleDevelopmentRegion</key> <key>CFBundleDevelopmentRegion</key>
@ -50,7 +50,5 @@
<false/> <false/>
<key>CADisableMinimumFrameDurationOnPhone</key> <key>CADisableMinimumFrameDurationOnPhone</key>
<true/> <true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict> </dict>
</plist> </plist>

View file

@ -1,3 +0,0 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart

View file

@ -1,69 +0,0 @@
import 'package:flutter/material.dart';
const lightColorScheme = ColorScheme(
brightness: Brightness.light,
primary: Color(0xFF5F52A7),
onPrimary: Color(0xFFFFFFFF),
primaryContainer: Color(0xFFE5DEFF),
onPrimaryContainer: Color(0xFF1A0261),
secondary: Color(0xFF5F5C71),
onSecondary: Color(0xFFFFFFFF),
secondaryContainer: Color(0xFFE5DFF9),
onSecondaryContainer: Color(0xFF1C192B),
tertiary: Color(0xFF763EC3),
onTertiary: Color(0xFFFFFFFF),
tertiaryContainer: Color(0xFFEDDCFF),
onTertiaryContainer: Color(0xFF290056),
error: Color(0xFFBA1A1A),
errorContainer: Color(0xFFFFDAD6),
onError: Color(0xFFFFFFFF),
onErrorContainer: Color(0xFF410002),
background: Color(0xFFFFFBFF),
onBackground: Color(0xFF1C1B1F),
surface: Color(0xFFFFFBFF),
onSurface: Color(0xFF1C1B1F),
surfaceVariant: Color(0xFFE5E0EC),
onSurfaceVariant: Color(0xFF48454E),
outline: Color(0xFF79767F),
onInverseSurface: Color(0xFFF4EFF4),
inverseSurface: Color(0xFF313033),
inversePrimary: Color(0xFFC8BFFF),
shadow: Color(0xFF000000),
surfaceTint: Color(0xFF5F52A7),
outlineVariant: Color(0xFFC9C5D0),
scrim: Color(0xFF000000),
);
const darkColorScheme = ColorScheme(
brightness: Brightness.dark,
primary: Color(0xFFC8BFFF),
onPrimary: Color(0xFF302175),
primaryContainer: Color(0xFF473A8D),
onPrimaryContainer: Color(0xFFE5DEFF),
secondary: Color(0xFFC9C3DC),
onSecondary: Color(0xFF312E41),
secondaryContainer: Color(0xFF484459),
onSecondaryContainer: Color(0xFFE5DFF9),
tertiary: Color(0xFFD7BAFF),
onTertiary: Color(0xFF440088),
tertiaryContainer: Color(0xFF5D20A9),
onTertiaryContainer: Color(0xFFEDDCFF),
error: Color(0xFFFFB4AB),
errorContainer: Color(0xFF93000A),
onError: Color(0xFF690005),
onErrorContainer: Color(0xFFFFDAD6),
background: Color(0xFF1C1B1F),
onBackground: Color(0xFFE5E1E6),
surface: Color(0xFF1C1B1F),
onSurface: Color(0xFFE5E1E6),
surfaceVariant: Color(0xFF48454E),
onSurfaceVariant: Color(0xFFC9C5D0),
outline: Color(0xFF938F99),
onInverseSurface: Color(0xFF1C1B1F),
inverseSurface: Color(0xFFE5E1E6),
inversePrimary: Color(0xFF5F52A7),
shadow: Color(0xFF000000),
surfaceTint: Color(0xFFC8BFFF),
outlineVariant: Color(0xFF48454E),
scrim: Color(0xFF000000),
);

View file

@ -1,83 +0,0 @@
{
"about": "O Aplikaci",
"agree": "Souhlasím",
"appName": "OpenCanteen",
"balance": "Kredit: ",
"cannotOrder": "Toto jídlo není možné objednat.",
"close": "Zavřít",
"copyright": "© 2022 Matyáš Caras a přispěvatelé",
"disagree": "Nesouhlasím",
"errorContacting": "Nastala chyba při kontaktování serveru, zkontrolujte připojení",
"errorOrdering": "Jídlo se nepodařilo objednat",
"exchange": "Burza",
"exchangeError": "Nepodařilo se vložit jídlo na burzu",
"friday": "Pátek",
"home": "Domů",
"httpLogin": "Snažíte se přihlásit přes nešifrované spojení HTTP, jste si jisti, že tak chcete učinit?",
"iCanteenUrl": "iCanteen URL",
"inExchange": "V BURZE",
"license": "Vydáno pod licencí GNU GPLv3",
"loading": "Načítání...",
"logIn": "Přihlášení",
"loggingIn": "Přihlašuji vás...",
"loginFailed": "Přihlášení se nezdařilo",
"menu": "Jídelníček",
"monday": "Pondělí",
"mustLogout": "Online přejdete přetažením dolů.",
"no": "Ne",
"noChange": "Ne, změnit",
"noExchange": "Žádné jídlo v burze",
"noFood": "Žádné jídlo pro tento den",
"notOfficial": "Toto není oficiální aplikace k ovládání iCanteen. Autor neručí za ztráty nebo nefunkčnost v souvislosti s používáním této aplikace. Pokračováním souhlasíte.",
"offline": "JSTE OFFLINE",
"ok": "OK",
"order": "Objednat",
"orderSuccess": "Jídlo bylo úspěšně objednáno",
"ordered": "Objednáno",
"ordering": "Objednávám...",
"password": "Heslo",
"pullToReload": "Potáhněte zvrchu pro načtení",
"rememberMe": "Zapamatovat si mě",
"reportBugs": "Zpětná vazba",
"saturday": "Sobota",
"saveOffline": "Ukládat jídelníček offline",
"settings": "Nastavení",
"signOut": "Odhlásit se",
"skipWeekend": "Při procházení menu přeskočit víkend",
"sunday": "Neděle",
"thursday": "Čtvrtek",
"tuesday": "Úterý",
"username": "Uživatelské jméno",
"verifyExchange": "Opravdu chcete vložit jídlo na burzu?",
"warning": "Pozor!",
"wednesday": "Středa",
"yes": "Ano",
"aboutFromExch": "Žádné jídlo? Žádný problém!",
"aboutOrder": "Klepnutím objednáte",
"aboutToExch": "Nemáte chuť?",
"appDesc": "OpenCanteen je neoficiální aplikace pro přístup do obědového systému iCanteen",
"howFromExch": "Z vysunovacího menu přejděte do burzy a objednejte si z dostupných jídel",
"howOrder": "Jednoduše klepněte na jídlo s modrým políčkem a máte objednáno",
"howToExch": "Stačí dlouze podržet jméno objednaného jídla a můžete ho přesunout na nebo z burzy",
"welcome": "Vítejte v OpenCanteen",
"next": "Další",
"checkOrdered": "Kontrolovat, jestli mám objednáno na příští týden",
"noOrder": "Na přístí týden nemáte objednané žádné jídlo!",
"corrupted": "Nastal problém s dešifrováním uložených údajů, prosím zkuste vyčistit veškerá data této aplikace.",
"notifyAt": "Odeslat v",
"notifyLunch": "V určený čas odeslat notifikaci s informacemi o obědě",
"lunchNotif": "Dnes máte objednáno",
"error": "Chyba",
"needRemember": "Musíte své přihlašovací údaje uložit na přihlašovací obrazovce",
"notifyWarning": "Vaše zařízení může mít povolenou optimalizaci baterie, což může způsobovat neodesílání oznámení. Zkontrolujte nastavení ve vašem zařízení.",
"signOutWarn": "Opravdu se chcete odhlásit?",
"jump": "Přeskočit",
"source": "Zdrojový kód",
"review": "Ohodnotit aplikaci",
"saveCount": "Počet dnů dostupných offline (Akt. limit je 7)",
"errorSaving": "Při ukládání offline nastala chyba, zkuste to znovu později.",
"todayTooltip": "Přejít na dnešní jídelníček",
"settingsExperience":"Zážitek",
"settingsFunctions":"Extra funkce",
"showAllergens":"Zobrazovat alergeny, jsou-li dostupné"
}

View file

@ -1,83 +0,0 @@
{
"about": "About",
"agree": "I agree",
"appName": "OpenCanteen",
"balance": "Balance: ",
"cannotOrder": "This food cannot be ordered.",
"close": "Close",
"copyright": "© 2022 Matyáš Caras and contributors",
"disagree": "I disagree",
"errorContacting": "Failed to contact the server, check your connection.",
"errorOrdering": "Could not order food.",
"exchange": "Exchange",
"exchangeError": "Could not put food on exchange.",
"friday": "Friday",
"home": "Home",
"httpLogin": "You are trying to sign in using an insecure HTTP connection, are you sure you want to continue?",
"iCanteenUrl": "iCanteen URL",
"inExchange": "ON EXCHANGE",
"license": "Released under the GNU GPLv3",
"loading": "Loading...",
"logIn": "Sign in",
"loggingIn": "Signing you in...",
"loginFailed": "Sign in failed",
"menu": "Food Menu",
"monday": "Monday",
"mustLogout": "To go online, pull down.",
"no": "No",
"noChange": "No, change",
"noExchange": "No meal in exchange",
"noFood": "No meal for this day",
"notOfficial": "This is not an official app for accessing iCanteen. The author is not responsible for non-functionality or losses while using this app. By continuing you agree.",
"offline": "YOU ARE OFFLINE",
"ok": "OK",
"order": "Order",
"orderSuccess": "Meal ordered succesfully",
"ordered": "Ordered",
"ordering": "Ordering...",
"password": "Password",
"pullToReload": "Pull to reload",
"rememberMe": "Remember me",
"reportBugs": "Feedback",
"saturday": "Saturday",
"saveOffline": "Save menu offline",
"settings": "Settings",
"signOut": "Sign out",
"skipWeekend": "Skip weekends when browsing menu",
"sunday": "Sunday",
"thursday": "Thursday",
"tuesday": "Tuesday",
"username": "Username",
"verifyExchange": "Are you sure you want to put this meal on exchange?",
"warning": "Warning!",
"wednesday": "Wednesday",
"yes": "Yes",
"appDesc": "OpenCanteen is a mobile app for accessing iCanteen.",
"welcome": "Welcome to OpenCanteen",
"aboutOrder": "Order with a tap",
"howOrder": "Simply tap on a meal with a blue checkbox next to it and it's done!",
"aboutToExch": "Don't want your food?",
"howToExch": "If you cannot cancel your order, simply long-tap on the ordered food and put it into the exchange.",
"aboutFromExch": "No food? No problem!",
"howFromExch": "Simply check the exchange from the sidebar and order when a meal is available.",
"next": "Next",
"checkOrdered": "Check if I have ordered food for the next week",
"noOrder": "You did not order any food for the next week!",
"corrupted": "The saved credentials seem to be corrupted, please try clearing the application's data.",
"notifyAt": "Send notification at",
"notifyLunch": "Send a notification with meal info",
"lunchNotif": "Today's ordered meal",
"error": "Error",
"needRemember": "You need to save your login details on the login screen first",
"notifyWarning": "Your device may have battery optimization enabled. This may cause notifications to not be sent. Check the application info in your device's settings.",
"signOutWarn": "Do you really want to sign out?",
"jump": "Jump",
"source": "Source code",
"review": "Review the app",
"saveCount": "Number of days to save offline (Current limit is 7)",
"errorSaving": "An error occured while trying to save menu offline, try again later.",
"todayTooltip": "Go to today's meal",
"settingsExperience":"App Experience",
"settingsFunctions":"Extra Functions",
"showAllergens":"Show allergens, if available"
}

167
lib/lang/lang.dart Normal file
View file

@ -0,0 +1,167 @@
import 'package:flutter/material.dart';
abstract class Languages {
static Languages? of(BuildContext context) {
return Localizations.of<Languages>(context, Languages);
}
String get appName;
String get home;
// Login
String get errorContacting;
String get loggingIn;
String get logIn;
String get username;
String get password;
String get iCanteenUrl;
String get rememberMe;
String get httpLogin;
String get yes;
String get noChange;
String get notOfficial;
String get agree;
String get disagree;
String get loginFailed;
String get warning;
String get corrupted;
// Jídelníček
String get loading;
String get monday;
String get tuesday;
String get wednesday;
String get thursday;
String get friday;
String get saturday;
String get sunday;
String get noFood;
String get inExchange;
String get ordering;
String get errorOrdering;
String get close;
String get verifyExchange;
String get no;
String get exchangeError;
String get signOut;
String get reportBugs;
String get about;
String get menu;
String get balance;
String get noOrder;
String get signOutWarn;
// Uvítací obrazovka
String get welcome;
String get appDesc;
String get aboutOrder;
String get howOrder;
String get aboutToExch;
String get howToExch;
String get aboutFromExch;
String get howFromExch;
String get next;
// Burza
String get exchange;
String get noExchange;
String get pullToReload;
String get ordered;
String get orderSuccess;
String get ok;
String get cannotOrder;
String get order;
// About
String get usedLibs;
String get license;
String get copyright;
// Nastavení
String get settings;
String get saveOffline;
String get skipWeekend;
String get checkOrdered;
String get notifyLunch;
String get notifyAt;
String get notifyWarning;
// Offline
String get offline;
String get mustLogout;
// Oznámit před obědem
String get lunchNotif;
String get error;
String get needRemember;
}

234
lib/lang/lang_cz.dart Normal file
View file

@ -0,0 +1,234 @@
import 'package:opencanteen/lang/lang.dart';
class LanguageCz extends Languages {
@override
String get about => "O Aplikaci";
@override
String get agree => "Souhlasím";
@override
String get appName => "OpenCanteen";
@override
String get balance => "Kredit: ";
@override
String get cannotOrder => "Toto jídlo není možné objednat.";
@override
String get close => "Zavřít";
@override
String get copyright => "© 2022 Matyáš Caras a přispěvatelé";
@override
String get disagree => "Nesouhlasím";
@override
String get errorContacting =>
"Nastala chyba při kontaktování serveru, zkontrolujte připojení";
@override
String get errorOrdering => "Jídlo se nepodařilo objednat";
@override
String get exchange => "Burza";
@override
String get exchangeError => "Nepodařilo se vložit jídlo na burzu";
@override
String get friday => "Pátek";
@override
String get home => "Domů";
@override
String get httpLogin =>
"Snažíte se přihlásit přes nešifrované spojení HTTP, jste si jisti, že tak chcete učinit?";
@override
String get iCanteenUrl => "iCanteen URL";
@override
String get inExchange => "V BURZE";
@override
String get license => "Vydáno pod licencí GNU GPLv3";
@override
String get loading => "Načítání...";
@override
String get logIn => "Přihlášení";
@override
String get loggingIn => "Přihlašuji vás...";
@override
String get loginFailed => "Přihlášení se nezdařilo";
@override
String get menu => "Jídelníček";
@override
String get monday => "Pondělí";
@override
String get mustLogout => "Online přejdete přetažením dolů.";
@override
String get no => "Ne";
@override
String get noChange => "Ne, změnit";
@override
String get noExchange => "Žádné jídlo v burze";
@override
String get noFood => "Žádné jídlo pro tento den";
@override
String get notOfficial =>
"Toto není oficiální aplikace k ovládání iCanteen. Autor neručí za ztráty nebo nefunkčnost v souvislosti s používáním této aplikace. Pokračováním souhlasíte.";
@override
String get offline => "JSTE OFFLINE";
@override
String get ok => "OK";
@override
String get order => "Objednat";
@override
String get orderSuccess => "Jídlo bylo úspěšně objednáno";
@override
String get ordered => "Objednáno";
@override
String get ordering => "Objednávám...";
@override
String get password => "Heslo";
@override
String get pullToReload => "Potáhněte zvrchu pro načtení";
@override
String get rememberMe => "Zapamatovat si mě";
@override
String get reportBugs => "Nahlásit chybu";
@override
String get saturday => "Sobota";
@override
String get saveOffline => "Ukládat jídelníček na dnešní den offline";
@override
String get settings => "Nastavení";
@override
String get signOut => "Odhlásit se";
@override
String get skipWeekend => "Při procházení menu přeskočit víkend";
@override
String get sunday => "Neděle";
@override
String get thursday => "Čtvrtek";
@override
String get tuesday => "Úterý";
@override
String get usedLibs => "Použité knihovny:";
@override
String get username => "Uživatelské jméno";
@override
String get verifyExchange => "Opravdu chcete vložit jídlo na burzu?";
@override
String get warning => "Pozor!";
@override
String get wednesday => "Středa";
@override
String get yes => "Ano";
@override
String get aboutFromExch => "Žádné jídlo? Žádný problém!";
@override
String get aboutOrder => "Klepnutím objednáte";
@override
String get aboutToExch => "Nemáte chuť?";
@override
String get appDesc =>
"OpenCanteen je neoficiální aplikace pro přístup do obědového systému iCanteen";
@override
String get howFromExch =>
"Z vysunovacího menu přejděte do burzy a objednejte si z dostupných jídel";
@override
String get howOrder =>
"Jednoduše klepněte na jídlo s modrým políčkem a máte objednáno";
@override
String get howToExch =>
"Stačí dlouze podržet jméno objednaného jídla a můžete ho přesunout na nebo z burzy";
@override
String get welcome => "Vítejte v OpenCanteen";
@override
String get next => "Další";
@override
String get checkOrdered =>
"Kontrolovat, jestli mám objednáno na příští týden";
@override
String get noOrder => "Na přístí týden nemáte objednané žádné jídlo!";
@override
String get corrupted =>
"Nastal problém s dešifrováním uložených údajů, prosím zkuste vyčistit veškerá data této aplikace.";
@override
String get notifyAt => "Odeslat v";
@override
String get notifyLunch =>
"V určený čas odeslat notifikaci s informacemi o obědě";
@override
String get lunchNotif => "Dnes máte objednáno";
@override
String get error => "Chyba";
@override
String get needRemember =>
"Musíte své přihlašovací údaje uložit na přihlašovací obrazovce";
@override
String get notifyWarning =>
"Vaše zařízení může mít povolenou optimalizaci baterie, což může způsobovat neodesílání oznámení. Zkontrolujte nastavení ve vašem zařízení.";
@override
String get signOutWarn => "Opravdu se chcete odhlásit?";
}

232
lib/lang/lang_en.dart Normal file
View file

@ -0,0 +1,232 @@
import 'package:opencanteen/lang/lang.dart';
class LanguageEn extends Languages {
@override
String get about => "About";
@override
String get agree => "I agree";
@override
String get appName => "OpenCanteen";
@override
String get balance => "Balance: ";
@override
String get cannotOrder => "This food cannot be ordered.";
@override
String get close => "Close";
@override
String get copyright => "© 2022 Matyáš Caras and contributors";
@override
String get disagree => "I disagree";
@override
String get errorContacting =>
"Failed to contact the server, check your connection.";
@override
String get errorOrdering => "Could not order food.";
@override
String get exchange => "Exchange";
@override
String get exchangeError => "Could not put food on exchange.";
@override
String get friday => "Friday";
@override
String get home => "Home";
@override
String get httpLogin =>
"You are trying to sign in using an insecure HTTP connection, are you sure you want to continue?";
@override
String get iCanteenUrl => "iCanteen URL";
@override
String get inExchange => "ON EXCHANGE";
@override
String get license => "Released under GNU GPLv3";
@override
String get loading => "Loading...";
@override
String get logIn => "Sign in";
@override
String get loggingIn => "Signing you in...";
@override
String get loginFailed => "Sign in failed";
@override
String get menu => "Food Menu";
@override
String get monday => "Monday";
@override
String get mustLogout => "To go online, pull down.";
@override
String get no => "No";
@override
String get noChange => "No, change";
@override
String get noExchange => "No meal in exchange";
@override
String get noFood => "No meal for this day";
@override
String get notOfficial =>
"This is not an official app for accessing iCanteen. The author is not responsible for non-functionality or losses while using this app. By continuing you agree.";
@override
String get offline => "YOU ARE OFFLINE";
@override
String get ok => "OK";
@override
String get order => "Order";
@override
String get orderSuccess => "Meal ordered succesfully";
@override
String get ordered => "Ordered";
@override
String get ordering => "Ordering...";
@override
String get password => "Password";
@override
String get pullToReload => "Pull to reload";
@override
String get rememberMe => "Remember me";
@override
String get reportBugs => "Report bug";
@override
String get saturday => "Saturday";
@override
String get saveOffline => "Save today's menu offline";
@override
String get settings => "Settings";
@override
String get signOut => "Sign out";
@override
String get skipWeekend => "Skip weekends when browsing menu";
@override
String get sunday => "Sunday";
@override
String get thursday => "Thursday";
@override
String get tuesday => "Tuesday";
@override
String get usedLibs => "Used libraries:";
@override
String get username => "Username";
@override
String get verifyExchange =>
"Are you sure you want to put this meal on exchange?";
@override
String get warning => "Warning!";
@override
String get wednesday => "Wednesday";
@override
String get yes => "Yes";
@override
String get appDesc => "OpenCanteen is a mobile app for accessing iCanteen.";
@override
String get welcome => "Welcome to OpenCanteen";
@override
String get aboutOrder => "Order with a tap";
@override
String get howOrder =>
"Simply tap on a meal with a blue checkbox next to it and it's done!";
@override
String get aboutToExch => "Don't want your food?";
@override
String get howToExch =>
"If you cannot cancel your order, simply long-tap on the ordered food and put it into the exchange.";
@override
String get aboutFromExch => "No food? No problem!";
@override
String get howFromExch =>
"Simply check the exchange from the sidebar and order when a meal is available.";
@override
String get next => "Next";
@override
String get checkOrdered => "Check if I have ordered food for the next week";
@override
String get noOrder => "You did not order any food for the next week!";
@override
String get corrupted =>
"The saved credentials seem to be corrupted, please try clearing the application's data.";
@override
String get notifyAt => "Send notification at";
@override
String get notifyLunch => "Send a notification with meal info";
@override
String get lunchNotif => "Today's ordered meal";
@override
String get error => "Error";
@override
String get needRemember =>
"You need to save your login details on the login screen first";
@override
String get notifyWarning =>
"Your device may have battery optimization enabled. This may cause notifications to not be sent. Check the application info in your device's settings.";
@override
String get signOutWarn => "Do you really want to sign out?";
}

View file

@ -1,16 +1,46 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
class LoginManager { class LoginManager {
static Future<Map<String, String>?> getDetails() async { static Future<Map<String, String>?> getDetails(String id) async {
// check secure storage for details // zkontrolovat secure storage pokud je něco uložené
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
var user = await storage.read(key: "oc_user"); var user = await storage.read(key: "oc_user_$id");
var pass = await storage.read(key: "oc_pass"); var pass = await storage.read(key: "oc_pass_$id");
var url = await storage.read(key: "oc_url"); var url = await storage.read(key: "oc_url_$id");
if (user == null || pass == null || url == null) return null; if (user == null || pass == null || url == null) return null;
return {"user": user, "pass": pass, "url": url}; return {"user": user, "pass": pass, "url": url};
} }
static Future<Map<int, List<String>>> ziskatVsechnyUlozene() async {
const storage = FlutterSecureStorage();
if (await storage.containsKey(key: "oc_user")) {
await prevest();
}
var vsechno = await storage.readAll();
vsechno.removeWhere((key, value) => key.startsWith("oc_pass_"));
var uzivatele = <int, List<String>>{};
vsechno.forEach((key, value) {
if (key.startsWith("oc_user_")) {
var user = int.parse(key.substring(8));
if (uzivatele[user] == null) {
uzivatele[user] = [value];
} else {
uzivatele[user]!.add(value);
}
} else {
var user = int.parse(key.substring(8));
if (uzivatele[user] == null) {
uzivatele[user] = [value];
} else {
uzivatele[user]!.add(value);
}
}
});
print(uzivatele);
return uzivatele;
}
static Future<void> setDetails(String user, String pass, String url) async { static Future<void> setDetails(String user, String pass, String url) async {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
await storage.write(key: "oc_user", value: user); await storage.write(key: "oc_user", value: user);
@ -18,8 +48,25 @@ class LoginManager {
await storage.write(key: "oc_url", value: url); await storage.write(key: "oc_url", value: url);
} }
static Future<bool> rememberme() async { static Future<bool> zapamatovat() async {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
return await storage.containsKey(key: "oc_pass"); return await storage.containsKey(key: "oc_pass");
} }
/// Převést na nový formát ukládání
static Future<bool> prevest() async {
const storage = FlutterSecureStorage();
var user = await storage.read(key: "oc_user");
var pass = await storage.read(key: "oc_pass");
var url = await storage.read(key: "oc_url");
if (user == null || pass == null || url == null) return false;
await storage.write(key: "oc_user_0", value: user);
await storage.write(key: "oc_pass_0", value: pass);
await storage.write(key: "oc_url_0", value: url);
await storage.delete(key: "oc_user");
await storage.delete(key: "oc_pass");
await storage.delete(key: "oc_url");
return true;
}
} }

View file

@ -1,21 +1,28 @@
import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/lang/lang_cz.dart';
import 'package:opencanteen/loginmanager.dart';
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:opencanteen/okna/login.dart'; import 'package:opencanteen/okna/offline_jidelnicek.dart';
import 'package:opencanteen/okna/welcome.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:timezone/data/latest_all.dart' as tz; import 'package:timezone/data/latest_all.dart' as tz;
import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart' as tz;
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'lang/lang.dart';
import 'color_schemes.g.dart'; import 'lang/lang_en.dart';
import 'loginmanager.dart'; import 'okna/jidelnicek.dart';
/* /*
Copyright (C) 2022 Matyáš Caras a přispěvatelé Copyright (C) 2022 Matyáš Caras a přispěvatelé
@ -35,64 +42,66 @@ Copyright (C) 2022 Matyáš Caras a přispěvatelé
final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin =
FlutterLocalNotificationsPlugin(); FlutterLocalNotificationsPlugin();
final settings = SettingsManager();
/// Used to setup notifications about ordered food void oznamitPredem(SharedPreferences prefs, tz.Location l) async {
void setupNotification(SharedPreferences prefs, tz.Location l) async {
String title; String title;
String locale = Intl.getCurrentLocale(); String locale = Intl.getCurrentLocale();
switch (locale) { switch (locale) {
case "cs_CZ": case "cs_CZ":
title = "Dnes máte objednáno"; title = LanguageCz().lunchNotif;
break; break;
default: default:
title = "Today's ordered meal"; title = LanguageEn().lunchNotif;
} }
/*if (prefs.getBool("offline") ?? false) { /*if (prefs.getBool("offline") ?? false) {
// TODO grab data from offline storage // TODO možnost brát z offline dat
} else {*/ } else {*/
// data from the web // bere online
var d = await LoginManager.getDetails(); // grab login if ((await LoginManager.ziskatVsechnyUlozene()).isNotEmpty) {
var d = await LoginManager.getDetails("0s"); // získat údaje
if (d != null) { if (d != null) {
var c = Canteen(d["url"]!); var c = Canteen(d["url"]!);
if (await c.login(d["user"]!, d["pass"]!)) { if (await c.login(d["user"]!, d["pass"]!)) {
var jidla = await c.jidelnicekDen(); var jidla = await c.jidelnicekDen();
try { try {
var jidlo = jidla.jidla var jidlo = jidla.jidla.singleWhere(
.singleWhere((element) => element.objednano); // grab ordered meal (element) => element.objednano); // získá objednané jídlo
var kdy = DateTime.parse(prefs.getString( var kdy = DateTime.parse(prefs.getString(
"oznameni_cas")!); // save the time the notif should be sent "oznameni_cas")!); // uložíme čas, kdy se odeslat oznámení
var cas = timeToDate(
TimeOfDay(hour: kdy.hour, minute: kdy.minute), // data o oznámení
);
if (cas.isBefore(DateTime.now())) return;
// notif data
const AndroidNotificationDetails androidSpec = const AndroidNotificationDetails androidSpec =
AndroidNotificationDetails('predobedem', 'Oznámení před obědem', AndroidNotificationDetails('predobedem', 'Oznámení před obědem',
channelDescription: 'Oznámení o dnešním jídle', channelDescription: 'Oznámení o dnešním jídle',
importance: Importance.max, importance: Importance.max,
priority: Priority.high, priority: Priority.high,
styleInformation: BigTextStyleInformation(''),
ticker: 'today meal'); ticker: 'today meal');
const IOSNotificationDetails iOSpec =
IOSNotificationDetails(presentAlert: true, presentBadge: true);
// plan through lib // naplánovat
await flutterLocalNotificationsPlugin.zonedSchedule( await flutterLocalNotificationsPlugin.zonedSchedule(
0, 0,
title, title,
"${jidlo.varianta} - ${jidlo.nazev}", "${jidlo.varianta} - ${jidlo.nazev}",
tz.TZDateTime.from(cas, l), tz.TZDateTime.from(
const NotificationDetails(android: androidSpec), casNaDate(
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, TimeOfDay(hour: kdy.hour, minute: kdy.minute),
),
l),
const NotificationDetails(android: androidSpec, iOS: iOSpec),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation: uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime); UILocalNotificationDateInterpretation.absoluteTime);
} on StateError catch (_) { } on StateError catch (_) {
// no ordered meal found // nenalezeno
} }
} }
// } // }
} }
}
} }
void main() async { void main() async {
@ -103,68 +112,368 @@ void main() async {
var prefs = await SharedPreferences.getInstance(); var prefs = await SharedPreferences.getInstance();
if (prefs.getBool("oznamit") ?? false) { if (prefs.getBool("oznamit") ?? false) {
setupNotification(prefs, l); oznamitPredem(prefs, l);
} }
settings.checkOrdered = prefs.getBool("tyden") ?? false;
settings.saveOffline = prefs.getBool("oznamit") ?? false;
settings.skipWeekend = prefs.getBool("skip") ?? false;
settings.allergens = prefs.getBool("allergens") ?? false;
// notif library setup // nastavit oznámení
const AndroidInitializationSettings initializationSettingsAndroid = const AndroidInitializationSettings initializationSettingsAndroid =
AndroidInitializationSettings('notif_icon'); AndroidInitializationSettings('notif_icon');
const ios = DarwinInitializationSettings(); final IOSInitializationSettings initializationSettingsIOS =
IOSInitializationSettings(onDidReceiveLocalNotification: (
int id,
String? title,
String? body,
String? payload,
) async {
debugPrint(body);
});
const InitializationSettings initializationSettings = final InitializationSettings initializationSettings = InitializationSettings(
InitializationSettings(android: initializationSettingsAndroid, iOS: ios); android: initializationSettingsAndroid,
await flutterLocalNotificationsPlugin.initialize(initializationSettings); iOS: initializationSettingsIOS,
);
await flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: (String? payload) async {
if (payload != null) {
debugPrint('notification payload: $payload');
}
});
// spustit aplikaci
runApp(const MyApp()); runApp(const MyApp());
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
const MyApp({super.key}); const MyApp({Key? key}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return (Platform return MaterialApp(
.isAndroid) // run app based on current platform to make use of the platform's respective UI lib
? MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
localizationsDelegates: const [ localizationsDelegates: const [
AppLocalizations.delegate, AppLocalizationsDelegate(),
...GlobalMaterialLocalizations.delegates ...GlobalMaterialLocalizations.delegates
], ],
supportedLocales: AppLocalizations.supportedLocales, supportedLocales: const [Locale("cs", ""), Locale("en", "")],
title: "OpenCanteen", title: "OpenCanteen",
theme: ThemeData(useMaterial3: true, colorScheme: lightColorScheme), theme: ThemeData(
primarySwatch: Colors.purple,
),
darkTheme: ThemeData( darkTheme: ThemeData(
brightness: Brightness.dark, brightness: Brightness.dark,
useMaterial3: true, primarySwatch: Colors.purple,
colorScheme: darkColorScheme), ),
home: const LoginPage(), home: const LoginPage(),
)
: Theme(
data: ThemeData(
useMaterial3: true,
colorScheme: (MediaQuery.of(context).platformBrightness ==
Brightness.dark)
? darkColorScheme
: lightColorScheme),
child: const CupertinoApp(
debugShowCheckedModeBanner: false,
localizationsDelegates: [
AppLocalizations.delegate,
...GlobalMaterialLocalizations.delegates
],
supportedLocales: AppLocalizations.supportedLocales,
title: "OpenCanteen",
theme: CupertinoThemeData(
primaryColor: Colors.purple,
),
home: LoginPage(),
),
); );
} }
} }
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController userControl = TextEditingController();
TextEditingController passControl = TextEditingController();
TextEditingController canteenControl = TextEditingController();
bool rememberMe = false;
@override
void initState() {
super.initState();
LoginManager.ziskatVsechnyUlozene().then((j) async {
if (Platform.isIOS) {
// žádat o oprávnění na iOS
await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
IOSFlutterLocalNotificationsPlugin>()
?.requestPermissions(
alert: true,
badge: true,
sound: true,
);
}
Map<String, String>? r;
if (j.isNotEmpty) {
r = await LoginManager.getDetails(
"0"); // TODO: změnit, aby šlo vybírat účet, který se použije
}
if (r != null) {
// Automaticky přihlásit
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => Dialog(
child: SizedBox(
height: 100,
child: Row(children: [
const Padding(
padding: EdgeInsets.all(10),
child: CircularProgressIndicator(),
),
Text(Languages.of(context)!.loggingIn)
]),
),
));
var canteen = Canteen(r["url"]!);
try {
var l = await canteen.login(r["user"]!, r["pass"]!);
if (!l) {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(Languages.of(context)!.loginFailed),
),
);
return;
}
const storage = FlutterSecureStorage();
var odsouhlasil = await storage.read(key: "oc_souhlas");
if (!mounted) return;
if (odsouhlasil == null || odsouhlasil != "ano") {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (c) => WelcomeScreen(
canteen: canteen, n: flutterLocalNotificationsPlugin)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => JidelnicekPage(
canteen: canteen, n: flutterLocalNotificationsPlugin)),
);
}
} on PlatformException {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(Languages.of(context)!.corrupted),
),
);
} catch (_) {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(Languages.of(context)!.errorContacting),
),
);
goOffline();
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Languages.of(context)!.logIn),
automaticallyImplyLeading: false,
),
body: Center(
child: SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width - 50,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
Languages.of(context)!.appName,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 40),
),
Text(
Languages.of(context)!.logIn,
textAlign: TextAlign.center,
),
TextField(
controller: userControl,
autofillHints: const [AutofillHints.username],
decoration: InputDecoration(
labelText: Languages.of(context)!.username),
),
TextField(
autofillHints: const [AutofillHints.password],
decoration: InputDecoration(
labelText: Languages.of(context)!.password),
controller: passControl,
obscureText: true,
),
TextField(
autofillHints: const [AutofillHints.url],
decoration: InputDecoration(
labelText: Languages.of(context)!.iCanteenUrl),
keyboardType: TextInputType.url,
controller: canteenControl,
),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Switch(
value: rememberMe,
onChanged: (value) {
setState(() {
rememberMe = value;
});
}),
Text(Languages.of(context)!.rememberMe)
]),
TextButton(
onPressed: () async {
if (canteenControl.text.contains("http://")) {
// kontrolujeme šifrované spojení
var d = await showDialog<bool>(
context: context,
builder: (c) => AlertDialog(
title: Text(Languages.of(context)!.warning),
content: SingleChildScrollView(
child: Text(
Languages.of(context)!.httpLogin)),
actions: [
TextButton(
onPressed: () =>
Navigator.pop(c, true),
child:
Text(Languages.of(context)!.yes)),
TextButton(
onPressed: () =>
Navigator.pop(c, false),
child: Text(
Languages.of(context)!.noChange))
],
));
if (!d!) return;
}
if (!canteenControl.text.startsWith("https://") &&
!canteenControl.text.startsWith("http://")) {
canteenControl.text =
"https://${canteenControl.text}";
}
var canteen = Canteen(canteenControl.text);
try {
var l = await canteen.login(
userControl.text, passControl.text);
if (!l) {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(Languages.of(context)!.loginFailed),
),
);
return;
}
if (rememberMe) {
LoginManager.setDetails(userControl.text,
passControl.text, canteenControl.text);
}
// souhlas
const storage = FlutterSecureStorage();
var odsouhlasil =
await storage.read(key: "oc_souhlas");
if (!mounted) return;
if (odsouhlasil == null || odsouhlasil != "ano") {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (c) => WelcomeScreen(
canteen: canteen,
n: flutterLocalNotificationsPlugin)));
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => JidelnicekPage(
canteen: canteen,
n: flutterLocalNotificationsPlugin)),
);
}
} on PlatformException {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(Languages.of(context)!.corrupted),
),
);
} on Exception catch (_) {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content:
Text(Languages.of(context)!.errorContacting),
),
);
goOffline();
}
},
child: Text(Languages.of(context)!.logIn)),
const Divider(
height: 5,
)
],
),
),
),
));
}
/// Získá offline soubor a zobrazí údaje
void goOffline() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
var den = DateTime.now();
var soubor = File(
"${appDocDir.path}/jidelnicek_${den.year}-${den.month}-${den.day}.json");
if (soubor.existsSync()) {
// načteme offline jídelníček
var input = await soubor.readAsString();
var r = jsonDecode(input);
List<OfflineJidlo> jidla = [];
for (var j in r) {
jidla.add(OfflineJidlo(
nazev: j["nazev"],
varianta: j["varianta"],
objednano: j["objednano"],
cena: j["cena"],
naBurze: j["naBurze"],
den: DateTime.parse(j["den"])));
}
if (!mounted) return;
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: ((context) => OfflineJidelnicek(jidla: jidla))));
}
}
}
class AppLocalizationsDelegate extends LocalizationsDelegate<Languages> {
const AppLocalizationsDelegate();
@override
bool isSupported(Locale locale) => ['cs', 'en'].contains(locale.languageCode);
@override
Future<Languages> load(Locale locale) => _load(locale);
static Future<Languages> _load(Locale locale) async {
switch (locale.languageCode) {
case 'cs':
return LanguageCz();
default:
return LanguageEn();
}
}
@override
bool shouldReload(LocalizationsDelegate<Languages> old) => false;
}

109
lib/okna/about.dart Normal file
View file

@ -0,0 +1,109 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
import '../lang/lang.dart';
class AboutPage extends StatefulWidget {
const AboutPage({Key? key}) : super(key: key);
@override
State<AboutPage> createState() => _AboutPageState();
}
class _AboutPageState extends State<AboutPage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(Languages.of(context)!.about),
),
body: Center(
child: SizedBox(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width - 50,
child: SingleChildScrollView(
child: Column(mainAxisSize: MainAxisSize.min, children: [
const Text("OpenCanteen", style: TextStyle(fontSize: 30)),
Text(Languages.of(context)!.copyright),
InkWell(
onTap: () => launchUrl(Uri.parse(
"https://github.com/hernikplays/opencanteen/blob/main/LICENSE")),
child: Text(Languages.of(context)!.license)),
const SizedBox(height: 15),
Text(Languages.of(context)!.usedLibs,
style: const TextStyle(fontSize: 19)),
const SizedBox(height: 10),
cudlik(
"Flutter",
"Copyright 2014 The Flutter Authors. All rights reserved, licence BSD 3-Clause",
"https://github.com/flutter/flutter/blob/master/LICENSE"),
const SizedBox(height: 10),
cudlik(
"Flutter_secure_storage",
"Copyright 2017 German Saprykin. All rights reserved, licence BSD 3-Clause",
"https://github.com/mogol/flutter_secure_storage/blob/develop/flutter_secure_storage/LICENSE"),
const SizedBox(height: 10),
cudlik(
"url_launcher",
"Copyright 2013 The Flutter Authors. All rights reserved, licence BSD 3-Clause",
"https://github.com/flutter/plugins/blob/main/packages/url_launcher/url_launcher/LICENSE"),
const SizedBox(height: 10),
cudlik(
"canteenlib",
"Copyright (c) 2022 Matyáš Caras and contributors, licence MIT",
"https://github.com/hernikplays/canteenlib/blob/main/LICENSE"),
const SizedBox(height: 10),
cudlik(
"path_provider",
"Copyright 2013 The Flutter Authors. All rights reserved, licence BSD-3-Clause",
"https://github.com/flutter/plugins/blob/main/packages/path_provider/path_provider/LICENSE"),
const SizedBox(height: 10),
cudlik(
"shared_preferences",
"Copyright 2013 The Flutter Authors. All rights reserved, licence BSD-3-Clause",
"https://github.com/flutter/plugins/blob/main/packages/path_provider/path_provider/LICENSE"),
const SizedBox(height: 10),
cudlik(
"introduction_screen",
"Copyright 2019 Jean-Charles Moussé, licence MIT",
"https://github.com/Pyozer/introduction_screen/blob/master/LICENSE"),
const SizedBox(height: 10),
cudlik(
"flutter_local_notifications",
"Copyright 2018 Michael Bui. All rights reserved, licence BSD-3-Clause",
"https://github.com/MaikuB/flutter_local_notifications/blob/master/flutter_local_notifications/LICENSE"),
const SizedBox(height: 10),
cudlik(
"timezone",
"Copyright 2014, timezone project authors, licence BSD-2-Clause",
"https://github.com/srawlins/timezone/blob/master/LICENSE"),
const SizedBox(height: 10),
cudlik(
"flutter_native_timezone",
"Copyright 2019 pinkfish, licence Apache 2.0",
"https://github.com/pinkfish/flutter_native_timezone/blob/master/LICENSE"),
]),
),
),
),
);
}
Widget cudlik(String nazev, String copyright, String licence) {
return InkWell(
onTap: () => launchUrl(Uri.parse(licence)),
child: Column(children: [
Text(
nazev,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 17),
textAlign: TextAlign.center,
),
Text(
copyright,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 12),
),
]),
);
}
}

View file

@ -1,47 +1,41 @@
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:opencanteen/okna/login.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:opencanteen/pw/platformbutton.dart';
import 'package:opencanteen/pw/platformdialog.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../lang/lang.dart';
import '../main.dart';
class BurzaView extends StatefulWidget { class BurzaPage extends StatefulWidget {
const BurzaView({super.key, required this.canteen}); const BurzaPage({Key? key, required this.canteen, required this.n})
: super(key: key);
final Canteen canteen; final Canteen canteen;
final FlutterLocalNotificationsPlugin n;
@override @override
State<BurzaView> createState() => _BurzaViewState(); State<BurzaPage> createState() => _BurzaPageState();
} }
class _BurzaViewState extends State<BurzaView> { class _BurzaPageState extends State<BurzaPage> {
List<Widget> content = []; List<Widget> obsah = [];
double balance = 0.0; double kredit = 0.0;
Future<void> nactiBurzu(BuildContext context) async {
Future<void> loadExchange(BuildContext context) async { obsah = [const CircularProgressIndicator()];
content = [const CircularProgressIndicator()]; widget.canteen.ziskejUzivatele().then((kr) {
var uzivatel = await widget.canteen.ziskejUzivatele().catchError((o) { kredit = kr.kredit;
if (!widget.canteen.prihlasen) { widget.canteen.ziskatBurzu().then((burza) {
Navigator.pushReplacement(
context, platformRouter((c) => const LoginPage()));
}
return Uzivatel(kredit: 0);
});
balance = uzivatel.kredit;
var burza = await widget.canteen.ziskatBurzu();
setState(() { setState(() {
content = []; obsah = [];
if (burza.isEmpty) { if (burza.isEmpty) {
content = [ obsah = [
Text( Text(
AppLocalizations.of(context)!.noExchange, Languages.of(context)!.noExchange,
style: const TextStyle(fontSize: 20), style: const TextStyle(fontSize: 20),
), ),
Text(AppLocalizations.of(context)!.pullToReload) Text(Languages.of(context)!.pullToReload)
]; ];
} else { } else {
for (var b in burza) { for (var b in burza) {
content.add( obsah.add(
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: Row( child: Row(
@ -55,20 +49,19 @@ class _BurzaViewState extends State<BurzaView> {
), ),
), ),
Text("${b.pocet}x"), Text("${b.pocet}x"),
PlatformButton( TextButton(
onPressed: () { onPressed: () {
widget.canteen.objednatZBurzy(b).then( widget.canteen.objednatZBurzy(b).then((a) {
(a) {
if (a) { if (a) {
showDialog( showDialog(
context: context, context: context,
builder: (context) => PlatformDialog( builder: (context) => AlertDialog(
title: AppLocalizations.of(context)!.ordered, title: Text(Languages.of(context)!.ordered),
content: content: Text(
AppLocalizations.of(context)!.orderSuccess, Languages.of(context)!.orderSuccess),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.ok, child: Text(Languages.of(context)!.ok),
onPressed: () => onPressed: () =>
Navigator.of(context).pop(), Navigator.of(context).pop(),
) )
@ -78,14 +71,14 @@ class _BurzaViewState extends State<BurzaView> {
} else { } else {
showDialog( showDialog(
context: context, context: context,
builder: (context) => PlatformDialog( builder: (context) => AlertDialog(
title: title: Text(
AppLocalizations.of(context)!.cannotOrder, Languages.of(context)!.cannotOrder),
content: content: Text(
AppLocalizations.of(context)!.errorOrdering, Languages.of(context)!.errorOrdering),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.ok, child: Text(Languages.of(context)!.ok),
onPressed: () => onPressed: () =>
Navigator.of(context).pop(), Navigator.of(context).pop(),
) )
@ -93,12 +86,10 @@ class _BurzaViewState extends State<BurzaView> {
), ),
); );
} }
loadExchange(context); nactiBurzu(context);
});
}, },
); child: Text(Languages.of(context)!.order)),
},
text: AppLocalizations.of(context)!.order,
),
], ],
), ),
), ),
@ -106,21 +97,27 @@ class _BurzaViewState extends State<BurzaView> {
} }
} }
}); });
return; });
}).catchError((o) {
if (!widget.canteen.prihlasen) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (c) => const LoginPage()));
}
});
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
loadExchange(context); nactiBurzu(context);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
drawer: drawerGenerator(context, widget.canteen, 3), drawer: drawerGenerator(context, widget.canteen, 3, widget.n),
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.exchange), title: Text(Languages.of(context)!.exchange),
), ),
body: RefreshIndicator( body: RefreshIndicator(
child: Center( child: Center(
@ -129,20 +126,20 @@ class _BurzaViewState extends State<BurzaView> {
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 10), const SizedBox(height: 10),
Text("${AppLocalizations.of(context)!.balance}$balance"), Text("${Languages.of(context)!.balance}$kredit"),
const SizedBox(height: 10), const SizedBox(height: 10),
SingleChildScrollView( SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: SizedBox( child: SizedBox(
height: MediaQuery.of(context).size.height / 1.3, height: MediaQuery.of(context).size.height / 1.3,
child: Column(children: content), child: Column(children: obsah),
), ),
) )
], ],
), ),
), ),
), ),
onRefresh: () => loadExchange(context)), onRefresh: () => nactiBurzu(context)),
); );
} }
} }

View file

@ -3,115 +3,91 @@ import 'dart:io';
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/main.dart';
import 'package:opencanteen/okna/login.dart';
import 'package:opencanteen/okna/nastaveni.dart'; import 'package:opencanteen/okna/nastaveni.dart';
import 'package:opencanteen/pw/platformbutton.dart';
import 'package:opencanteen/pw/platformdialog.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../lang/lang.dart';
import '../main.dart';
import 'about.dart';
class MealView extends StatefulWidget { class JidelnicekPage extends StatefulWidget {
const MealView({super.key, required this.canteen}); const JidelnicekPage({Key? key, required this.canteen, required this.n})
: super(key: key);
final Canteen canteen; final Canteen canteen;
final FlutterLocalNotificationsPlugin n;
@override @override
State<MealView> createState() => _MealViewState(); State<JidelnicekPage> createState() => _JidelnicekPageState();
} }
class _MealViewState extends State<MealView> { class _JidelnicekPageState extends State<JidelnicekPage> {
List<Widget> content = [const CircularProgressIndicator()]; List<Widget> obsah = [const CircularProgressIndicator()];
DateTime day = DateTime.now(); DateTime den = DateTime.now();
String dayOWeek = ""; String denTydne = "";
double balance = 0.0; double kredit = 0.0;
bool _skipWeekend = false;
void checkWeek(BuildContext context) async { void kontrolaTyden(BuildContext context) async {
if (settings.checkOrdered) { var prefs = await SharedPreferences.getInstance();
// Check if user has ordered a meal in the next week if (prefs.getBool("tyden") ?? false) {
var pristi = day.add(const Duration(days: 6)); // Zjistit jestli je objednáno na přístí týden
for (var i = 0; i < 5; i++) { var pristi = den.add(const Duration(days: 7));
var jidelnicek = await widget.canteen var jidelnicek = await widget.canteen.jidelnicekDen(den: pristi);
.jidelnicekDen(den: pristi.add(Duration(days: i + 1)));
if (jidelnicek.jidla.isNotEmpty && if (jidelnicek.jidla.isNotEmpty &&
!jidelnicek.jidla.any((element) => element.objednano == true)) { !jidelnicek.jidla.any((element) => element.objednano == true)) {
if (!mounted) break; if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
content: Text(AppLocalizations.of(context)!.noOrder), content: Text(Languages.of(context)!.noOrder),
duration: const Duration(seconds: 5), duration: const Duration(seconds: 5),
action: SnackBarAction(
onPressed: () => setState(
() {
day = pristi.add(Duration(days: i + 1));
loadMeals();
},
),
label: AppLocalizations.of(context)!.jump,
),
), ),
); );
break;
}
} }
} }
} }
Future<void> loadMeals() async { Future<void> nactiJidlo() async {
content = [const CircularProgressIndicator()]; obsah = [const CircularProgressIndicator()];
switch (day.weekday) { switch (den.weekday) {
case 2: case 2:
dayOWeek = AppLocalizations.of(context)!.tuesday; denTydne = Languages.of(context)!.tuesday;
break; break;
case 3: case 3:
dayOWeek = AppLocalizations.of(context)!.wednesday; denTydne = Languages.of(context)!.wednesday;
break; break;
case 4: case 4:
dayOWeek = AppLocalizations.of(context)!.thursday; denTydne = Languages.of(context)!.thursday;
break; break;
case 5: case 5:
dayOWeek = AppLocalizations.of(context)!.friday; denTydne = Languages.of(context)!.friday;
break; break;
case 6: case 6:
dayOWeek = AppLocalizations.of(context)!.saturday; denTydne = Languages.of(context)!.saturday;
break; break;
case 7: case 7:
dayOWeek = AppLocalizations.of(context)!.sunday; denTydne = Languages.of(context)!.sunday;
break; break;
default: default:
dayOWeek = AppLocalizations.of(context)!.monday; denTydne = Languages.of(context)!.monday;
} }
Uzivatel uzivatel; widget.canteen.ziskejUzivatele().then((kr) {
try { kredit = kr.kredit;
uzivatel = await widget.canteen.ziskejUzivatele(); widget.canteen.jidelnicekDen(den: den).then((jd) async {
} catch (e) { setState(() {
if (!widget.canteen.prihlasen && mounted) { obsah = [];
Navigator.pushReplacement(
context, platformRouter((c) => const LoginPage()));
}
return;
}
balance = uzivatel.kredit;
var jd = await widget.canteen.jidelnicekDen(den: day).catchError((_) {
showInfo(context, AppLocalizations.of(context)!.errorContacting);
return Jidelnicek(DateTime.now(), []);
});
setState(
() {
content = [];
if (jd.jidla.isEmpty) { if (jd.jidla.isEmpty) {
content.add(Text( obsah.add(Text(
AppLocalizations.of(context)!.noFood, Languages.of(context)!.noFood,
style: const TextStyle(fontSize: 15), style: const TextStyle(fontSize: 15),
)); ));
} else { } else {
for (var j in jd.jidla) { for (var j in jd.jidla) {
content.add( obsah.add(
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: InkWell( child: InkWell(
@ -122,47 +98,38 @@ class _MealViewState extends State<MealView> {
const SizedBox(width: 10), const SizedBox(width: 10),
Flexible( Flexible(
child: Text( child: Text(
(settings.allergens && j.alergeny.isNotEmpty) j.nazev,
? "${j.nazev} (${j.alergeny.map<String>((e) => (e.kod != null) ? e.kod.toString() : e.nazev).join(', ')})"
: j.nazev,
), ),
), ),
Text((j.naBurze) Text((j.naBurze)
? AppLocalizations.of(context)!.inExchange ? Languages.of(context)!.inExchange
: "${j.cena}"), : "${j.cena}"),
Checkbox( Checkbox(
value: j.objednano, value: j.objednano,
side: BorderSide( fillColor: (j.lzeObjednat)
width: 2, ? MaterialStateProperty.all(Colors.blue)
color: (j.lzeObjednat) : MaterialStateProperty.all(Colors.grey),
? (Colors.purple)
: (Colors.grey)),
fillColor: (j.lzeObjednat && j.objednano)
? MaterialStateProperty.all(Colors.purple)
: (j.objednano)
? MaterialStateProperty.all(Colors.grey)
: MaterialStateProperty.all(Colors.transparent),
onChanged: (v) async { onChanged: (v) async {
if (!j.lzeObjednat) { if (!j.lzeObjednat) {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return PlatformDialog( return AlertDialog(
title: AppLocalizations.of(context)! title: Text(Languages.of(context)!
.errorOrdering, .errorOrdering),
content: content: Text(
AppLocalizations.of(context)!.cannotOrder, Languages.of(context)!.cannotOrder),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.ok, child:
Text(Languages.of(context)!.ok),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )
], ],
); );
}, });
);
} else { } else {
showDialog( showDialog(
context: context, context: context,
@ -170,48 +137,44 @@ class _MealViewState extends State<MealView> {
builder: (_) => Dialog( builder: (_) => Dialog(
child: SizedBox( child: SizedBox(
height: 100, height: 100,
child: Row( child: Row(children: [
children: [
const Padding( const Padding(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: CircularProgressIndicator(), child:
CircularProgressIndicator(),
), ),
Text(AppLocalizations.of(context)! Text(Languages.of(context)!
.ordering) .ordering)
], ]),
), ),
), ));
),
);
widget.canteen.objednat(j).then((_) { widget.canteen.objednat(j).then((_) {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true)
loadMeals(); .pop();
}).catchError( nactiJidlo();
(o) { }).catchError((o) {
Navigator.of(context, rootNavigator: true) Navigator.of(context, rootNavigator: true)
.pop(); .pop();
showDialog( showDialog(
context: context, context: context,
builder: (bc) => PlatformDialog( builder: (bc) => AlertDialog(
title: AppLocalizations.of(context)! title: Text(Languages.of(context)!
.errorOrdering, .errorOrdering),
content: o.toString(), content: Text(o.toString()),
actions: [ actions: [
PlatformButton( TextButton(
text: child: Text(
AppLocalizations.of(context)!.close, Languages.of(context)!
.close),
onPressed: () { onPressed: () {
Navigator.pop(bc); Navigator.pop(bc);
}, },
) )
], ],
), ));
); });
},
);
} }
}, })
)
], ],
), ),
onTap: () async { onTap: () async {
@ -219,20 +182,21 @@ class _MealViewState extends State<MealView> {
showDialog( showDialog(
context: context, context: context,
builder: (context) { builder: (context) {
return PlatformDialog( return AlertDialog(
title: AppLocalizations.of(context)!.errorOrdering, title:
content: AppLocalizations.of(context)!.cannotOrder, Text(Languages.of(context)!.errorOrdering),
content:
Text(Languages.of(context)!.cannotOrder),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.ok, child: Text(Languages.of(context)!.ok),
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )
], ],
); );
}, });
);
} else { } else {
showDialog( showDialog(
context: context, context: context,
@ -245,87 +209,84 @@ class _MealViewState extends State<MealView> {
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
), ),
Text(AppLocalizations.of(context)!.ordering) Text(Languages.of(context)!.ordering)
]), ]),
), ),
), ));
);
widget.canteen.objednat(j).then((_) { widget.canteen.objednat(j).then((_) {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true).pop();
loadMeals(); nactiJidlo();
}).catchError( }).catchError((o) {
(o) {
Navigator.of(context, rootNavigator: true).pop(); Navigator.of(context, rootNavigator: true).pop();
showDialog( showDialog(
context: context, context: context,
builder: (bc) => PlatformDialog( builder: (bc) => AlertDialog(
title: title: Text(
AppLocalizations.of(context)!.errorOrdering, Languages.of(context)!.errorOrdering),
content: o.toString(), content: Text(o.toString()),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.close, child:
Text(Languages.of(context)!.close),
onPressed: () { onPressed: () {
Navigator.pop(bc); Navigator.pop(bc);
}, },
) )
], ],
), ));
); });
},
);
} }
}, },
onLongPress: () async { onLongPress: () async {
if (!j.objednano || j.burzaUrl == null) return; if (!j.objednano || j.burzaUrl == null) return;
if (!j.naBurze) { if (!j.naBurze) {
// if not in exchange, we ask // pokud není na burze, radši se zeptáme
var d = await showDialog( var d = await showDialog(
context: context, context: context,
builder: (bc) => PlatformDialog( builder: (bc) => SimpleDialog(
title: AppLocalizations.of(context)!.verifyExchange, title: Text(
actions: [ Languages.of(context)!.verifyExchange),
PlatformButton( children: [
SimpleDialogOption(
onPressed: () { onPressed: () {
Navigator.pop(bc, true); Navigator.pop(bc, true);
}, },
text: AppLocalizations.of(context)!.yes, child: Text(Languages.of(context)!.yes),
), ),
PlatformButton( SimpleDialogOption(
onPressed: () { onPressed: () {
Navigator.pop(bc, false); Navigator.pop(bc, false);
}, },
text: AppLocalizations.of(context)!.no, child: Text(Languages.of(context)!.no),
), ),
], ],
), ));
);
if (d) { if (d) {
widget.canteen widget.canteen
.doBurzy(j) .doBurzy(j)
.then((_) => loadMeals()) .then((_) => nactiJidlo())
.catchError((o) { .catchError((o) {
showDialog( showDialog(
context: context, context: context,
builder: (bc) => PlatformDialog( builder: (bc) => AlertDialog(
title: title: Text(
AppLocalizations.of(context)!.exchangeError, Languages.of(context)!.exchangeError),
content: o.toString(), content: Text(o.toString()),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.close, child: Text(
Languages.of(context)!.close),
onPressed: () { onPressed: () {
Navigator.pop(bc); Navigator.pop(bc);
}, },
) )
], ],
), ));
);
}); });
} }
} else { } else {
// else no // jinak ne
widget.canteen.doBurzy(j).then((_) => loadMeals()); widget.canteen.doBurzy(j).then((_) => nactiJidlo());
} }
}, },
), ),
@ -333,113 +294,92 @@ class _MealViewState extends State<MealView> {
); );
} }
} }
}, kontrolaTyden(context);
); });
return; });
}).catchError((o) {
if (!widget.canteen.prihlasen) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (c) => const LoginPage()));
}
});
} }
Future<void> click(String value, BuildContext context) async { Future<void> kliknuti(String value, BuildContext context,
if (value == AppLocalizations.of(context)!.signOut) { FlutterLocalNotificationsPlugin n) async {
if (value == Languages.of(context)!.signOut) {
await showDialog<bool>( await showDialog<bool>(
context: context, context: context,
builder: (c) => PlatformDialog( builder: (c) => AlertDialog(
title: AppLocalizations.of(context)!.warning, title: Text(Languages.of(context)!.warning),
content: AppLocalizations.of(context)!.signOutWarn, content: Text(Languages.of(context)!.signOutWarn),
actions: [ actions: [
PlatformButton( TextButton(
onPressed: () { onPressed: () {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
storage.deleteAll(); storage.deleteAll();
Navigator.pushAndRemoveUntil( Navigator.pushReplacement(context,
context, MaterialPageRoute(builder: (c) => const LoginPage()));
platformRouter((c) => const LoginPage()),
(route) => false);
}, },
text: AppLocalizations.of(context)!.yes), child: Text(Languages.of(context)!.yes)),
PlatformButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
text: AppLocalizations.of(context)!.no, child: Text(Languages.of(context)!.no))
)
], ],
), ),
); );
} else if (value == AppLocalizations.of(context)!.review) { } else if (value == Languages.of(context)!.reportBugs) {
launchUrl( launchUrl(Uri.parse(
Uri.parse((Platform.isAndroid) "https://github.com/hernikplays/opencanteen/issues/new/choose"));
? "market://details?id=cz.hernikplays.opencanteen" } else if (value == Languages.of(context)!.about) {
: "https://apps.apple.com/cz/app/opencanteen/id1621124445"), Navigator.push(
mode: LaunchMode.externalApplication); context, MaterialPageRoute(builder: (c) => const AboutPage()));
} else if (value == AppLocalizations.of(context)!.reportBugs) { } else if (value == Languages.of(context)!.settings) {
launchUrl(Uri.parse("https://forms.gle/jKN7QeFJwpaApSbC8"), Navigator.push(
mode: LaunchMode.externalApplication); context, MaterialPageRoute(builder: (c) => Nastaveni(n: n)));
} else if (value == AppLocalizations.of(context)!.about) {
var packageInfo = await PackageInfo.fromPlatform();
if (!mounted) return;
showAboutDialog(
context: context,
applicationName: "OpenCanteen",
applicationLegalese:
"${AppLocalizations.of(context)!.copyright}\n${AppLocalizations.of(context)!.license}",
applicationVersion: packageInfo.version,
children: [
PlatformButton(
onPressed: (() => launchUrl(
Uri.parse("https://git.mnau.xyz/hernik/opencanteen"),
mode: LaunchMode.externalApplication)),
text: AppLocalizations.of(context)!.source,
)
]);
} else if (value == AppLocalizations.of(context)!.settings) {
Navigator.push(context, platformRouter((c) => const AndroidNastaveni()));
} }
} }
void loadSettings() async { void nactiNastaveni() async {
if (!mounted) return; var prefs = await SharedPreferences.getInstance();
checkWeek(context); _skipWeekend = prefs.getBool("skip") ?? false;
} }
void saveOffline() async { /// uložení jídelníčku pro dnešek offline
if (!settings.saveOffline) return; void ulozitDnesekOffline() async {
// clear offline storage var prefs = await SharedPreferences.getInstance();
if (prefs.getBool("offline") != null && prefs.getBool("offline")!) {
Directory appDocDir = await getApplicationDocumentsDirectory(); Directory appDocDir = await getApplicationDocumentsDirectory();
for (var f in appDocDir.listSync()) { for (var f in appDocDir.listSync()) {
// Vymažeme obsah
if (f.path.contains("jidelnicek")) { if (f.path.contains("jidelnicek")) {
f.deleteSync(); f.deleteSync();
} }
} }
var prefs = await SharedPreferences.getInstance();
// save X meal lists // Uložíme nová data
var pocet = prefs.getInt("offline_pocet") ?? 1; Jidelnicek j = Jidelnicek(DateTime.now(), []);
if (pocet > 7) pocet = 7;
for (var i = 0; i < pocet; i++) {
var d = day.add(Duration(days: i));
Jidelnicek? j;
try { try {
j = await widget.canteen.jidelnicekDen(den: d); j = await widget.canteen.jidelnicekDen();
} catch (e) { } catch (e) {
if (!widget.canteen.prihlasen) { if (!widget.canteen.prihlasen) {
if (!mounted) return; if (!mounted) return; // ! Přidat chybu, pokud není mounted
ScaffoldMessenger.of(context).hideCurrentSnackBar(); Navigator.pushReplacement(
ScaffoldMessenger.of(context).showSnackBar(SnackBar( context, MaterialPageRoute(builder: (c) => const LoginPage()));
content: Text(AppLocalizations.of(context)!.errorSaving),
duration: const Duration(seconds: 5),
));
break;
} }
} }
var soubor = File( var soubor = File(
"${appDocDir.path}/jidelnicek_${d.year}-${d.month}-${d.day}.json"); "${appDocDir.path}/jidelnicek_${den.year}-${den.month}-${den.day}.json");
soubor.createSync(); soubor.createSync();
var jidla = []; var jidla = [];
for (var jidlo in j!.jidla) { for (var jidlo in j.jidla) {
jidla.add({ jidla.add({
"nazev": jidlo.nazev, "nazev": jidlo.nazev,
"varianta": jidlo.varianta, "varianta": jidlo.varianta,
"objednano": jidlo.objednano, "objednano": jidlo.objednano,
"cena": jidlo.cena, "cena": jidlo.cena,
"naBurze": jidlo.naBurze, "naBurze": jidlo.naBurze,
"den": d.toString() "den": den.toString()
}); });
} }
await soubor.writeAsString(json.encode(jidla)); await soubor.writeAsString(json.encode(jidla));
@ -449,27 +389,26 @@ class _MealViewState extends State<MealView> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
loadSettings(); nactiNastaveni();
saveOffline(); ulozitDnesekOffline();
loadMeals(); nactiJidlo();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
drawer: drawerGenerator(context, widget.canteen, 1), drawer: drawerGenerator(context, widget.canteen, 1, widget.n),
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.menu), title: Text(Languages.of(context)!.menu),
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
onSelected: ((String value) => click(value, context)), onSelected: ((String value) => kliknuti(value, context, widget.n)),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return { return {
AppLocalizations.of(context)!.reportBugs, Languages.of(context)!.reportBugs,
AppLocalizations.of(context)!.review, Languages.of(context)!.settings,
AppLocalizations.of(context)!.settings, Languages.of(context)!.about,
AppLocalizations.of(context)!.about, Languages.of(context)!.signOut
AppLocalizations.of(context)!.signOut
}.map((String choice) { }.map((String choice) {
return PopupMenuItem<String>( return PopupMenuItem<String>(
value: choice, value: choice,
@ -481,102 +420,90 @@ class _MealViewState extends State<MealView> {
], ],
), ),
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: loadMeals, onRefresh: nactiJidlo,
child: Center( child: Center(
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width - 50,
child: Column( child: Column(
children: [ children: [
const SizedBox(height: 10), const SizedBox(height: 10),
Text("${AppLocalizations.of(context)!.balance}$balance"), Text("${Languages.of(context)!.balance}$kredit"),
Row( Row(mainAxisAlignment: MainAxisAlignment.center, children: [
mainAxisAlignment: MainAxisAlignment.center,
children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
day = day.subtract(const Duration(days: 1)); den = den.subtract(const Duration(days: 1));
if (day.weekday == 7 && settings.skipWeekend) { if (den.weekday == 7 && _skipWeekend) {
day = day.subtract(const Duration(days: 2)); den = den.subtract(const Duration(days: 2));
} }
loadMeals(); nactiJidlo();
}); });
}, },
icon: const Icon(Icons.arrow_left)), icon: const Icon(Icons.arrow_left)),
PlatformButton( TextButton(
onPressed: () async { onPressed: () async {
var datePicked = await showDatePicker( var datePicked = await showDatePicker(
context: context, context: context,
initialDate: day, initialDate: den,
currentDate: day, currentDate: den,
firstDate: DateTime(2019, 1, 1), firstDate: DateTime(2019, 1, 1),
lastDate: DateTime(day.year + 1, 12, 31), lastDate: DateTime(den.year + 1, 12, 31),
locale: Localizations.localeOf(context)); locale: Localizations.localeOf(context));
if (datePicked == null) return; if (datePicked == null) return;
setState(() { setState(() {
day = datePicked; den = datePicked;
loadMeals(); nactiJidlo();
}); });
}, },
text: "${day.day}. ${day.month}. ${day.year} - $dayOWeek", child: Text(
), "${den.day}. ${den.month}. ${den.year} - $denTydne")),
IconButton( IconButton(
onPressed: () { onPressed: () {
setState(() { setState(() {
day = day.add(const Duration(days: 1)); den = den.add(const Duration(days: 1));
if (day.weekday == 6 && settings.skipWeekend) { if (den.weekday == 6 && _skipWeekend) {
day = day.add(const Duration(days: 2)); den = den.add(const Duration(days: 2));
} }
loadMeals(); nactiJidlo();
}); });
}, },
icon: const Icon(Icons.arrow_right), icon: const Icon(Icons.arrow_right),
), ),
Tooltip( IconButton(
message: AppLocalizations.of(context)!.todayTooltip, onPressed: () => setState(() {
child: IconButton( den = DateTime.now();
onPressed: () => setState( nactiJidlo();
() { }),
day = DateTime.now(); icon: const Icon(Icons.today))
loadMeals(); ]),
},
),
icon: const Icon(Icons.today),
),
)
],
),
SingleChildScrollView( SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: GestureDetector( child: GestureDetector(
child: Container( child: Container(
padding: const EdgeInsets.only(left: 20, right: 20),
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
.onPrimary .onPrimary
.withOpacity(0), .withOpacity(0),
height: MediaQuery.of(context).size.height / 1.3, height: MediaQuery.of(context).size.height / 1.3,
child: Column(children: content), child: Column(children: obsah),
), ),
onHorizontalDragEnd: (details) { onHorizontalDragEnd: (details) {
if (details.primaryVelocity?.compareTo(0) == -1) { if (details.primaryVelocity?.compareTo(0) == -1) {
setState(() { setState(() {
day = day.add(const Duration(days: 1)); den = den.add(const Duration(days: 1));
if (day.weekday == 6 && settings.skipWeekend) { if (den.weekday == 6 && _skipWeekend) {
day = day.add(const Duration(days: 2)); den = den.add(const Duration(days: 2));
} }
loadMeals(); nactiJidlo();
}); });
} else { } else {
setState( setState(() {
() { den = den.subtract(const Duration(days: 1));
day = day.subtract(const Duration(days: 1)); if (den.weekday == 7 && _skipWeekend) {
if (day.weekday == 7 && settings.skipWeekend) { den = den.subtract(const Duration(days: 2));
day = day.subtract(const Duration(days: 2));
} }
loadMeals(); nactiJidlo();
}, });
);
} }
}, },
), ),

View file

@ -1,247 +0,0 @@
import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/okna/welcome.dart';
import 'package:opencanteen/pw/platformbutton.dart';
import 'package:opencanteen/pw/platformfield.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../../loginmanager.dart';
import '../../util.dart';
import '../pw/platformswitch.dart';
import 'jidelnicek.dart';
import 'offline_jidelnicek.dart';
class LoginPage extends StatefulWidget {
const LoginPage({super.key});
@override
State<LoginPage> createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
TextEditingController userControl = TextEditingController();
TextEditingController passControl = TextEditingController();
TextEditingController canteenControl = TextEditingController();
bool rememberMe = false;
bool _showUrl = false;
String dropdownUrl = instance.first["url"] ?? "";
@override
void initState() {
super.initState();
LoginManager.getDetails().then((r) async {
if (r != null) {
// Autologin
showDialog(
context: context,
barrierDismissible: false,
builder: (_) => Dialog(
child: SizedBox(
height: 100,
child: Row(children: [
const Padding(
padding: EdgeInsets.all(10),
child: CircularProgressIndicator(),
),
Text(AppLocalizations.of(context)!.loggingIn)
]),
),
));
var canteen = Canteen(r["url"]!);
try {
var l = await canteen.login(r["user"]!, r["pass"]!);
if (!l) {
if (!mounted) return;
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(AppLocalizations.of(context)!.loginFailed),
),
);
return;
}
const storage = FlutterSecureStorage();
var odsouhlasil = await storage.read(key: "oc_souhlas");
if (!mounted) return;
if (odsouhlasil == null || odsouhlasil != "ano") {
Navigator.pushAndRemoveUntil(
context,
platformRouter(
(c) => WelcomePage(canteen: canteen),
),
(route) => false);
} else {
Navigator.pushAndRemoveUntil(
context,
platformRouter(
(context) => MealView(canteen: canteen),
),
(route) => false);
}
} on PlatformException {
if (!mounted) return;
Navigator.of(context).pop();
showInfo(context, AppLocalizations.of(context)!.corrupted);
} catch (_) {
if (!mounted) return;
Navigator.of(context).pop();
showInfo(context, AppLocalizations.of(context)!.errorContacting);
goOffline();
}
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppLocalizations.of(context)!.logIn),
automaticallyImplyLeading: false,
),
body: Center(
child: SingleChildScrollView(
child: SizedBox(
width: MediaQuery.of(context).size.width - 50,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
AppLocalizations.of(context)!.appName,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 40),
),
Text(
AppLocalizations.of(context)!.logIn,
textAlign: TextAlign.center,
),
PlatformField(
controller: userControl,
autofillHints: const [AutofillHints.username],
labelText: AppLocalizations.of(context)!.username,
),
PlatformField(
autofillHints: const [AutofillHints.password],
labelText: AppLocalizations.of(context)!.password,
controller: passControl,
obscureText: true,
),
const SizedBox(
height: 10,
),
DropdownButton(
isExpanded: true,
value: dropdownUrl,
items: instance.map<DropdownMenuItem<String>>((e) {
return DropdownMenuItem<String>(
value: e["url"],
child: Text(e["name"]!),
);
}).toList(),
onChanged: (String? value) {
setState(() {
if (value == "") {
_showUrl = true;
} else {
_showUrl = false;
}
dropdownUrl = value!;
});
},
),
AnimatedOpacity(
opacity: _showUrl ? 1.0 : 0.0,
duration: const Duration(milliseconds: 300),
child: PlatformField(
autofillHints: const [AutofillHints.url],
labelText: AppLocalizations.of(context)!.iCanteenUrl,
keyboardType: TextInputType.url,
controller: canteenControl,
),
),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [
PlatformSwitch(
value: rememberMe,
onChanged: (value) {
setState(() {
rememberMe = value;
});
},
),
Text(AppLocalizations.of(context)!.rememberMe)
]),
PlatformButton(
onPressed: () async {
var canteenUrl = (dropdownUrl == "")
? canteenControl.text
: dropdownUrl;
if (!canteenUrl.startsWith("https://") &&
!canteenUrl.startsWith("http://")) {
canteenUrl = "https://$canteenUrl";
}
var canteen = Canteen(canteenUrl);
try {
var l = await canteen.login(
userControl.text, passControl.text);
if (!l) {
if (!mounted) return;
showInfo(context,
AppLocalizations.of(context)!.loginFailed);
return;
}
if (rememberMe) {
LoginManager.setDetails(
userControl.text, passControl.text, canteenUrl);
}
// souhlas
const storage = FlutterSecureStorage();
var odsouhlasil = await storage.read(key: "oc_souhlas");
if (!mounted) return;
if (odsouhlasil == null || odsouhlasil != "ano") {
Navigator.pushAndRemoveUntil(
context,
platformRouter(
(context) => WelcomePage(
canteen: canteen,
),
),
(route) => false);
} else {
Navigator.pushAndRemoveUntil(
context,
platformRouter(
(context) => MealView(
canteen: canteen,
),
),
(route) => false);
}
} on PlatformException {
if (!mounted) return;
showInfo(
context, AppLocalizations.of(context)!.corrupted);
} on Exception catch (_) {
if (!mounted) return;
showInfo(context,
AppLocalizations.of(context)!.errorContacting);
//goOffline();
}
},
text: AppLocalizations.of(context)!.logIn),
],
),
),
),
),
);
}
/// Switch to offline view
void goOffline() async {
if (!mounted) return;
Navigator.pushAndRemoveUntil(context,
platformRouter((context) => const OfflineMealView()), (route) => false);
}
}

View file

@ -2,267 +2,208 @@ import 'dart:io';
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_native_timezone/flutter_native_timezone.dart'; import 'package:flutter_native_timezone/flutter_native_timezone.dart';
import 'package:opencanteen/pw/platformbutton.dart';
import 'package:opencanteen/pw/platformdialog.dart';
import 'package:opencanteen/pw/platformfield.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:settings_ui/settings_ui.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:timezone/timezone.dart' as tz; import 'package:timezone/timezone.dart' as tz;
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../lang/lang.dart';
import '../../loginmanager.dart'; import '../loginmanager.dart';
import '../../main.dart'; import '../util.dart';
import '../../util.dart';
class AndroidNastaveni extends StatefulWidget { class Nastaveni extends StatefulWidget {
const AndroidNastaveni({super.key}); const Nastaveni({Key? key, required this.n}) : super(key: key);
final FlutterLocalNotificationsPlugin n;
@override @override
State<AndroidNastaveni> createState() => _AndroidNastaveniState(); State<Nastaveni> createState() => _NastaveniState();
} }
class _AndroidNastaveniState extends State<AndroidNastaveni> { class _NastaveniState extends State<Nastaveni> {
bool _saveOffline = false; bool _ukladatOffline = false;
bool _skipWeekend = false; bool _preskakovatVikend = false;
bool _checkWeek = false; bool _kontrolovatTyden = false;
bool _notifyMeal = false; bool _oznameniObed = false;
bool _remember = false; bool _zapamatovany = false;
bool _allergens = false; TimeOfDay _oznameniCas = TimeOfDay.now();
TimeOfDay _notifTime = TimeOfDay.now();
final TextEditingController _countController =
TextEditingController(text: "1");
SharedPreferences? preferences;
void loadSettings() async { void najitNastaveni() async {
preferences = await SharedPreferences.getInstance(); var preferences = await SharedPreferences.getInstance();
_remember = await LoginManager.rememberme(); _zapamatovany = await LoginManager.zapamatovat();
setState( setState(() {
() { _ukladatOffline = preferences.getBool("offline") ?? false;
_saveOffline = preferences!.getBool("offline") ?? false; _preskakovatVikend = preferences.getBool("skip") ?? false;
_skipWeekend = preferences!.getBool("skip") ?? false; _kontrolovatTyden = preferences.getBool("tyden") ?? false;
_checkWeek = preferences!.getBool("tyden") ?? false; _oznameniObed = preferences.getBool("oznamit") ?? false;
_notifyMeal = preferences!.getBool("oznamit") ?? false; var casStr = preferences.getString("oznameni_cas");
_countController.text =
(preferences!.getInt("offline_pocet") ?? 1).toString();
var casStr = preferences!.getString("oznameni_cas");
if (casStr == null) { if (casStr == null) {
var now = DateTime.now(); var now = DateTime.now();
_notifTime = TimeOfDay.fromDateTime( _oznameniCas = TimeOfDay.fromDateTime(
DateTime.now().add(const Duration(hours: 1))); DateTime.now().add(const Duration(hours: 1)));
preferences!.setString("oznameni_cas", now.toString()); preferences.setString("oznameni_cas", now.toString());
} else { } else {
_notifTime = TimeOfDay.fromDateTime(DateTime.parse(casStr)); _oznameniCas = TimeOfDay.fromDateTime(DateTime.parse(casStr));
} }
}, });
);
} }
void changeSetting(String key, bool value) async { void zmenitNastaveni(String key, bool value) async {
preferences!.setBool(key, value); var preferences = await SharedPreferences.getInstance();
preferences.setBool(key, value);
} }
@override @override
void initState() { void initState() {
super.initState(); super.initState();
loadSettings(); najitNastaveni();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.settings), title: Text(Languages.of(context)!.settings),
), ),
body: SettingsList( body: Center(
platform: DevicePlatform.device, child: SizedBox(
sections: [ width: MediaQuery.of(context).size.width / 1.1,
SettingsSection( child: Column(
tiles: [
SettingsTile.switchTile(
initialValue: _allergens,
onToggle: (value) {
_allergens = value;
settings.allergens = value;
changeSetting("allergens", _allergens);
setState(() {});
},
title: Text(AppLocalizations.of(context)!.showAllergens),
),
SettingsTile.switchTile(
initialValue: _saveOffline,
onToggle: (value) {
_saveOffline = value;
settings.saveOffline = value;
changeSetting("offline", value);
setState(() {});
},
title: Text(AppLocalizations.of(context)!.saveOffline),
),
CustomSettingsTile(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
SizedBox( Row(
width: MediaQuery.of(context).size.width * 0.76, mainAxisAlignment: MainAxisAlignment.spaceBetween,
child: Text( children: [
AppLocalizations.of(context)!.saveCount, Text(Languages.of(context)!.saveOffline),
softWrap: true, Switch(
style: TextStyle( value: _ukladatOffline,
fontSize: (Platform.isAndroid) ? 18 : 17, onChanged: (value) {
fontWeight: setState(() {
(Platform.isAndroid) ? FontWeight.w400 : null, _ukladatOffline = value;
), cistit(value);
), zmenitNastaveni("offline", value);
), });
const SizedBox( })
width: 10,
),
SizedBox(
width: 35,
child: PlatformField(
controller: _countController,
enabled: _saveOffline,
keyboardType: TextInputType.number,
inputFormatters: [
FilteringTextInputFormatter.digitsOnly
],
onChanged: (c) {
var cislo = int.tryParse(c);
if (cislo != null) {
preferences!.setInt("offline_pocet", cislo);
}
},
),
)
], ],
), ),
), Row(
SettingsTile.switchTile( mainAxisAlignment: MainAxisAlignment.spaceBetween,
initialValue: _skipWeekend, children: [
onToggle: (value) { Text(Languages.of(context)!.skipWeekend),
_skipWeekend = value; Switch(
settings.skipWeekend = value; value: _preskakovatVikend,
changeSetting("skip", value); onChanged: (value) {
setState(() {}); setState(() {
}, _preskakovatVikend = value;
title: Text(AppLocalizations.of(context)!.skipWeekend), zmenitNastaveni("skip", value);
), });
SettingsTile.switchTile( })
initialValue: _checkWeek,
onToggle: (value) {
_checkWeek = value;
settings.checkOrdered = value;
changeSetting("tyden", value);
setState(() {});
},
title: Text(AppLocalizations.of(context)!.checkOrdered),
),
], ],
title: Text(AppLocalizations.of(context)!.settingsExperience),
), ),
SettingsSection( Row(
tiles: [ mainAxisAlignment: MainAxisAlignment.spaceBetween,
SettingsTile.switchTile( children: [
initialValue: _notifyMeal, Flexible(child: Text(Languages.of(context)!.checkOrdered)),
enabled: _remember, Switch(
onToggle: (value) { value: _kontrolovatTyden,
if (!_remember) { onChanged: (value) {
setState(() {
_kontrolovatTyden = value;
zmenitNastaveni("tyden", value);
});
})
],
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(Languages.of(context)!.notifyLunch)),
Switch(
value: _oznameniObed,
thumbColor: (!_zapamatovany
? MaterialStateProperty.all(Colors.grey)
: null),
onChanged: (value) {
if (!_zapamatovany) {
showDialog( showDialog(
context: context, context: context,
builder: (bc) => PlatformDialog( builder: (bc) => AlertDialog(
title: AppLocalizations.of(context)!.error, title: Text(Languages.of(context)!.error),
content: AppLocalizations.of(context)!.needRemember, content:
Text(Languages.of(context)!.needRemember),
actions: [ actions: [
PlatformButton( TextButton(
text: AppLocalizations.of(context)!.ok, child: Text(Languages.of(context)!.ok),
onPressed: () {
Navigator.of(bc).pop();
},
)
],
),
);
} else {
_notifyMeal = value;
if (_notifyMeal) {
showDialog(
context: context,
builder: (context) => PlatformDialog(
title: AppLocalizations.of(context)!.warning,
content: AppLocalizations.of(context)!.notifyWarning,
actions: [
PlatformButton(
text: AppLocalizations.of(context)!.ok,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )
], ],
), ));
); } else {
createNotif(timeToDate(_notifTime)); setState(() {
} _oznameniObed = value;
changeSetting("oznamit", value); if (_oznameniObed) {
setState(() {}); showDialog(
} context: context,
builder: (context) => AlertDialog(
title:
Text(Languages.of(context)!.warning),
content: Text(
Languages.of(context)!.notifyWarning),
actions: [
TextButton(
child:
Text(Languages.of(context)!.ok),
onPressed: () {
Navigator.of(context).pop();
}, },
title: Text( )
AppLocalizations.of(context)!.notifyLunch, ],
));
vytvoritOznameni(casNaDate(_oznameniCas));
}
zmenitNastaveni("oznamit", value);
});
}
})
],
), ),
), Text(Languages.of(context)!.notifyAt),
CustomSettingsTile( TextButton(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
AppLocalizations.of(context)!.notifyAt,
style: TextStyle(
fontSize: (Platform.isAndroid) ? 18 : 17,
fontWeight:
(Platform.isAndroid) ? FontWeight.w400 : null,
),
),
PlatformButton(
textStyle:
TextStyle(fontSize: (Platform.isAndroid ? 18 : 17)),
text: _notifTime.format(context),
onPressed: () async { onPressed: () async {
if (!_notifyMeal) return; if (_oznameniObed) {
var cas = await showTimePicker( var cas = await showTimePicker(
context: context, initialTime: _notifTime); context: context, initialTime: _oznameniCas);
if (cas == null) return; if (cas != null) {
var prefs = await SharedPreferences.getInstance(); var prefs = await SharedPreferences.getInstance();
prefs.setString( prefs.setString("oznameni_cas",
"oznameni_cas", casNaDate(cas).toString()); // aktualizovat vybraný čas
timeToDate(cas) var den = casNaDate(cas);
.toString()); // aktualizovat vybraný čas
var den = timeToDate(cas);
debugPrint(den.isAfter(DateTime.now()).toString()); debugPrint(den.isAfter(DateTime.now()).toString());
if (den.isAfter(DateTime.now())) { if (den.isAfter(DateTime.now())) {
// znovu vytvořit oznámení POUZE když je čas v budoucnosti // znovu vytvořit oznámení POUZE když je čas v budoucnosti
createNotif(den); vytvoritOznameni(den);
}
}
setState(() {
_oznameniCas = cas ?? _oznameniCas;
});
} }
_notifTime = cas;
setState(() {});
}, },
) child: Text(
], "${(_oznameniCas.hour < 10 ? "0" : "") + _oznameniCas.hour.toString()}:${(_oznameniCas.minute < 10 ? "0" : "") + _oznameniCas.minute.toString()}",
), style: TextStyle(
) color: (!_oznameniObed) ? Colors.grey : Colors.purple),
], ),
title: Text(AppLocalizations.of(context)!.settingsFunctions), ),
)
], ],
), ),
)),
); );
} }
void clear(bool value) async { void cistit(bool value) async {
if (!value) { if (!value) {
Directory appDocDir = await getApplicationDocumentsDirectory(); Directory appDocDir = await getApplicationDocumentsDirectory();
for (var f in appDocDir.listSync()) { for (var f in appDocDir.listSync()) {
@ -274,10 +215,11 @@ class _AndroidNastaveniState extends State<AndroidNastaveni> {
} }
} }
void createNotif(DateTime den) async { void vytvoritOznameni(DateTime den) async {
await flutterLocalNotificationsPlugin.cancelAll(); await widget.n.cancelAll();
var d = await LoginManager.getDetails(); // grab details var d = await LoginManager.getDetails("0"); // získat údaje TODO: změnit
if (d != null) { if (d != null) {
// Nové oznámení
var c = Canteen(d["url"]!); var c = Canteen(d["url"]!);
if (await c.login(d["user"]!, d["pass"]!)) { if (await c.login(d["user"]!, d["pass"]!)) {
var jidla = await c.jidelnicekDen(); var jidla = await c.jidelnicekDen();
@ -290,34 +232,23 @@ class _AndroidNastaveniState extends State<AndroidNastaveni> {
importance: Importance.max, importance: Importance.max,
priority: Priority.high, priority: Priority.high,
ticker: 'today meal'); ticker: 'today meal');
const IOSNotificationDetails iOSpec =
IOSNotificationDetails(presentAlert: true, presentBadge: true);
var l = var l =
tz.getLocation(await FlutterNativeTimezone.getLocalTimezone()); tz.getLocation(await FlutterNativeTimezone.getLocalTimezone());
var notifGranted = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestNotificationsPermission() ??
true; // request android notification access
var granted = await flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestExactAlarmsPermission() ??
true; // request android exact alarm permission
if (!granted || !notifGranted) return;
if (!mounted) return; if (!mounted) return;
await flutterLocalNotificationsPlugin.zonedSchedule( await widget.n.zonedSchedule(
// schedules a notification // Vytvoří nové oznámení pro daný čas a datum
0, 0,
AppLocalizations.of(context)!.lunchNotif, Languages.of(context)!.lunchNotif,
"${jidlo.varianta} - ${jidlo.nazev}", "${jidlo.varianta} - ${jidlo.nazev}",
tz.TZDateTime.from(den, l), tz.TZDateTime.from(den, l),
const NotificationDetails(android: androidSpec), const NotificationDetails(android: androidSpec, iOS: iOSpec),
androidScheduleMode: AndroidScheduleMode.exactAllowWhileIdle, androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation: uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime); UILocalNotificationDateInterpretation.absoluteTime);
} on StateError catch (_) { } on StateError catch (_) {
// no meal found // nenalezeno
} }
} }
} }

View file

@ -1,179 +1,115 @@
import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:opencanteen/okna/login.dart';
import 'package:opencanteen/pw/platformbutton.dart';
import 'package:opencanteen/util.dart'; import 'package:opencanteen/util.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../lang/lang.dart';
import '../main.dart';
import 'about.dart';
class OfflineMealView extends StatefulWidget { class OfflineJidelnicek extends StatefulWidget {
const OfflineMealView({super.key}); const OfflineJidelnicek({Key? key, required this.jidla}) : super(key: key);
final List<OfflineJidlo> jidla;
@override @override
State<OfflineMealView> createState() => _OfflineMealViewState(); State<OfflineJidelnicek> createState() => _OfflineJidelnicekState();
} }
class _OfflineMealViewState extends State<OfflineMealView> { class _OfflineJidelnicekState extends State<OfflineJidelnicek> {
List<Widget> content = [const CircularProgressIndicator()]; // view content List<Widget> obsah = [const CircularProgressIndicator()];
var _skipWeekend = false; // skip weekend setting DateTime den = DateTime.now();
DateTime currentDay = DateTime.now(); // the day we are supposed to show String denTydne = "";
String dayOWeek = ""; // the name of the day (to show to user) Future<void> nactiJidlo() async {
List<List<OfflineMeal>> data = []; // meal data den = widget.jidla[0].den;
var mealIndex = 0; // index of the currently shown day switch (den.weekday) {
/// Loads the offline data from local storage
void loadFromFile() async {
Directory appDocDir = await getApplicationDocumentsDirectory();
for (var f in appDocDir.listSync()) {
if (f.path.contains("jidelnicek")) {
var soubor = File(f.path);
var input = await soubor.readAsString();
var r = jsonDecode(input);
List<OfflineMeal> jidla = [];
for (var j in r) {
jidla.add(OfflineMeal(
name: j["nazev"],
variant: j["varianta"],
ordered: j["objednano"],
price: j["cena"],
onExchange: j["naBurze"],
day: DateTime.parse(j["den"])));
}
data.add(jidla);
}
}
loadFood();
}
Future<void> loadFood() async {
var jidelnicek = data[mealIndex];
currentDay = jidelnicek[0].day;
switch (currentDay.weekday) {
case 2: case 2:
dayOWeek = AppLocalizations.of(context)!.tuesday; denTydne = Languages.of(context)!.tuesday;
break; break;
case 3: case 3:
dayOWeek = AppLocalizations.of(context)!.wednesday; denTydne = Languages.of(context)!.wednesday;
break; break;
case 4: case 4:
dayOWeek = AppLocalizations.of(context)!.thursday; denTydne = Languages.of(context)!.thursday;
break; break;
case 5: case 5:
dayOWeek = AppLocalizations.of(context)!.friday; denTydne = Languages.of(context)!.friday;
break; break;
case 6: case 6:
dayOWeek = AppLocalizations.of(context)!.saturday; denTydne = Languages.of(context)!.saturday;
break; break;
case 7: case 7:
dayOWeek = AppLocalizations.of(context)!.sunday; denTydne = Languages.of(context)!.sunday;
break; break;
default: default:
dayOWeek = AppLocalizations.of(context)!.monday; denTydne = Languages.of(context)!.monday;
} }
content = []; obsah = [];
for (OfflineMeal j in jidelnicek) { for (OfflineJidlo j in widget.jidla) {
content.add( obsah.add(
Padding( Padding(
padding: const EdgeInsets.only(top: 15), padding: const EdgeInsets.only(top: 15),
child: InkWell( child: InkWell(
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(j.variant), Text(j.varianta),
const SizedBox(width: 10), const SizedBox(width: 10),
Flexible( Flexible(
child: Text( child: Text(
j.name, j.nazev,
), ),
), ),
Text((j.onExchange) Text((j.naBurze)
? AppLocalizations.of(context)!.inExchange ? Languages.of(context)!.inExchange
: "${j.price}"), : "${j.cena}"),
Checkbox( Checkbox(
value: j.ordered, value: j.objednano,
fillColor: MaterialStateProperty.all(Colors.grey), fillColor: MaterialStateProperty.all(Colors.grey),
onChanged: (v) async { onChanged: (v) async {
return; return;
}, })
)
], ],
), )),
),
), ),
); );
} }
setState(() {}); setState(() {});
} }
void click(String value, BuildContext context) async { void kliknuti(String value, BuildContext context) {
if (value == AppLocalizations.of(context)!.signOut) { if (value == Languages.of(context)!.signOut) {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
storage.deleteAll(); storage.deleteAll();
Navigator.pushReplacement( Navigator.pushReplacement(
context, platformRouter((c) => const LoginPage())); context, MaterialPageRoute(builder: (c) => const LoginPage()));
} else if (value == AppLocalizations.of(context)!.review) { } else if (value == Languages.of(context)!.reportBugs) {
launchUrl( launchUrl(Uri.parse(
Uri.parse((Platform.isAndroid) "https://github.com/hernikplays/opencanteen/issues/new/choose"));
? "market://details?id=cz.hernikplays.opencanteen" } else if (value == Languages.of(context)!.about) {
: "https://apps.apple.com/cz/app/opencanteen/id1621124445"), Navigator.push(
mode: LaunchMode.externalApplication); context, MaterialPageRoute(builder: (c) => const AboutPage()));
} else if (value == AppLocalizations.of(context)!.reportBugs) {
launchUrl(Uri.parse("https://forms.gle/jKN7QeFJwpaApSbC8"),
mode: LaunchMode.externalApplication);
} else if (value == AppLocalizations.of(context)!.about) {
var packageInfo = await PackageInfo.fromPlatform();
if (!mounted) return;
showAboutDialog(
context: context,
applicationName: "OpenCanteen",
applicationLegalese:
"${AppLocalizations.of(context)!.copyright}\n${AppLocalizations.of(context)!.license}",
applicationVersion: packageInfo.version,
children: [
PlatformButton(
onPressed: (() => launchUrl(
Uri.parse("https://git.mnau.xyz/hernik/opencanteen"))),
text: AppLocalizations.of(context)!.source,
)
],
);
} }
} }
@override @override
void didChangeDependencies() { void didChangeDependencies() {
super.didChangeDependencies(); super.didChangeDependencies();
loadSettings(); nactiJidlo();
}
void loadSettings() async {
var prefs = await SharedPreferences.getInstance();
_skipWeekend = prefs.getBool("skip") ?? false;
if (!mounted) return;
loadFromFile();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
title: Text(AppLocalizations.of(context)!.menu), title: Text(Languages.of(context)!.menu),
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
actions: [ actions: [
PopupMenuButton( PopupMenuButton(
onSelected: ((String value) => click(value, context)), onSelected: ((String value) => kliknuti(value, context)),
itemBuilder: (BuildContext context) { itemBuilder: (BuildContext context) {
return { return {
AppLocalizations.of(context)!.reportBugs, Languages.of(context)!.reportBugs,
AppLocalizations.of(context)!.review, Languages.of(context)!.about,
AppLocalizations.of(context)!.about, Languages.of(context)!.signOut
AppLocalizations.of(context)!.signOut
}.map((String choice) { }.map((String choice) {
return PopupMenuItem<String>( return PopupMenuItem<String>(
value: choice, value: choice,
@ -192,78 +128,28 @@ class _OfflineMealViewState extends State<OfflineMealView> {
children: [ children: [
const SizedBox(height: 10), const SizedBox(height: 10),
Text( Text(
AppLocalizations.of(context)!.offline, Languages.of(context)!.offline,
style: const TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
Text(AppLocalizations.of(context)!.mustLogout), Text(Languages.of(context)!.mustLogout),
const SizedBox(height: 10), const SizedBox(height: 10),
Row(mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton(
IconButton( child:
onPressed: () { Text("${den.day}. ${den.month}. ${den.year} - $denTydne"),
if (data.length <= 1) return; onPressed: () => {},
content = [const CircularProgressIndicator()];
setState(() {
if (currentDay.weekday == 1 && _skipWeekend) {
// pokud je pondělí a chceme přeskočit víkend
if (mealIndex - 2 >= 0) {
mealIndex -= data.length - 3;
} else {
mealIndex = data.length - 1;
}
} else if (mealIndex == 0) {
mealIndex = data.length - 1;
} else {
mealIndex -= 1;
}
loadFood();
});
},
icon: const Icon(Icons.arrow_left)),
PlatformButton(
onPressed: () async {},
text:
"${currentDay.day}. ${currentDay.month}. ${currentDay.year} - $dayOWeek"),
IconButton(
onPressed: () {
if (data.length <= 1) return;
content = [const CircularProgressIndicator()];
setState(() {
if (currentDay.weekday == 5 && _skipWeekend) {
// pokud je pondělí a chceme přeskočit víkend
if (mealIndex + 2 <= data.length - 1) {
mealIndex += 2;
} else {
mealIndex = 0;
}
} else if (mealIndex == data.length) {
mealIndex = 0;
} else {
mealIndex += 1;
}
loadFood();
});
},
icon: const Icon(Icons.arrow_right),
), ),
IconButton(
onPressed: () {
mealIndex = 0;
},
icon: const Icon(Icons.today))
]),
SingleChildScrollView( SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(), physics: const AlwaysScrollableScrollPhysics(),
child: Column( child: Column(
children: content, children: obsah,
), ),
), ),
], ],
), ),
), ),
), ),
onRefresh: () => Navigator.pushReplacement( onRefresh: () => Navigator.pushReplacement(context,
context, platformRouter((context) => const LoginPage())), MaterialPageRoute(builder: ((context) => const LoginPage()))),
), ),
); );
} }

View file

@ -1,58 +1,60 @@
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:introduction_screen/introduction_screen.dart'; import 'package:introduction_screen/introduction_screen.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:opencanteen/lang/lang.dart';
import 'package:opencanteen/okna/jidelnicek.dart'; import 'package:opencanteen/okna/jidelnicek.dart';
import 'package:opencanteen/util.dart';
class WelcomePage extends StatefulWidget { class WelcomeScreen extends StatefulWidget {
const WelcomePage({super.key, required this.canteen}); const WelcomeScreen({Key? key, required this.canteen, required this.n})
: super(key: key);
final Canteen canteen; final Canteen canteen;
final FlutterLocalNotificationsPlugin n;
@override @override
State<WelcomePage> createState() => _WelcomePageState(); State<WelcomeScreen> createState() => _WelcomeScreenState();
} }
class _WelcomePageState extends State<WelcomePage> { class _WelcomeScreenState extends State<WelcomeScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var listPagesViewModel = [ var listPagesViewModel = [
PageViewModel( PageViewModel(
title: AppLocalizations.of(context)!.welcome, title: Languages.of(context)!.welcome,
body: AppLocalizations.of(context)!.appDesc, body: Languages.of(context)!.appDesc,
image: const Center( image: const Center(
child: Icon(Icons.waving_hand_outlined, size: 175), child: Icon(Icons.waving_hand_outlined, size: 175),
), ),
), ),
PageViewModel( PageViewModel(
title: AppLocalizations.of(context)!.aboutOrder, title: Languages.of(context)!.aboutOrder,
body: AppLocalizations.of(context)!.howOrder, body: Languages.of(context)!.howOrder,
image: Center( image: Center(
child: Image.asset('assets/objednavam.png', child: Image.asset('assets/objednavam.png',
width: MediaQuery.of(context).size.width * 0.85), width: MediaQuery.of(context).size.width * 0.85),
), ),
), ),
PageViewModel( PageViewModel(
title: AppLocalizations.of(context)!.aboutToExch, title: Languages.of(context)!.aboutToExch,
body: AppLocalizations.of(context)!.howToExch, body: Languages.of(context)!.howToExch,
image: Center( image: Center(
child: Image.asset('assets/doburzy.png', child: Image.asset('assets/doburzy.png',
width: MediaQuery.of(context).size.width * 0.85), width: MediaQuery.of(context).size.width * 0.85),
), ),
), ),
PageViewModel( PageViewModel(
title: AppLocalizations.of(context)!.aboutFromExch, title: Languages.of(context)!.aboutFromExch,
body: AppLocalizations.of(context)!.howFromExch, body: Languages.of(context)!.howFromExch,
image: Center( image: Center(
child: Image.asset('assets/burza.png', child: Image.asset('assets/burza.png',
width: MediaQuery.of(context).size.width * 0.85), width: MediaQuery.of(context).size.width * 0.85),
), ),
), ),
PageViewModel( PageViewModel(
title: AppLocalizations.of(context)!.warning, title: Languages.of(context)!.warning,
body: AppLocalizations.of(context)!.notOfficial, body: Languages.of(context)!.notOfficial,
image: const Center( image: const Center(
child: Icon(Icons.warning_amber_outlined, size: 175), child: Icon(Icons.warning_amber_outlined, size: 175),
), ),
@ -61,16 +63,16 @@ class _WelcomePageState extends State<WelcomePage> {
return Scaffold( return Scaffold(
body: IntroductionScreen( body: IntroductionScreen(
pages: listPagesViewModel, pages: listPagesViewModel,
next: Text(AppLocalizations.of(context)!.next), next: Text(Languages.of(context)!.next),
done: Text(AppLocalizations.of(context)!.ok, done: Text(Languages.of(context)!.ok,
style: const TextStyle(fontWeight: FontWeight.w600)), style: const TextStyle(fontWeight: FontWeight.w600)),
onDone: () async { onDone: () async {
const storage = FlutterSecureStorage(); const storage = FlutterSecureStorage();
await storage.write(key: "oc_souhlas", value: "ano"); await storage.write(key: "oc_souhlas", value: "ano");
if (!mounted) return; if (!mounted) return;
Navigator.of(context).pushAndRemoveUntil( Navigator.of(context).pushReplacement(MaterialPageRoute(
platformRouter((c) => MealView(canteen: widget.canteen)), builder: (c) =>
(route) => false); JidelnicekPage(canteen: widget.canteen, n: widget.n)));
}, },
), ),
); );

View file

@ -1,27 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:opencanteen/pw/platformwidget.dart';
class PlatformButton extends PlatformWidget<TextButton, CupertinoButton> {
final String text;
final void Function()? onPressed;
final TextStyle? textStyle;
const PlatformButton(
{super.key, required this.text, required this.onPressed, this.textStyle});
@override
TextButton createAndroidWidget(BuildContext context) => TextButton(
onPressed: onPressed,
child: Text(
text,
style: textStyle,
),
);
@override
CupertinoButton createIosWidget(BuildContext context) => CupertinoButton(
onPressed: onPressed,
child: Text(text, style: textStyle),
);
}

View file

@ -1,30 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:opencanteen/pw/platformwidget.dart';
class PlatformDialog extends PlatformWidget<AlertDialog, CupertinoAlertDialog> {
final String title;
final String? content;
final List<Widget> actions;
const PlatformDialog(
{super.key, required this.title, this.content, this.actions = const []});
@override
AlertDialog createAndroidWidget(BuildContext context) => AlertDialog(
title: Text(title),
content: (content != null)
? SingleChildScrollView(child: Text(content!))
: null,
actions: actions,
);
@override
CupertinoAlertDialog createIosWidget(BuildContext context) =>
CupertinoAlertDialog(
title: Text(title),
content: (content != null)
? SingleChildScrollView(child: Text(content!))
: null,
actions: actions,
);
}

View file

@ -1,54 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:opencanteen/pw/platformwidget.dart';
class PlatformField extends PlatformWidget<TextField, CupertinoTextField> {
final TextEditingController? controller;
final bool enabled;
final bool obscureText;
final String? labelText;
final bool autocorrect;
final TextInputType? keyboardType;
final List<TextInputFormatter> inputFormatters;
final void Function(String)? onChanged;
final List<String>? autofillHints;
const PlatformField({
super.key,
this.controller,
this.labelText,
this.obscureText = false,
this.autocorrect = false,
this.keyboardType,
this.inputFormatters = const [],
this.onChanged,
this.enabled = true,
this.autofillHints,
});
@override
TextField createAndroidWidget(BuildContext context) => TextField(
controller: controller,
enabled: enabled,
obscureText: obscureText,
decoration: InputDecoration(labelText: labelText),
autocorrect: autocorrect,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
onChanged: onChanged,
autofillHints: autofillHints,
);
@override
CupertinoTextField createIosWidget(BuildContext context) =>
CupertinoTextField(
controller: controller,
enabled: enabled,
obscureText: obscureText,
prefix: (labelText == null) ? null : Text(labelText!),
autocorrect: autocorrect,
keyboardType: keyboardType,
inputFormatters: inputFormatters,
onChanged: onChanged,
);
}

View file

@ -1,28 +0,0 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:opencanteen/pw/platformwidget.dart';
class PlatformSwitch extends PlatformWidget<Switch, CupertinoSwitch> {
final bool value;
final void Function(bool)? onChanged;
final Color? thumbColor;
const PlatformSwitch(
{super.key,
required this.value,
required this.onChanged,
this.thumbColor});
@override
Switch createAndroidWidget(BuildContext context) => Switch(
value: value,
onChanged: onChanged,
thumbColor: MaterialStateProperty.all(thumbColor),
);
@override
CupertinoSwitch createIosWidget(BuildContext context) => CupertinoSwitch(
value: value,
onChanged: onChanged,
thumbColor: thumbColor,
);
}

View file

@ -1,22 +0,0 @@
import 'dart:io';
import 'package:flutter/material.dart';
/// Abstract class used to create widgets for the respective platform UI library
abstract class PlatformWidget<A extends Widget, I extends Widget>
extends StatelessWidget {
const PlatformWidget({super.key});
@override
Widget build(BuildContext context) {
if (Platform.isAndroid) {
return createAndroidWidget(context);
} else {
return createIosWidget(context);
}
}
A createAndroidWidget(BuildContext context);
I createIosWidget(BuildContext context);
}

View file

@ -1,14 +1,13 @@
import 'dart:io';
import 'package:canteenlib/canteenlib.dart'; import 'package:canteenlib/canteenlib.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:opencanteen/okna/burza.dart'; import 'package:opencanteen/okna/burza.dart';
import 'package:opencanteen/okna/jidelnicek.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) { import 'lang/lang.dart';
import 'okna/jidelnicek.dart';
Drawer drawerGenerator(BuildContext context, Canteen canteen, int p,
FlutterLocalNotificationsPlugin n) {
Drawer drawer = const Drawer(); Drawer drawer = const Drawer();
switch (p) { switch (p) {
case 1: case 1:
@ -17,20 +16,22 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
child: ListView( child: ListView(
children: [ children: [
DrawerHeader( DrawerHeader(
child: Text(AppLocalizations.of(context)!.appName), child: Text(Languages.of(context)!.appName),
), ),
ListTile( ListTile(
selected: true, selected: true,
title: Text(AppLocalizations.of(context)!.home), title: Text(Languages.of(context)!.home),
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
), ),
ListTile( ListTile(
leading: const Icon(Icons.store), leading: const Icon(Icons.store),
title: Text(AppLocalizations.of(context)!.exchange), title: Text(Languages.of(context)!.exchange),
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
platformRouter((context) => BurzaView(canteen: canteen)), MaterialPageRoute(
builder: (context) => BurzaPage(canteen: canteen, n: n),
),
), ),
), ),
], ],
@ -43,20 +44,20 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
child: ListView( child: ListView(
children: [ children: [
DrawerHeader( DrawerHeader(
child: Text(AppLocalizations.of(context)!.appName), child: Text(Languages.of(context)!.appName),
), ),
ListTile( ListTile(
leading: const Icon(Icons.home), leading: const Icon(Icons.home),
title: Text(AppLocalizations.of(context)!.home), title: Text(Languages.of(context)!.home),
onTap: () => Navigator.push( onTap: () => Navigator.push(
context, context,
platformRouter((c) => MealView(canteen: canteen)), MaterialPageRoute(
), builder: (c) => JidelnicekPage(canteen: canteen, n: n))),
), ),
ListTile( ListTile(
leading: const Icon(Icons.store), leading: const Icon(Icons.store),
selected: true, selected: true,
title: Text(AppLocalizations.of(context)!.exchange), title: Text(Languages.of(context)!.exchange),
onTap: () => Navigator.pop(context), onTap: () => Navigator.pop(context),
), ),
], ],
@ -66,67 +67,26 @@ Drawer drawerGenerator(BuildContext context, Canteen canteen, int p) {
return drawer; return drawer;
} }
class OfflineMeal { class OfflineJidlo {
String name; String nazev;
String variant; String varianta;
bool ordered; bool objednano;
double price; double cena;
bool onExchange; bool naBurze;
DateTime day; DateTime den;
OfflineMeal( OfflineJidlo(
{required this.name, {required this.nazev,
required this.variant, required this.varianta,
required this.ordered, required this.objednano,
required this.price, required this.cena,
required this.onExchange, required this.naBurze,
required this.day}); required this.den});
} }
/// Parses [DateTime] from [TimeOfDay] /// Vytvoří [DateTime] z [TimeOfDay]
DateTime timeToDate(TimeOfDay c) { DateTime casNaDate(TimeOfDay c) {
var now = DateTime.now(); var now = DateTime.now();
return DateTime.parse( return DateTime.parse(
"${now.year}-${(now.month < 10 ? "0" : "") + now.month.toString()}-${(now.day < 10 ? "0" : "") + now.day.toString()} ${(c.hour < 10 ? "0" : "") + c.hour.toString()}:${(c.minute < 10 ? "0" : "") + c.minute.toString()}:00"); "${now.year}-${(now.month < 10 ? "0" : "") + now.month.toString()}-${(now.day < 10 ? "0" : "") + now.day.toString()} ${(c.hour < 10 ? "0" : "") + c.hour.toString()}:${(c.minute < 10 ? "0" : "") + c.minute.toString()}:00");
} }
/// List of instances to be used in the dropdown menu
List<Map<String, String>> instance = [
{"name": "SŠTE Brno, Olomoucká", "url": "https://stravovani.sstebrno.cz"},
{"name": "Jiné", "url": ""}
];
/// Used to display either a toas or a snackbar
void showInfo(BuildContext context, String message) {
if (Platform.isAndroid) {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
duration: const Duration(seconds: 4),
),
);
} else {
Fluttertoast.showToast(
msg: message,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 3,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0,
);
}
}
Route platformRouter(Widget Function(BuildContext context) builder) =>
(Platform.isAndroid)
? MaterialPageRoute(builder: builder)
: CupertinoPageRoute(builder: builder);
class SettingsManager {
bool skipWeekend = false;
bool checkOrdered = false;
bool saveOffline = false;
bool allergens = false;
}

View file

@ -1,2 +0,0 @@
- lepší podpora pro Android 13
- změna na adaptivní ikony na Androidu

View file

@ -1,3 +0,0 @@
- Umožnit ukládat více dnů offline
- Pop-up "Přihlašování" nyní zmizí v případě neúspěšného přihlášení
- chyba při ukládání offline vás nyní již nevyhodí ale zobrazí pouze zprávu

View file

@ -1,2 +0,0 @@
- aktualizace knihovny canteenlib
- splitování APK

View file

@ -1,2 +0,0 @@
- Optimalizace ze strany kodu
- Úpravy vzhledu

View file

@ -1 +0,0 @@
- Opravit chybu s přidáváním jídla na burzu

View file

@ -1,3 +0,0 @@
- Aktualizovány knihovny
- Vylepšen kódový základ
- Přidána podpora Material 3

View file

@ -1,9 +0,0 @@
OpenCanteen je open-source alternativní klient pro obědový systém iCanteen
Aplikace nyní umožňuje:
- prohlížet jídelníček
- objednávat či rušit obědy
- přidávat do nebo objednávat z burzy
- zobrazit oznámení ve vybraný čas obsahující objednané jídlo v daný den
Aplikace nemusí na všech instancích fungovat, v případě problémů vytvořte issue v repu nebo zanechte zpětnou vazbu skrz tlačítko v aplikaci.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 165 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

View file

@ -1 +0,0 @@
Neoficiální klient pro systém iCanteen

View file

@ -1,2 +0,0 @@
- better support for Android 13
- changed to adaptive + themed icons

View file

@ -1,3 +0,0 @@
- Support downloading multiple days of meal lists offline
- "Signing in" pop-up disappears when not signed in succesfully
- Error while saving menu offline no longer throws you to the log-in screen, but shows just an error message

View file

@ -1,2 +0,0 @@
- updated canteenlib library
- added support for APK splitting

View file

@ -1,2 +0,0 @@
- Code optimization
- Theme edits

View file

@ -1 +0,0 @@
- Fix issue with adding food to the exchange

View file

@ -1,3 +0,0 @@
- Updated libraries
- Polished codebase
- Added Material 3 support

View file

@ -1,9 +0,0 @@
OpenCanteen is an alternative free open-source client for the iCanteen food ordering system.
Using this app, you can:
- browse the menu of your iCanteen instance
- order or cancel meals
- place food into or the exchange
- have a notification displayed at a defined time with info about the ordered meal
The app may not work on every iCanteen instance, so in case of any bugs, make sure to open an issue in the repo.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 245 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

View file

@ -1 +0,0 @@
An unofficial client for the iCanteen food system

View file

@ -1 +0,0 @@
OpenCanteen

10
package.json Normal file
View file

@ -0,0 +1,10 @@
{
"devDependencies": {
"@commitlint/config-conventional": "^17.0.0",
"@commitlint/cli": "^17.0.0",
"husky": "^8.0.0"
},
"scripts": {
"prepare": "husky install"
}
}

1253
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more