Build all apps from Markdown content; Compose Desktop replaces GTK on Linux and Windows

- content/ + scripts/build-content.py produce content.json for every app
- stable lesson IDs with one-time progress migration (Swift, Kotlin)
- shared answer-check tests (tests/answers.json) for Kotlin and Swift
- android/desktop: Compose app with updater, speech and GTK progress import
- CI packages AppImage and Windows zip with ProGuard, runs the tests

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-09 20:40:37 +02:00
commit c3bc32113b
964 changed files with 57285 additions and 1156 deletions

View file

@ -24,19 +24,27 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Install dependencies # ProGuard and jlink need a JDK that ships jmods; Temurin 21 does.
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 21
- uses: gradle/actions/setup-gradle@v4
with:
build-root-directory: android
- name: Answer checks
run: | run: |
sudo apt-get update chmod +x android/gradlew
sudo apt-get install -y \ cd android
build-essential pkg-config curl \ ./gradlew :desktop:test --no-daemon
libgtk-4-dev librsvg2-dev \
desktop-file-utils \
appstream
- name: Build AppImage - name: Build AppImage
env: env:
COMMIT: ${{ github.sha }} COMMIT: ${{ github.sha }}
run: | run: |
export PACKAGE_JDK="$JAVA_HOME"
chmod +x scripts/bundle-linux.sh chmod +x scripts/bundle-linux.sh
./scripts/bundle-linux.sh ./scripts/bundle-linux.sh
@ -51,38 +59,35 @@ jobs:
runs-on: windows-latest runs-on: windows-latest
defaults: defaults:
run: run:
shell: msys2 {0} shell: bash
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- uses: msys2/setup-msys2@v2 - uses: actions/setup-java@v4
with: with:
msystem: UCRT64 distribution: temurin
update: true java-version: 21
install: >-
make
zip
mingw-w64-ucrt-x86_64-gcc
mingw-w64-ucrt-x86_64-gtk4
mingw-w64-ucrt-x86_64-pkg-config
mingw-w64-ucrt-x86_64-make
- name: Build and bundle - uses: actions/setup-python@v5
with:
python-version: "3.12"
- uses: gradle/actions/setup-gradle@v4
with:
build-root-directory: android
- name: Build zip
env: env:
COMMIT: ${{ github.sha }} COMMIT: ${{ github.sha }}
run: | run: |
make bundle export PACKAGE_JDK="$JAVA_HOME"
cd dist bash scripts/bundle-windows.sh
zip -qry ../graduately-windows-x64.zip .
cd ..
mkdir -p dist-out
mv graduately-windows-x64.zip dist-out/
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
with: with:
name: graduately-windows name: graduately-windows
path: dist-out/graduately-windows-x64.zip path: dist/graduately-windows-x64.zip
macos: macos:
name: macOS app name: macOS app
@ -173,11 +178,11 @@ jobs:
with: with:
build-root-directory: android build-root-directory: android
- name: Extract content and build APK - name: Build content and APK
env: env:
COMMIT: ${{ github.sha }} COMMIT: ${{ github.sha }}
run: | run: |
python3 scripts/extract-android-content.py python3 scripts/build-content.py
chmod +x android/gradlew chmod +x android/gradlew
cd android cd android
./gradlew :app:assembleRelease --no-daemon ./gradlew :app:assembleRelease --no-daemon
@ -198,7 +203,14 @@ jobs:
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v4
- name: Extract content and build IPA - name: Answer checks
env:
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
run: |
chmod +x scripts/test-answers-swift.sh
./scripts/test-answers-swift.sh
- name: Build content and IPA
env: env:
COMMIT: ${{ github.sha }} COMMIT: ${{ github.sha }}
DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer DEVELOPER_DIR: /Applications/Xcode.app/Contents/Developer
@ -253,6 +265,8 @@ jobs:
[ -f fresh/graduately-android.apk ] && echo "${{ github.sha }}" > staged/VERSION-android [ -f fresh/graduately-android.apk ] && echo "${{ github.sha }}" > staged/VERSION-android
[ -f fresh/graduately-ios.ipa ] && echo "${{ github.sha }}" > staged/VERSION-ios [ -f fresh/graduately-ios.ipa ] && echo "${{ github.sha }}" > staged/VERSION-ios
ls fresh/graduately-macos-*.zip >/dev/null 2>&1 && echo "${{ github.sha }}" > staged/VERSION-macos ls fresh/graduately-macos-*.zip >/dev/null 2>&1 && echo "${{ github.sha }}" > staged/VERSION-macos
[ -f fresh/graduately-windows-x64.zip ] && echo "${{ github.sha }}" > staged/VERSION-windows
[ -f fresh/graduately-linux-x86_64.AppImage ] && echo "${{ github.sha }}" > staged/VERSION-linux
# Compatibility aliases so older installers/updaters still find packages. # Compatibility aliases so older installers/updaters still find packages.
[ -f staged/graduately-macos-arm64.zip ] && cp -f staged/graduately-macos-arm64.zip staged/maturita-macos-arm64.zip [ -f staged/graduately-macos-arm64.zip ] && cp -f staged/graduately-macos-arm64.zip staged/maturita-macos-arm64.zip
[ -f staged/graduately-macos-x86_64.zip ] && cp -f staged/graduately-macos-x86_64.zip staged/maturita-macos-x86_64.zip [ -f staged/graduately-macos-x86_64.zip ] && cp -f staged/graduately-macos-x86_64.zip staged/maturita-macos-x86_64.zip

1
.gitignore vendored
View file

@ -30,6 +30,7 @@ CLAUDE.md
android/.gradle/ android/.gradle/
android/build/ android/build/
android/app/build/ android/app/build/
android/desktop/build/
android/local.properties android/local.properties
android/.idea/ android/.idea/
android/*.iml android/*.iml

160
Makefile
View file

@ -1,111 +1,49 @@
CC = gcc # Every app reads the lessons from content/ (built into content.json).
# Android, Linux, Windows: Kotlin + Compose in android/ (shared code in android/shared)
# iOS, macOS: Swift + SwiftUI in ios/ and macos/
#
# Published builds pass COMMIT (the CI workflow exports it); the updaters
# compare it against the build in the repository. Without it a build stays
# "dev" and never offers an update.
# Make sure pkg-config is found even when make runs with a minimal PATH GRADLE = cd android && ./gradlew
# (e.g. launched from an IDE such as CLion on macOS).
PKG_CONFIG ?= $(firstword $(wildcard \
$(addsuffix /pkg-config,$(subst :, ,$(PATH)) /opt/homebrew/bin /usr/local/bin)))
ifeq ($(PKG_CONFIG),)
PKG_CONFIG := pkg-config
endif
GTK_CFLAGS := $(shell $(PKG_CONFIG) --cflags gtk4) all: content
GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk4)
SRC_DIR := src content:
DATA_DIR := data python3 scripts/build-content.py
BUILD_DIR := build
CFLAGS = -Wall -Wextra -Wno-deprecated-declarations -I$(SRC_DIR) \ # Runs the desktop app from source on this machine (Linux, Windows or macOS).
-MMD -MP $(GTK_CFLAGS) run:
LIBS = $(GTK_LIBS) -lm $(GRADLE) :desktop:run
# Published builds pass the commit they were made from (the CI workflow # Answer checks shared by Kotlin and Swift (tests/answers.json), plus
# exports it), which the updater compares against the build in the repository. # progress migration and import of GTK progress.
# Without it the build keeps the "dev" default and never offers an update. test:
COMMIT ?= $(GRADLE) :desktop:test
ifneq ($(COMMIT),) @if command -v xcrun >/dev/null 2>&1; then \
CFLAGS += -DAPP_COMMIT='"$(COMMIT)"' chmod +x scripts/test-answers-swift.sh && ./scripts/test-answers-swift.sh; \
endif else \
echo "Swift checks skipped (no Xcode)"; \
ifeq ($(OS),Windows_NT)
TARGET = graduately.exe
LIBS += -mwindows
WINDRES ?= windres
RC_SRC = $(DATA_DIR)/windows/maturita.rc
RC_OBJ = $(BUILD_DIR)/maturita_rc.o
UNAME_S :=
else
TARGET = graduately
RC_SRC =
RC_OBJ =
UNAME_S := $(shell uname -s 2>/dev/null)
ifeq ($(UNAME_S),Darwin)
LIBS += -framework AppKit -framework Foundation
endif
endif
SRC = $(wildcard $(SRC_DIR)/*.c)
OBJ = $(patsubst $(SRC_DIR)/%.c,$(BUILD_DIR)/%.o,$(SRC))
DEP = $(OBJ:.o=.d)
ICON_DST = $(DATA_DIR)/share/icons/hicolor/512x512/apps/maturita.png
ICON_ICO = assets/app-icon.ico
all: $(TARGET)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
$(BUILD_DIR)/%.o: $(SRC_DIR)/%.c | $(BUILD_DIR)
$(CC) $(CFLAGS) -c -o $@ $<
ifeq ($(OS),Windows_NT)
$(RC_OBJ): $(RC_SRC) $(ICON_ICO) | $(BUILD_DIR)
$(WINDRES) -I. -o $@ $<
$(TARGET): $(OBJ) $(RC_OBJ)
$(CC) $(CFLAGS) -o $@ $(OBJ) $(RC_OBJ) $(LIBS)
else
$(TARGET): $(OBJ)
$(CC) $(CFLAGS) -o $@ $(OBJ) $(LIBS)
endif
clean:
rm -f $(TARGET) graduately.exe maturita.exe maturita
rm -rf $(BUILD_DIR)
run: $(TARGET)
./$(TARGET)
ifeq ($(OS),Windows_NT)
bundle: $(TARGET)
@rm -rf dist
@mkdir -p dist
@cp $(TARGET) dist/
@cp $(DATA_DIR)/style.css dist/
@cp $(DATA_DIR)/changelog.txt dist/
@mkdir -p dist/icons/hicolor/512x512/apps
@cp "$(ICON_DST)" dist/icons/hicolor/512x512/apps/
@cp -R $(DATA_DIR)/share dist/
@ldd $(TARGET) | grep -Ei '/(ucrt64|mingw64)/bin/' | awk '{print $$3}' \
| xargs -r -I{} cp -f {} dist/
@GTK_PREFIX="$$($(PKG_CONFIG) --variable=prefix gtk4)"; \
if [ -d "$$GTK_PREFIX/share/glib-2.0" ]; then \
mkdir -p dist/share && cp -R "$$GTK_PREFIX/share/glib-2.0" dist/share/; \
fi; \
if [ -d "$$GTK_PREFIX/lib/gdk-pixbuf-2.0" ]; then \
mkdir -p dist/lib && cp -R "$$GTK_PREFIX/lib/gdk-pixbuf-2.0" dist/lib/; \
fi fi
@echo "Bundled into dist/ - run dist/$(TARGET)"
else ifeq ($(UNAME_S),Darwin) android:
bundle: $(GRADLE) :app:assembleRelease
@chmod +x scripts/bundle-macos-swift.sh
@./scripts/bundle-macos-swift.sh linux:
else chmod +x scripts/bundle-linux.sh
bundle: ./scripts/bundle-linux.sh
@chmod +x scripts/bundle-linux.sh
@./scripts/bundle-linux.sh windows:
endif bash scripts/bundle-windows.sh
ios:
chmod +x scripts/bundle-ios.sh
./scripts/bundle-ios.sh
macos:
chmod +x scripts/bundle-macos-swift.sh
./scripts/bundle-macos-swift.sh
# Browsers render .command/.sh/.cmd as text/plain. Rebuild these zips after # Browsers render .command/.sh/.cmd as text/plain. Rebuild these zips after
# changing a script so the README download links stay in sync. The macOS zip # changing a script so the README download links stay in sync. The macOS zip
@ -118,18 +56,8 @@ smoke:
chmod +x scripts/smoke-platforms.sh chmod +x scripts/smoke-platforms.sh
./scripts/smoke-platforms.sh ./scripts/smoke-platforms.sh
-include $(DEP) clean:
rm -rf build dist dist-android dist-ios
$(GRADLE) clean
android: .PHONY: all content run test android linux windows ios macos installer-zips smoke clean
python3 scripts/extract-android-content.py
cd android && ./gradlew :app:assembleRelease
ios:
chmod +x scripts/bundle-ios.sh
./scripts/bundle-ios.sh
macos:
chmod +x scripts/bundle-macos-swift.sh
./scripts/bundle-macos-swift.sh
.PHONY: all clean run bundle installer-zips smoke android ios macos

196
README.md
View file

@ -54,8 +54,10 @@ spolehlivější instalátor výše, nebo stažení přímo z té stránky větv
- [graduately-android.apk](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-android.apk) - [graduately-android.apk](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-android.apk)
- [graduately-ios.ipa](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-ios.ipa) - [graduately-ios.ipa](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-ios.ipa)
Postup a nastavení se ukládají vedle `.app` / složky / AppImage do `progress/`. Na Linuxu a Windows se postup a nastavení ukládají do `progress/` vedle
Na Androidu, iOS a macOS do úložiště aplikace (stejné statistiky, témata a jazyk). AppImage / ve složce aplikace; postup ze starší GTK verze se při prvním
spuštění převezme. Na Androidu, iOS a macOS do úložiště aplikace (stejné
statistiky, témata a jazyk).
Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#sestavení-ze-zdroje). Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#sestavení-ze-zdroje).
@ -84,7 +86,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
- **Angličtina** — 4 ročníky, 32 lekcí podle osnov gymnázia (A2 až maturita): - **Angličtina** — 4 ročníky, 32 lekcí podle osnov gymnázia (A2 až maturita):
poslech, čtení, psaní, doplňování, kvízy a ročníkové testy poslech, čtení, psaní, doplňování, kvízy a ročníkové testy
- statistiky, hledání (lupa nebo `Cmd/Ctrl+K`), 10 témat, tmavý/světlý režim, čeština/angličtina - statistiky, hledání (lupa nebo `Cmd/Ctrl+K`), 10 témat, tmavý/světlý režim, čeština/angličtina
- ukončení: `Cmd/Super+Q` nebo `Alt+F4` - ukončení: `Ctrl/Cmd+Q` nebo `Alt+F4`, zpět: `Esc`
### Použití ### Použití
@ -95,49 +97,42 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
### Sestavení ze zdroje ### Sestavení ze zdroje
macOS je nativní SwiftUI aplikace (Xcode 26, macOS 26+). Linux a Windows Dvě kódové základny, jeden obsah (`content/`) a společné testy (`tests/`):
zůstávají GTK 4.
```bash - **Kotlin + Compose** (`android/`): Android, Linux a Windows. Obrazovky,
# macOS cvičení a kontrola odpovědí jsou v `android/shared/`, platformy se liší jen
# Xcode 26 z App Storu; žádné GTK vstupním bodem (`android/app`, `android/desktop`).
- **Swift + SwiftUI** (`ios/`, `macos/`): iPhone a Mac (Xcode 26, macOS 26+).
# Debian / Ubuntu
sudo apt install build-essential pkg-config libgtk-4-dev
# Fedora
sudo dnf install gcc make pkgconf-pkg-config gtk4-devel
# Arch
sudo pacman -S base-devel pkgconf gtk4
```
```bash ```bash
git clone https://github.com/pepaskopec-blip/Graduately.git git clone https://github.com/pepaskopec-blip/Graduately.git
cd Graduately cd Graduately
make test # kontrola odpovědí v Kotlinu (a ve Swiftu, je-li Xcode)
``` ```
Linux, Windows (i Mac pro vývoj) — stačí JDK 17+ a Python 3:
```bash
make run # spustí desktopovou aplikaci ze zdroje
make linux # dist/graduately-linux-x86_64.AppImage
make windows # dist/graduately-windows-x64.zip (v Git Bash)
make smoke # instalátory, obsah, testy, dostupnost builds
```
Na balení (`make linux` / `make windows`) je potřeba JDK s `jmods`, např.
Temurin 21; cestu k němu předejte v `PACKAGE_JDK`. ProGuard zmenší balíček
pod limit GitHubu 100 MB.
Na Macu: Na Macu:
```bash ```bash
open Graduately.xcworkspace # scheme Graduately z macos/Graduately.xcodeproj open Graduately.xcworkspace # scheme Graduately z macos/Graduately.xcodeproj
# nebo: make bundle # zip v dist/graduately-macos-arm64.zip # nebo: make macos # zip v dist/graduately-macos-arm64.zip
``` ```
Na Linuxu a Windows: Na macOS `make macos` automaticky vloží aktuální Git commit, aby fungovala
```bash
make
make run # nebo ./graduately
make bundle # AppImage / Windows složka s DLL
make smoke # instalátory, content packs, dostupnost builds
```
Volitelně CMake (GTK): `cmake -S . -B build && cmake --build build`.
Na macOS `make bundle` automaticky vloží aktuální Git commit, aby fungovala
kontrola aktualizací. Při balení ze zdrojů bez `.git` zadejte kontrola aktualizací. Při balení ze zdrojů bez `.git` zadejte
`COMMIT=<zdrojový-commit> make bundle`. `COMMIT=<zdrojový-commit> make macos`.
Aby stažená aplikace na macOS šla otevřít bez potvrzení v Nastavení, CI Aby stažená aplikace na macOS šla otevřít bez potvrzení v Nastavení, CI
potřebuje **Apple Developer Program** a GitHub Secrets: potřebuje **Apple Developer Program** a GitHub Secrets:
@ -149,43 +144,28 @@ potřebuje **Apple Developer Program** a GitHub Secrets:
Bez toho zůstane jen ad-hoc podpis a Gatekeeper stažený build zablokuje. Bez toho zůstane jen ad-hoc podpis a Gatekeeper stažený build zablokuje.
#### Windows (MSYS2 UCRT64, bez Visual Studia)
```cmd
winget install --id MSYS2.MSYS2 -e --source winget
C:\msys64\ucrt64.exe pacman -S --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gtk4 mingw-w64-ucrt-x86_64-pkg-config make
```
V terminálu **MSYS2 UCRT64**:
```bash
cd /c/Users/<jméno>/Graduately # C:\Users\<jméno>\Graduately v MSYS2
make
./graduately.exe
make bundle # dist/graduately.exe jde spustit i z Průzkumníka
```
### Struktura ### Struktura
``` ```
src/ C zdroje (GTK přehrávač pro Linux a Windows) content/ texty lekcí, cvičení a knih v Markdownu (viz content/README.md)
android/ Jetpack Compose přehrávač (stejný obsah) android/shared/ společný Kotlin: obrazovky, cvičení, kontrola odpovědí, postup
ios/ SwiftUI přehrávač (stejný obsah) android/app/ Android (vstupní bod, aktualizace APK, předčítání)
macos/ nativní SwiftUI aplikace pro Mac (stejný obsah) android/desktop/ Linux a Windows (okno, aktualizace, předčítání, import z GTK)
data/ style.css, changelog.txt, share/, cetba/*.json ios/ SwiftUI pro iPhone
macos/ SwiftUI pro Mac (sdílí kód s ios/)
tests/ společné testy kontroly odpovědí (answers.json) + Swift harness
data/share/ ikony a .desktop soubor
assets/ zdrojová ikona a screenshoty assets/ zdrojová ikona a screenshoty
installers/ instalátory, které stáhnou aktuální build installers/ instalátory, které stáhnou aktuální build
scripts/ balení AppImage, .app, IPA, extract a smoke test scripts/ build-content.py, balení AppImage, zip, .app, IPA a smoke test
.github/workflows/ CI: push na main → větev builds .github/workflows/ CI: push na main → větev builds
progress/ vzniká za běhu (cvičení, nastavení)
``` ```
Android ze zdroje (JDK 17 + Android SDK): Android ze zdroje (JDK 17 + Android SDK):
```bash ```bash
python3 scripts/extract-android-content.py
cd android cd android
./gradlew :app:assembleRelease ./gradlew :app:assembleRelease # obsah z content/ se sestaví sám
# APK: android/app/build/outputs/apk/release/app-release.apk # APK: android/app/build/outputs/apk/release/app-release.apk
``` ```
@ -202,12 +182,12 @@ iOS ze zdroje (Xcode 26, iOS 26+ / Liquid Glass):
```bash ```bash
open Graduately.xcworkspace # v kořeni repozitáře open Graduately.xcworkspace # v kořeni repozitáře
# Simulátor: vyber iPhone a Run, nic dalšího není potřeba (obsah se # Simulátor: vyber iPhone a Run, nic dalšího není potřeba (obsah se
# vytáhne z C zdrojů při buildu). Na iPhone: v Signing vyber svůj Team. # sestaví z content/ při buildu). Na iPhone: v Signing vyber svůj Team.
# nebo: make ios # unsigned IPA v dist-ios/graduately-ios.ipa # nebo: make ios # unsigned IPA v dist-ios/graduately-ios.ipa
``` ```
Workspace odkazuje na `ios/Graduately.xcodeproj` a `macos/Graduately.xcodeproj`. Workspace odkazuje na `ios/Graduately.xcodeproj` a `macos/Graduately.xcodeproj`.
Projekty nepřesouvejte – build čte `../src` a `../scripts`. Projekty nepřesouvejte – build čte `../content` a `../scripts`.
Licence: [GPL-3.0](LICENSE). Licence: [GPL-3.0](LICENSE).
@ -261,8 +241,10 @@ above, or download from that branch page:
- [graduately-android.apk](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-android.apk) - [graduately-android.apk](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-android.apk)
- [graduately-ios.ipa](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-ios.ipa) - [graduately-ios.ipa](https://github.com/pepaskopec-blip/Graduately/raw/refs/heads/builds/graduately-ios.ipa)
Progress and settings live in `progress/` next to the `.app` / folder / On Linux and Windows, progress and settings live in `progress/` next to the
AppImage. On Android, iOS and macOS they stay in app storage (same stats, themes, language). AppImage / in the app folder; progress from the older GTK build is taken over
on first launch. On Android, iOS and macOS they stay in app storage (same
stats, themes, language).
Intel Mac, another architecture, or hacking on the code → Intel Mac, another architecture, or hacking on the code →
[build from source](#build-from-source). [build from source](#build-from-source).
@ -293,7 +275,7 @@ Intel Mac, another architecture, or hacking on the code →
- **English** — 4 years, 32 lessons from the gymnasium syllabus (A2 through - **English** — 4 years, 32 lessons from the gymnasium syllabus (A2 through
the maturita): listening, reading, writing, gap-fill, quizzes and year tests the maturita): listening, reading, writing, gap-fill, quizzes and year tests
- statistics, search (magnifier or `Cmd/Ctrl+K`), 10 palettes, dark/light mode, Czech/English - statistics, search (magnifier or `Cmd/Ctrl+K`), 10 palettes, dark/light mode, Czech/English
- quit with `Cmd/Super+Q` or `Alt+F4` - quit with `Ctrl/Cmd+Q` or `Alt+F4`, back with `Esc`
### Usage ### Usage
@ -304,49 +286,42 @@ Intel Mac, another architecture, or hacking on the code →
### Build from source ### Build from source
macOS is a native SwiftUI app (Xcode 26, macOS 26+). Linux and Windows Two code bases, one content tree (`content/`) and shared tests (`tests/`):
stay on GTK 4.
```bash - **Kotlin + Compose** (`android/`): Android, Linux and Windows. Screens,
# macOS exercises and answer checking live in `android/shared/`; the platforms only
# Xcode 26 from the App Store; no GTK differ in their entry point (`android/app`, `android/desktop`).
- **Swift + SwiftUI** (`ios/`, `macos/`): iPhone and Mac (Xcode 26, macOS 26+).
# Debian / Ubuntu
sudo apt install build-essential pkg-config libgtk-4-dev
# Fedora
sudo dnf install gcc make pkgconf-pkg-config gtk4-devel
# Arch
sudo pacman -S base-devel pkgconf gtk4
```
```bash ```bash
git clone https://github.com/pepaskopec-blip/Graduately.git git clone https://github.com/pepaskopec-blip/Graduately.git
cd Graduately cd Graduately
make test # answer checks in Kotlin (and Swift when Xcode is there)
``` ```
Linux, Windows (and a Mac for development) need JDK 17+ and Python 3:
```bash
make run # run the desktop app from source
make linux # dist/graduately-linux-x86_64.AppImage
make windows # dist/graduately-windows-x64.zip (in Git Bash)
make smoke # installers, content, tests, builds availability
```
Packaging (`make linux` / `make windows`) needs a JDK that ships `jmods`,
such as Temurin 21; pass its path in `PACKAGE_JDK`. ProGuard keeps the
package under GitHub's 100 MB limit.
On a Mac: On a Mac:
```bash ```bash
open Graduately.xcworkspace # scheme Graduately from macos/Graduately.xcodeproj open Graduately.xcworkspace # scheme Graduately from macos/Graduately.xcodeproj
# or: make bundle # zip at dist/graduately-macos-arm64.zip # or: make macos # zip at dist/graduately-macos-arm64.zip
``` ```
On Linux and Windows: On macOS, `make macos` embeds the current Git commit so the installed app
```bash
make
make run # or ./graduately
make bundle # AppImage / Windows DLL folder
make smoke # installers, content packs, builds availability
```
Optional CMake (GTK): `cmake -S . -B build && cmake --build build`.
On macOS, `make bundle` embeds the current Git commit so the installed app
can check for updates. When packaging a source export without `.git`, use can check for updates. When packaging a source export without `.git`, use
`COMMIT=<source-commit> make bundle`. `COMMIT=<source-commit> make macos`.
Skipping Gatekeeper on a downloaded macOS build needs an Skipping Gatekeeper on a downloaded macOS build needs an
**Apple Developer Program** membership and these GitHub Secrets: **Apple Developer Program** membership and these GitHub Secrets:
@ -358,43 +333,28 @@ Skipping Gatekeeper on a downloaded macOS build needs an
Without them the bundle stays ad-hoc signed and Gatekeeper still blocks it. Without them the bundle stays ad-hoc signed and Gatekeeper still blocks it.
#### Windows (MSYS2 UCRT64, no Visual Studio)
```cmd
winget install --id MSYS2.MSYS2 -e --source winget
C:\msys64\ucrt64.exe pacman -S --noconfirm mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-gtk4 mingw-w64-ucrt-x86_64-pkg-config make
```
In an **MSYS2 UCRT64** terminal:
```bash
cd /c/Users/<name>/Graduately # C:\Users\<name>\Graduately in MSYS2
make
./graduately.exe
make bundle # dist/graduately.exe can be double-clicked
```
### Layout ### Layout
``` ```
src/ C sources (GTK player for Linux and Windows) content/ lesson, exercise and book texts in Markdown (see content/README.md)
android/ Jetpack Compose player (same content) android/shared/ shared Kotlin: screens, exercises, answer checking, progress
ios/ SwiftUI player (same content) android/app/ Android (entry point, APK updates, speech)
macos/ native SwiftUI Mac app (same content) android/desktop/ Linux and Windows (window, updates, speech, GTK progress import)
data/ style.css, changelog.txt, share/, cetba/*.json ios/ SwiftUI for iPhone
macos/ SwiftUI for Mac (shares code with ios/)
tests/ shared answer-check cases (answers.json) + Swift harness
data/share/ icons and the .desktop file
assets/ source icon and screenshots assets/ source icon and screenshots
installers/ fetch-the-latest installers installers/ fetch-the-latest installers
scripts/ AppImage / .app / IPA bundlers, extract, smoke test scripts/ build-content.py, AppImage / zip / .app / IPA bundlers, smoke test
.github/workflows/ CI: push to main → builds branch .github/workflows/ CI: push to main → builds branch
progress/ created at runtime (exercises, settings)
``` ```
Android from source (JDK 17 + Android SDK): Android from source (JDK 17 + Android SDK):
```bash ```bash
python3 scripts/extract-android-content.py
cd android cd android
./gradlew :app:assembleRelease ./gradlew :app:assembleRelease # content/ is built automatically
# APK: android/app/build/outputs/apk/release/app-release.apk # APK: android/app/build/outputs/apk/release/app-release.apk
``` ```
@ -411,11 +371,11 @@ iOS from source (Xcode 26, iOS 26+ / Liquid Glass):
```bash ```bash
open Graduately.xcworkspace # at the repo root open Graduately.xcworkspace # at the repo root
# Simulator: pick an iPhone and Run, nothing else needed (content is # Simulator: pick an iPhone and Run, nothing else needed (content is
# extracted from the C sources during the build). Device: pick your Team. # built from content/ during the build). Device: pick your Team.
# or: make ios # unsigned IPA at dist-ios/graduately-ios.ipa # or: make ios # unsigned IPA at dist-ios/graduately-ios.ipa
``` ```
The workspace points at `ios/Graduately.xcodeproj` and `macos/Graduately.xcodeproj`. The workspace points at `ios/Graduately.xcodeproj` and `macos/Graduately.xcodeproj`.
Do not move the projects – the build reads `../src` and `../scripts`. Do not move the projects – the build reads `../content` and `../scripts`.
License: [GPL-3.0](LICENSE). License: [GPL-3.0](LICENSE).

View file

@ -66,6 +66,9 @@ android {
compose = true compose = true
buildConfig = true buildConfig = true
} }
// UI, data and navigation shared with the desktop app (../desktop).
sourceSets.getByName("main").kotlin.directories.add("../shared/src")
} }
kotlin { kotlin {
@ -88,13 +91,14 @@ dependencies {
debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-tooling")
} }
val extractContent = tasks.register<Exec>("extractContent") { val buildContent = tasks.register<Exec>("buildContent") {
workingDir = rootProject.projectDir.parentFile workingDir = rootProject.projectDir.parentFile
commandLine("python3", "scripts/extract-android-content.py") commandLine("python3", "scripts/build-content.py")
inputs.dir(rootProject.projectDir.parentFile.resolve("src")) inputs.dir(rootProject.projectDir.parentFile.resolve("content"))
inputs.file(rootProject.projectDir.parentFile.resolve("scripts/build-content.py"))
outputs.file(file("src/main/assets/content.json")) outputs.file(file("src/main/assets/content.json"))
} }
tasks.named("preBuild").configure { tasks.named("preBuild").configure {
dependsOn(extractContent) dependsOn(buildContent)
} }

View file

@ -0,0 +1,88 @@
package org.maturita.maturita
import android.app.Application
import android.content.Context
import android.content.SharedPreferences
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import org.maturita.maturita.platform.Platform
import org.maturita.maturita.platform.Prefs
import org.maturita.maturita.platform.Speaker
import org.maturita.maturita.update.Updater
import java.util.Locale
class AndroidPlatform(private val app: Application) : Platform {
override val commit: String = BuildConfig.COMMIT
override val versionName: String = BuildConfig.VERSION_NAME
override val welcomeKey = "welcome_body_android"
override val prefs: Prefs = AndroidPrefs(app.getSharedPreferences("maturita", Context.MODE_PRIVATE))
override val updater = Updater(app)
override fun speaker(): Speaker = AndroidSpeaker(app)
}
private class AndroidPrefs(private val sp: SharedPreferences) : Prefs {
override fun getBoolean(key: String, fallback: Boolean) = sp.getBoolean(key, fallback)
override fun getInt(key: String, fallback: Int) = sp.getInt(key, fallback)
override fun getString(key: String, fallback: String?): String? = sp.getString(key, fallback)
override fun edit(): Prefs.Editor = Editor(sp.edit())
private class Editor(private val e: SharedPreferences.Editor) : Prefs.Editor {
override fun putBoolean(key: String, value: Boolean) = apply { e.putBoolean(key, value) }
override fun putInt(key: String, value: Int) = apply { e.putInt(key, value) }
override fun putString(key: String, value: String) = apply { e.putString(key, value) }
override fun apply() = e.apply()
}
}
private class AndroidSpeaker(context: Context) : Speaker {
private val main = Handler(Looper.getMainLooper())
private var ready = false
private val tts: TextToSpeech = TextToSpeech(context) { status ->
ready = status == TextToSpeech.SUCCESS
}
override fun speak(
text: String,
locale: Locale,
rate: Float,
volume: Float,
onRange: (Int) -> Unit,
onEnd: (Boolean) -> Unit,
) {
if (!ready) {
main.post { onEnd(false) }
return
}
val id = "en-${System.nanoTime()}"
tts.language = locale
tts.setSpeechRate(rate)
tts.setOnUtteranceProgressListener(object : UtteranceProgressListener() {
override fun onStart(utteranceId: String?) {}
override fun onRangeStart(utteranceId: String?, rangeStart: Int, rangeEnd: Int, frame: Int) {
if (utteranceId == id) main.post { onRange(rangeStart) }
}
override fun onDone(utteranceId: String?) {
if (utteranceId == id) main.post { onEnd(true) }
}
@Deprecated("Deprecated in Java")
override fun onError(utteranceId: String?) {
if (utteranceId == id) main.post { onEnd(false) }
}
})
val params = Bundle().apply { putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume) }
tts.speak(text, TextToSpeech.QUEUE_FLUSH, params, id)
}
override fun stop() {
tts.stop()
}
override fun close() {
tts.stop()
tts.shutdown()
}
}

View file

@ -1,15 +1,7 @@
package org.maturita.maturita package org.maturita.maturita
import android.app.Application
import android.os.Bundle import android.os.Bundle
import java.util.Locale
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import android.util.TypedValue import android.util.TypedValue
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
@ -21,60 +13,19 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.compose.foundation.layout.Box import androidx.lifecycle.AndroidViewModel
import androidx.compose.foundation.layout.fillMaxSize import org.maturita.maturita.data.Content
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface /** Keeps the shared app state alive across rotation and other config changes. */
import androidx.compose.material3.darkColorScheme class AppHolder(app: Application) : AndroidViewModel(app) {
import androidx.compose.material3.lightColorScheme val vm = AppViewModel(
import androidx.compose.runtime.Composable Content.parse(runCatching { app.assets.open("content.json").bufferedReader().use { it.readText() } }.getOrNull()),
import androidx.compose.runtime.LaunchedEffect AndroidPlatform(app),
import androidx.compose.ui.Modifier )
import androidx.compose.ui.graphics.Color }
import org.maturita.maturita.data.ColorMode
import org.maturita.maturita.data.Palette
import org.maturita.maturita.ui.BookListScreen
import org.maturita.maturita.ui.BookScreen
import org.maturita.maturita.ui.CzechMapScreen
import org.maturita.maturita.ui.GermanExercise
import org.maturita.maturita.ui.HomeScreen
import org.maturita.maturita.ui.HwQuizScreen
import org.maturita.maturita.ui.HwYearsScreen
import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.FyzQuizScreen
import org.maturita.maturita.ui.MathPracticeScreen
import org.maturita.maturita.ui.MatYearsScreen
import org.maturita.maturita.ui.FyzYearsScreen
import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow
import org.maturita.maturita.ui.LitQuizScreen
import org.maturita.maturita.ui.SciMapScreen
import org.maturita.maturita.ui.SciQuizScreen
import org.maturita.maturita.ui.MluvExercise
import org.maturita.maturita.ui.NetQuizScreen
import org.maturita.maturita.ui.NetYearsScreen
import org.maturita.maturita.ui.On2QuizScreen
import org.maturita.maturita.ui.On3QuizScreen
import org.maturita.maturita.ui.On4QuizScreen
import org.maturita.maturita.ui.OnQuizScreen
import org.maturita.maturita.ui.OnYearsScreen
import org.maturita.maturita.ui.EnYearsScreen
import org.maturita.maturita.ui.ChoiceLessonScreen
import org.maturita.maturita.ui.DeHomeScreen
import org.maturita.maturita.ui.ItYearScreen
import org.maturita.maturita.ui.EngExScreen
import org.maturita.maturita.ui.PlotScreen
import org.maturita.maturita.ui.RoadmapScreen
import org.maturita.maturita.ui.SearchScreen
import org.maturita.maturita.ui.SettingsScreen
import org.maturita.maturita.ui.SlidesScreen
import org.maturita.maturita.ui.StatsScreen
import org.maturita.maturita.ui.UnitMapScreen
import org.maturita.maturita.ui.VocabExercise
import org.maturita.maturita.ui.appTypography
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
private val vm: AppViewModel by viewModels() private val holder: AppHolder by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -96,7 +47,11 @@ class MainActivity : ComponentActivity() {
return return
} }
enableEdgeToEdge() enableEdgeToEdge()
setContent { MaturitaApp(vm) } val vm = holder.vm
setContent {
BackHandler(vm.canGoBack || vm.searchOpen) { vm.handleBack() }
MaturitaApp(vm)
}
} }
private fun crashView(dump: String, retry: () -> Unit): LinearLayout { private fun crashView(dump: String, retry: () -> Unit): LinearLayout {
@ -135,512 +90,3 @@ class MainActivity : ComponentActivity() {
} }
} }
/** Material 3 colour roles from the shared theme palette. */
private fun schemeFor(p: Palette, dark: Boolean) = if (dark) {
darkColorScheme(
primary = p.tint,
onPrimary = p.onTint,
primaryContainer = p.accent,
onPrimaryContainer = p.onAccent,
secondary = p.accent2,
onSecondary = p.onAccent,
secondaryContainer = p.surface1,
onSecondaryContainer = p.text,
tertiary = p.warning,
onTertiary = p.crust,
background = p.base,
onBackground = p.text,
surface = p.base,
onSurface = p.text,
surfaceVariant = p.surface0,
onSurfaceVariant = p.subtext,
surfaceContainerLowest = p.crust,
surfaceContainerLow = p.mantle,
surfaceContainer = p.mantle,
surfaceContainerHigh = p.surface0,
surfaceContainerHighest = p.surface1,
outline = p.overlay,
outlineVariant = p.surface1,
error = p.error,
onError = Color.White,
errorContainer = p.error.copy(alpha = 0.22f),
onErrorContainer = p.text,
)
} else {
lightColorScheme(
primary = p.tint,
onPrimary = p.onTint,
primaryContainer = p.accent,
onPrimaryContainer = p.onAccent,
secondary = p.accent2,
onSecondary = Color.White,
secondaryContainer = p.surface1,
onSecondaryContainer = p.text,
tertiary = p.warning,
onTertiary = Color.White,
background = p.base,
onBackground = p.text,
surface = p.base,
onSurface = p.text,
surfaceVariant = p.surface1,
onSurfaceVariant = p.subtext,
surfaceContainerLowest = p.mantle,
surfaceContainerLow = p.mantle,
surfaceContainer = p.mantle,
surfaceContainerHigh = p.surface1,
surfaceContainerHighest = p.surface2,
outline = p.overlay,
outlineVariant = p.surface2,
error = p.error,
onError = Color.White,
errorContainer = p.error.copy(alpha = 0.14f),
onErrorContainer = p.text,
)
}
@Composable
fun MaturitaApp(vm: AppViewModel) {
MaterialTheme(colorScheme = schemeFor(vm.palette, vm.mode == ColorMode.Dark), typography = appTypography()) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
MaturitaAppBody(vm)
}
}
}
@Composable
private fun MaturitaAppBody(vm: AppViewModel) {
LaunchedEffect(Unit) { vm.checkUpdate(false) }
BackHandler(vm.canGoBack || vm.searchOpen) {
if (vm.searchOpen) vm.searchOpen = false else vm.back()
}
Box(Modifier.fillMaxSize()) {
AnimatedContent(
targetState = vm.route,
modifier = Modifier.fillMaxSize(),
transitionSpec = {
val tabs = targetState.isRoot && initialState.isRoot
if (tabs) {
fadeIn(tween(180)) togetherWith fadeOut(tween(140))
} else if (vm.navigatingForward) {
(fadeIn(tween(220)) + slideInHorizontally(tween(220)) { it / 5 })
.togetherWith(fadeOut(tween(150)) + slideOutHorizontally(tween(150)) { -it / 8 })
} else {
(fadeIn(tween(220)) + slideInHorizontally(tween(220)) { -it / 8 })
.togetherWith(fadeOut(tween(150)) + slideOutHorizontally(tween(150)) { it / 5 })
}
},
label = "screen",
) { route ->
when (val r = route) {
Route.Welcome, Route.Subjects -> HomeScreen(vm)
Route.Stats -> StatsScreen(vm)
Route.Settings -> SettingsScreen(vm)
Route.DeHome -> DeHomeScreen(vm)
is Route.DeMap -> LessonListScreen(
vm, vm.tr("de_year${r.year}"), vm.tr("de_y${r.year}_sub"),
vm.content.deYear(r.year).map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.deDone(r.year, id), Route.DeLesson(r.year, id))
},
)
is Route.DeLesson -> {
val l = vm.content.deLesson(r.year, r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.DeEx(r.year, r.id))
}
}
is Route.DeEx -> EngExScreen(
vm, r.year, r.id, Locale.GERMANY, vm.content.deLesson(r.year, r.id),
) { vm.markDe(r.year, r.id) }
Route.Roadmap -> RoadmapScreen(vm)
is Route.UnitMap -> UnitMapScreen(vm, r.unitId)
is Route.GermanEx -> GermanExercise(vm, r.unitId, r.ex)
is Route.Vocab -> VocabExercise(vm, r.unitId)
Route.NetYears -> NetYearsScreen(vm)
Route.NetMap -> LessonListScreen(
vm, vm.tr("net_year1"), vm.tr("net_sub"),
vm.content.netLessons.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.netDone(id), Route.NetLesson(id))
},
)
is Route.NetLesson -> {
val l = vm.content.netLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.NetEx(r.id))
}
}
is Route.NetEx -> NetQuizScreen(vm, r.id)
Route.Net2Map -> ItYearScreen(vm, "net2", "net_year2", "net2_sub") { Route.Net2Lesson(it) }
is Route.Net2Lesson -> {
val l = vm.content.itLesson("net2", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net2Ex(r.id))
}
}
is Route.Net2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net2", r.id)) { vm.markCourse("net2", r.id) }
Route.Net3Map -> ItYearScreen(vm, "net3", "net_year3", "net3_sub") { Route.Net3Lesson(it) }
is Route.Net3Lesson -> {
val l = vm.content.itLesson("net3", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net3Ex(r.id))
}
}
is Route.Net3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net3", r.id)) { vm.markCourse("net3", r.id) }
Route.Net4Map -> ItYearScreen(vm, "net4", "net_year4", "net4_sub") { Route.Net4Lesson(it) }
is Route.Net4Lesson -> {
val l = vm.content.itLesson("net4", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net4Ex(r.id))
}
}
is Route.Net4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net4", r.id)) { vm.markCourse("net4", r.id) }
Route.HwYears -> HwYearsScreen(vm)
Route.HwMap -> LessonListScreen(
vm, vm.tr("hw_year1"), vm.tr("hw_sub"),
vm.content.hw.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.hwDone(id), Route.HwLesson(id))
},
)
is Route.HwLesson -> {
val l = vm.content.hwLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.HwEx(r.id))
}
}
is Route.HwEx -> HwQuizScreen(vm, r.id)
Route.Hw2Map -> ItYearScreen(vm, "hw2", "hw_year2", "hw2_sub") { Route.Hw2Lesson(it) }
is Route.Hw2Lesson -> {
val l = vm.content.itLesson("hw2", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw2Ex(r.id))
}
}
is Route.Hw2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw2", r.id)) { vm.markCourse("hw2", r.id) }
Route.Hw3Map -> ItYearScreen(vm, "hw3", "hw_year3", "hw3_sub") { Route.Hw3Lesson(it) }
is Route.Hw3Lesson -> {
val l = vm.content.itLesson("hw3", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw3Ex(r.id))
}
}
is Route.Hw3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw3", r.id)) { vm.markCourse("hw3", r.id) }
Route.Hw4Map -> ItYearScreen(vm, "hw4", "hw_year4", "hw4_sub") { Route.Hw4Lesson(it) }
is Route.Hw4Lesson -> {
val l = vm.content.itLesson("hw4", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw4Ex(r.id))
}
}
is Route.Hw4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw4", r.id)) { vm.markCourse("hw4", r.id) }
Route.EnYears -> EnYearsScreen(vm)
is Route.EnMap -> LessonListScreen(
vm, vm.tr("en_year${r.year}"), vm.tr("en_y${r.year}_sub"),
vm.content.enYear(r.year).map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.enDone(r.year, id), Route.EnLesson(r.year, id))
},
)
is Route.EnLesson -> {
val l = vm.content.enLesson(r.year, r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.EnEx(r.year, r.id))
}
}
is Route.EnEx -> EngExScreen(vm, r.year, r.id)
Route.OnYears -> OnYearsScreen(vm)
Route.OnMap -> LessonListScreen(
vm, vm.tr("on_year1"), vm.tr("on_sub"),
vm.content.on.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.onDone(id), Route.OnLesson(id))
},
)
is Route.OnLesson -> {
val l = vm.content.onLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.OnEx(r.id))
}
}
is Route.OnEx -> OnQuizScreen(vm, r.id)
Route.On2Map -> LessonListScreen(
vm, vm.tr("on_year2"), vm.tr("on2_sub"),
vm.content.on2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on2Done(id), Route.On2Lesson(id))
},
)
is Route.On2Lesson -> {
val l = vm.content.on2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On2Ex(r.id))
}
}
is Route.On2Ex -> On2QuizScreen(vm, r.id)
Route.On3Map -> LessonListScreen(
vm, vm.tr("on_year3"), vm.tr("on3_sub"),
vm.content.on3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on3Done(id), Route.On3Lesson(id))
},
)
is Route.On3Lesson -> {
val l = vm.content.on3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On3Ex(r.id))
}
}
is Route.On3Ex -> On3QuizScreen(vm, r.id)
Route.On4Map -> LessonListScreen(
vm, vm.tr("on_year4"), vm.tr("on4_sub"),
vm.content.on4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on4Done(id), Route.On4Lesson(id))
},
)
is Route.On4Lesson -> {
val l = vm.content.on4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On4Ex(r.id))
}
}
is Route.On4Ex -> On4QuizScreen(vm, r.id)
Route.LitYears -> LitYearsScreen(vm)
Route.LitMap -> LessonListScreen(
vm, vm.tr("lit_year1"), vm.tr("lit_sub"),
vm.content.lit.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.litDone(id), Route.LitLesson(id))
},
)
is Route.LitLesson -> {
val l = vm.content.litLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.LitEx(r.id))
}
}
is Route.LitEx -> LitQuizScreen(vm, 1, r.id)
Route.Lit2Map -> LessonListScreen(
vm, vm.tr("lit_year2"), vm.tr("lit2_sub"),
vm.content.lit2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit2Done(id), Route.Lit2Lesson(id))
},
)
is Route.Lit2Lesson -> {
val l = vm.content.lit2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit2Ex(r.id))
}
}
is Route.Lit2Ex -> LitQuizScreen(vm, 2, r.id)
Route.Lit3Map -> LessonListScreen(
vm, vm.tr("lit_year3"), vm.tr("lit3_sub"),
vm.content.lit3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit3Done(id), Route.Lit3Lesson(id))
},
)
is Route.Lit3Lesson -> {
val l = vm.content.lit3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit3Ex(r.id))
}
}
is Route.Lit3Ex -> LitQuizScreen(vm, 3, r.id)
Route.Lit4Map -> LessonListScreen(
vm, vm.tr("lit_year4"), vm.tr("lit4_sub"),
vm.content.lit4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit4Done(id), Route.Lit4Lesson(id))
},
)
is Route.Lit4Lesson -> {
val l = vm.content.lit4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit4Ex(r.id))
}
}
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
Route.MatYears -> MatYearsScreen(vm)
Route.Mat0Map -> LessonListScreen(
vm, vm.tr("mat0_year"), vm.tr("mat0_sub"),
vm.content.mat0.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat0Done(id), Route.Mat0Lesson(id))
},
)
is Route.Mat0Lesson -> {
val l = vm.content.mat0Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat0Ex(r.id))
}
}
is Route.Mat0Ex -> MathPracticeScreen(vm, 0, r.id)
Route.MatMap -> LessonListScreen(
vm, vm.tr("mat_year1"), vm.tr("mat_sub"),
vm.content.mat.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.matDone(id), Route.MatLesson(id))
},
)
is Route.MatLesson -> {
val l = vm.content.matLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.MatEx(r.id))
}
}
is Route.MatEx -> MathPracticeScreen(vm, 1, r.id)
Route.Mat2Map -> LessonListScreen(
vm, vm.tr("mat_year2"), vm.tr("mat2_sub"),
vm.content.mat2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat2Done(id), Route.Mat2Lesson(id))
},
)
is Route.Mat2Lesson -> {
val l = vm.content.mat2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat2Ex(r.id))
}
}
is Route.Mat2Ex -> MathPracticeScreen(vm, 2, r.id)
Route.Mat3Map -> LessonListScreen(
vm, vm.tr("mat_year3"), vm.tr("mat3_sub"),
vm.content.mat3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat3Done(id), Route.Mat3Lesson(id))
},
)
is Route.Mat3Lesson -> {
val l = vm.content.mat3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat3Ex(r.id))
}
}
is Route.Mat3Ex -> MathPracticeScreen(vm, 3, r.id)
Route.Mat4Map -> LessonListScreen(
vm, vm.tr("mat_year4"), vm.tr("mat4_sub"),
vm.content.mat4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat4Done(id), Route.Mat4Lesson(id))
},
)
is Route.Mat4Lesson -> {
val l = vm.content.mat4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat4Ex(r.id))
}
}
is Route.Mat4Ex -> MathPracticeScreen(vm, 4, r.id)
Route.FyzYears -> FyzYearsScreen(vm)
Route.FyzMap -> LessonListScreen(
vm, vm.tr("fyz_year1"), vm.tr("fyz_sub"),
vm.content.fyz.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyzDone(id), Route.FyzLesson(id))
},
)
is Route.FyzLesson -> {
val l = vm.content.fyzLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.FyzEx(r.id))
}
}
is Route.FyzEx -> FyzQuizScreen(vm, 1, r.id)
Route.Fyz2Map -> LessonListScreen(
vm, vm.tr("fyz_year2"), vm.tr("fyz2_sub"),
vm.content.fyz2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz2Done(id), Route.Fyz2Lesson(id))
},
)
is Route.Fyz2Lesson -> {
val l = vm.content.fyz2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz2Ex(r.id))
}
}
is Route.Fyz2Ex -> FyzQuizScreen(vm, 2, r.id)
Route.Fyz3Map -> LessonListScreen(
vm, vm.tr("fyz_year3"), vm.tr("fyz3_sub"),
vm.content.fyz3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz3Done(id), Route.Fyz3Lesson(id))
},
)
is Route.Fyz3Lesson -> {
val l = vm.content.fyz3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz3Ex(r.id))
}
}
is Route.Fyz3Ex -> FyzQuizScreen(vm, 3, r.id)
Route.Fyz4Map -> LessonListScreen(
vm, vm.tr("fyz_year4"), vm.tr("fyz4_sub"),
vm.content.fyz4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz4Done(id), Route.Fyz4Lesson(id))
},
)
is Route.Fyz4Lesson -> {
val l = vm.content.fyz4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz4Ex(r.id))
}
}
is Route.Fyz4Ex -> FyzQuizScreen(vm, 4, r.id)
Route.SciMap -> SciMapScreen(vm)
Route.ChemMap -> LessonListScreen(
vm, vm.tr("Chemie"), vm.tr("chem_sub"),
vm.content.chem.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.chemDone(id), Route.ChemLesson(id))
},
)
is Route.ChemLesson -> {
val l = vm.content.chemLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.ChemEx(r.id))
}
}
is Route.ChemEx -> SciQuizScreen(vm, "chem", r.id)
Route.BioMap -> LessonListScreen(
vm, vm.tr("Biologie"), vm.tr("bio_sub"),
vm.content.bio.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.bioDone(id), Route.BioLesson(id))
},
)
is Route.BioLesson -> {
val l = vm.content.bioLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.BioEx(r.id))
}
}
is Route.BioEx -> SciQuizScreen(vm, "bio", r.id)
Route.CzechMap -> CzechMapScreen(vm)
Route.Mluvnice -> LessonListScreen(
vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"),
vm.content.mluvnice.map { m ->
val n = m.int("id")
LessonRow(m.str("name"), vm.progress.mluvDone(n), Route.MluvEx(n))
},
)
is Route.MluvEx -> MluvExercise(vm, r.n)
Route.ReadingList -> BookListScreen(vm)
is Route.Book -> BookScreen(vm, r.id)
is Route.BookQuiz -> LitQuizScreen(vm, r.id)
is Route.BookPlot -> PlotScreen(vm, r.id)
}
}
AnimatedVisibility(
visible = vm.searchOpen,
enter = fadeIn(tween(160)),
exit = fadeOut(tween(120)),
) {
Box(Modifier.fillMaxSize()) { SearchScreen(vm) }
}
}
}

View file

@ -9,26 +9,20 @@ import android.os.Looper
import android.provider.Settings import android.provider.Settings
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import org.maturita.maturita.BuildConfig import org.maturita.maturita.BuildConfig
import org.maturita.maturita.platform.AppUpdater
import org.maturita.maturita.platform.UpdateState
import java.io.File import java.io.File
import java.net.HttpURLConnection import java.net.HttpURLConnection
import java.net.URL import java.net.URL
import java.util.concurrent.Executors import java.util.concurrent.Executors
data class UpdateState(
val status: String = "idle",
val remote: String? = null,
val messageKey: String = "update_current",
val messageArg: String? = BuildConfig.COMMIT.take(7),
val canInstall: Boolean = false,
)
/** /**
* Self-update from the `builds` branch. CI rewrites that branch on every * Self-update from the `builds` branch. CI rewrites that branch on every
* push to main with a commit titled "Build from <sha>"; the APK there is * push to main with a commit titled "Build from <sha>"; the APK there is
* signed with the repository's sideload key, so Android accepts it as an * signed with the repository's sideload key, so Android accepts it as an
* update over any earlier build signed the same way. * update over any earlier build signed the same way.
*/ */
class Updater(private val app: Application) { class Updater(private val app: Application) : AppUpdater {
private val io = Executors.newSingleThreadExecutor() private val io = Executors.newSingleThreadExecutor()
private val main = Handler(Looper.getMainLooper()) private val main = Handler(Looper.getMainLooper())
private val repo = BuildConfig.UPDATE_REPO private val repo = BuildConfig.UPDATE_REPO
@ -38,7 +32,7 @@ class Updater(private val app: Application) {
main.post { cb(state) } main.post { cb(state) }
} }
fun check(interactive: Boolean, cb: (UpdateState) -> Unit) { override fun check(interactive: Boolean, cb: (UpdateState) -> Unit) {
if (BuildConfig.COMMIT == "dev") { if (BuildConfig.COMMIT == "dev") {
emit(cb, UpdateState(status = "dev", messageKey = "update_err_devbuild", messageArg = null)) emit(cb, UpdateState(status = "dev", messageKey = "update_err_devbuild", messageArg = null))
return return
@ -63,7 +57,7 @@ class Updater(private val app: Application) {
} }
} }
fun install(cb: (UpdateState) -> Unit) { override fun install(cb: (UpdateState) -> Unit) {
// Android 8+ needs a per-app opt-in before we may hand an APK to the // Android 8+ needs a per-app opt-in before we may hand an APK to the
// package installer. Send the user there and keep the Update button // package installer. Send the user there and keep the Update button
// so the second tap goes straight to the download. // so the second tap goes straight to the download.

View file

@ -1,4 +1,6 @@
plugins { plugins {
id("com.android.application") version "9.1.1" apply false id("com.android.application") version "9.1.1" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false id("org.jetbrains.kotlin.plugin.compose") version "2.2.10" apply false
id("org.jetbrains.kotlin.jvm") version "2.2.10" apply false
id("org.jetbrains.compose") version "1.9.3" apply false
} }

View file

@ -0,0 +1,103 @@
// Linux and Windows app: the same Compose UI as Android (../shared), packaged
// with its own Java runtime by jpackage.
plugins {
id("org.jetbrains.kotlin.jvm")
id("org.jetbrains.kotlin.plugin.compose")
id("org.jetbrains.compose")
}
val commit: String = System.getenv("COMMIT")?.takeIf { it.isNotBlank() } ?: "dev"
val repoRoot: File = rootProject.projectDir.parentFile
val generated = layout.buildDirectory.dir("generated/resources")
kotlin {
compilerOptions {
jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
}
sourceSets.named("main") {
kotlin.srcDir("../shared/src")
}
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
sourceSets.named("main") {
resources.srcDir(generated)
}
dependencies {
implementation(compose.desktop.currentOs)
implementation(compose.material3)
implementation("org.jetbrains.compose.material:material-icons-extended:1.7.3")
implementation("org.json:json:20250517")
testImplementation(kotlin("test"))
}
val buildContent = tasks.register<Exec>("buildContent") {
val out = generated.get().file("content.json").asFile
workingDir = repoRoot
val python = System.getenv("PYTHON")?.takeIf { it.isNotBlank() }
?: if (System.getProperty("os.name").startsWith("Windows")) "python" else "python3"
commandLine(python, "scripts/build-content.py", "--out", out.path)
inputs.dir(repoRoot.resolve("content"))
inputs.file(repoRoot.resolve("scripts/build-content.py"))
outputs.file(out)
}
val buildInfo = tasks.register("buildInfo") {
val out = generated.get().file("build-info.properties").asFile
inputs.property("commit", commit)
outputs.file(out)
doLast {
out.parentFile.mkdirs()
out.writeText("commit=$commit\n")
}
}
val appIcon = tasks.register<Copy>("appIcon") {
from(repoRoot.resolve("data/share/icons/hicolor/256x256/apps/maturita.png"))
into(generated)
rename { "icon.png" }
}
tasks.named("processResources") {
dependsOn(buildContent, buildInfo, appIcon)
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
systemProperty("graduately.repo", repoRoot.path)
}
compose.desktop {
application {
mainClass = "org.maturita.maturita.desktop.MainKt"
// jpackage and ProGuard need a JDK with jmods (Temurin 21 has them, 24+ may not).
System.getenv("PACKAGE_JDK")?.takeIf { it.isNotBlank() }?.let { javaHome = it }
// Shrinking drops the thousands of unused icons and keeps the
// packages under GitHub's 100 MB file limit.
buildTypes.release.proguard {
version.set("7.10.0")
obfuscate.set(false)
configurationFiles.from(project.file("proguard-rules.pro"))
}
nativeDistributions {
// The launcher must stay graduately(.exe): installers and the
// updater of older builds start it by that name.
packageName = "graduately"
packageVersion = "1.0.0"
description = "Graduately"
vendor = "Graduately"
modules("java.naming", "jdk.crypto.ec", "jdk.unsupported")
linux {
iconFile.set(repoRoot.resolve("data/share/icons/hicolor/512x512/apps/maturita.png"))
}
windows {
iconFile.set(repoRoot.resolve("assets/app-icon.ico"))
}
}
}
}

2
android/desktop/proguard-rules.pro vendored Normal file
View file

@ -0,0 +1,2 @@
-dontwarn org.slf4j.**
-dontnote **

View file

@ -0,0 +1,17 @@
package org.maturita.maturita.desktop
import org.maturita.maturita.platform.Platform
import org.maturita.maturita.platform.Prefs
import org.maturita.maturita.platform.Speaker
class DesktopPlatform(
override val commit: String,
override val prefs: Prefs,
install: Install,
) : Platform {
override val versionName: String = if (Install.isWindows) "windows" else "linux"
override val welcomeKey = "welcome_body_desktop"
override val updater = DesktopUpdater(commit, install)
override fun speaker(): Speaker? = DesktopSpeaker.create()
}

View file

@ -0,0 +1,99 @@
package org.maturita.maturita.desktop
import org.maturita.maturita.platform.Speaker
import java.io.File
import java.util.Locale
import javax.swing.SwingUtilities
import kotlin.concurrent.thread
/**
* Speech through whatever the system already has: SAPI via PowerShell on
* Windows, espeak-ng / espeak / spd-say on Linux, `say` on a Mac. None of them
* report word positions, so pause and resume start the passage again.
*/
class DesktopSpeaker private constructor(private val command: (String, Locale, Float, Float) -> List<String>) : Speaker {
@Volatile private var process: Process? = null
override fun speak(
text: String,
locale: Locale,
rate: Float,
volume: Float,
onRange: (Int) -> Unit,
onEnd: (Boolean) -> Unit,
) {
stop()
val argv = command(text, locale, rate, volume)
val stdin = argv.lastOrNull() == STDIN
val p = runCatching {
ProcessBuilder(if (stdin) argv.dropLast(1) else argv)
.redirectErrorStream(true)
.redirectOutput(ProcessBuilder.Redirect.DISCARD)
.start()
}.getOrElse {
SwingUtilities.invokeLater { onEnd(false) }
return
}
process = p
thread(isDaemon = true, name = "speech") {
if (stdin) runCatching { p.outputStream.writer(Charsets.UTF_8).use { it.write(text) } }
val ok = p.waitFor() == 0
if (process === p) {
process = null
SwingUtilities.invokeLater { onEnd(ok) }
}
}
}
override fun stop() {
process?.let {
process = null
it.destroy()
}
}
override fun close() = stop()
companion object {
/** Marks a command that reads the text from standard input. */
private const val STDIN = "\u0000stdin"
private fun onPath(name: String): Boolean =
System.getenv("PATH").orEmpty().split(File.pathSeparator).any { File(it, name).canExecute() }
fun create(): DesktopSpeaker? {
val os = System.getProperty("os.name").orEmpty()
return when {
os.startsWith("Windows") -> DesktopSpeaker { _, locale, rate, volume ->
val sapiRate = ((rate - 1f) * 10f).toInt().coerceIn(-10, 10)
val script = "[Console]::InputEncoding=[Text.Encoding]::UTF8;" +
"Add-Type -AssemblyName System.Speech;" +
"\$s=New-Object System.Speech.Synthesis.SpeechSynthesizer;" +
"try{\$s.SelectVoiceByHints('NotSet','NotSet',0,[Globalization.CultureInfo]'${locale.toLanguageTag()}')}catch{};" +
"\$s.Rate=$sapiRate;\$s.Volume=${(volume * 100).toInt()};" +
"\$s.Speak([Console]::In.ReadToEnd())"
listOf("powershell", "-NoProfile", "-NonInteractive", "-Command", script, STDIN)
}
os.startsWith("Mac") -> DesktopSpeaker { text, _, rate, _ ->
listOf("say", "-r", (180 * rate).toInt().toString(), text)
}
onPath("espeak-ng") || onPath("espeak") -> {
val bin = if (onPath("espeak-ng")) "espeak-ng" else "espeak"
DesktopSpeaker { _, locale, rate, volume ->
val voice = if (locale.language == "de") "de" else "en-gb"
listOf(bin, "-v", voice, "-s", (165 * rate).toInt().toString(), "-a", (volume * 150).toInt().toString(), "--stdin", STDIN)
}
}
onPath("spd-say") -> DesktopSpeaker { text, locale, rate, volume ->
listOf(
"spd-say", "-w", "-l", locale.language,
"-r", ((rate - 1f) * 100f).toInt().coerceIn(-100, 100).toString(),
"-i", ((volume - 1f) * 100f).toInt().coerceIn(-100, 100).toString(),
text,
)
}
else -> null
}
}
}
}

View file

@ -0,0 +1,220 @@
package org.maturita.maturita.desktop
import org.maturita.maturita.platform.AppUpdater
import org.maturita.maturita.platform.UpdateState
import java.io.File
import java.net.HttpURLConnection
import java.net.URI
import java.util.concurrent.Executors
import java.util.zip.ZipInputStream
import javax.swing.SwingUtilities
import kotlin.system.exitProcess
/**
* Self-update from the `builds` branch, the same scheme the GTK builds used,
* so their updater and this one hand over cleanly: the asset names stay, and
* progress/ next to the app survives the swap.
*
* raw.githubusercontent.com caches branch-named URLs, so the branch tip is
* read from the Atom feed and every file is fetched from that commit. A running
* app cannot replace itself; a small script waits for this process to exit,
* swaps the new version in and starts it.
*/
class DesktopUpdater(private val commit: String, private val install: Install) : AppUpdater {
private val io = Executors.newSingleThreadExecutor { r -> Thread(r, "updater").apply { isDaemon = true } }
private val repo = "pepaskopec-blip/Graduately"
private val branch = "builds"
private val platform = if (Install.isWindows) "windows" else "linux"
private val asset = if (Install.isWindows) "graduately-windows-x64.zip" else "graduately-linux-x86_64.AppImage"
private fun emit(cb: (UpdateState) -> Unit, state: UpdateState) = SwingUtilities.invokeLater { cb(state) }
override fun check(interactive: Boolean, cb: (UpdateState) -> Unit) {
if (!commit.matches(Regex("[0-9a-fA-F]{7,40}"))) {
emit(cb, UpdateState("dev", messageKey = "update_err_devbuild"))
return
}
if (interactive) emit(cb, UpdateState("checking", messageKey = "update_checking"))
io.execute {
try {
val atom = get("https://github.com/$repo/commits/$branch.atom")
val tip = atomTip(atom) ?: error("no tip")
val remote = remoteCommit(atom, tip)
emit(cb, when {
sameCommit(remote, commit) -> UpdateState("ok", remote, "update_uptodate", remote.take(7))
install.kind == Install.Kind.Unsupported ->
UpdateState("unsupported", remote, if (interactive) "update_err_unsupported" else "update_current", commit.take(7))
else -> UpdateState("available", remote, "update_available", remote.take(7), canInstall = true)
})
} catch (_: Exception) {
emit(cb, if (interactive) UpdateState("error", messageKey = "update_err_network")
else UpdateState(messageArg = commit.take(7)))
}
}
}
override fun install(cb: (UpdateState) -> Unit) {
val target = install.target
if (install.kind == Install.Kind.Unsupported || target == null) {
emit(cb, UpdateState("error", messageKey = "update_err_unsupported"))
return
}
emit(cb, UpdateState("downloading", messageKey = "update_downloading"))
io.execute {
val staging = File(System.getProperty("java.io.tmpdir"), "graduately-update")
val source: File
try {
val tip = atomTip(get("https://github.com/$repo/commits/$branch.atom")) ?: error("no tip")
staging.deleteRecursively()
staging.mkdirs()
val file = File(staging, asset)
download("https://raw.githubusercontent.com/$repo/$tip/$asset", file)
source = if (install.kind == Install.Kind.WindowsDir) {
File(staging, "new").also { unzip(file, it) }
.takeIf { File(it, "graduately.exe").isFile } ?: error("bad zip")
} else {
file.takeIf { isElf(it) }?.also { it.setExecutable(true) } ?: error("bad AppImage")
}
} catch (_: Exception) {
staging.deleteRecursively()
emit(cb, UpdateState("error", messageKey = "update_err_download", canInstall = true))
return@execute
}
try {
startSwap(target, source, staging)
} catch (_: Exception) {
emit(cb, UpdateState("error", messageKey = "update_err_stage", canInstall = true))
return@execute
}
emit(cb, UpdateState("staged", messageKey = "update_staged"))
SwingUtilities.invokeLater { exitProcess(0) }
}
}
private fun startSwap(target: File, source: File, staging: File) {
val pid = ProcessHandle.current().pid().toString()
val windows = install.kind == Install.Kind.WindowsDir
val script = File(System.getProperty("java.io.tmpdir"), if (windows) "graduately-update.cmd" else "graduately-update.sh")
script.writeText(if (windows) WINDOWS_SWAP else UNIX_SWAP)
script.setExecutable(true)
val argv = if (windows) listOf("cmd", "/c", script.path) else listOf("/bin/sh", script.path)
val pb = ProcessBuilder(argv + listOf(pid, target.path, source.path, staging.path))
// The new AppImage must get its own mount, not inherit this one.
pb.environment().keys.removeAll(setOf("APPIMAGE", "APPDIR", "ARGV0", "OWD"))
val p = pb.redirectErrorStream(true).redirectOutput(ProcessBuilder.Redirect.DISCARD).start()
// The script detaches itself and exits; wait so we do not quit first.
p.waitFor()
}
/** Commit whose package for this platform sits on the branch. */
private fun remoteCommit(atom: String, tip: String): String {
val marker = runCatching { get("https://raw.githubusercontent.com/$repo/$tip/VERSION-$platform").trim() }.getOrNull()
if (marker != null && marker.length >= 7) return marker
return atomMainSha(atom) ?: get("https://raw.githubusercontent.com/$repo/$tip/VERSION").trim()
}
private fun atomTip(atom: String): String? =
Regex("""Commit/([0-9a-f]{40})""", RegexOption.IGNORE_CASE).find(atom)?.groupValues?.get(1)
private fun atomMainSha(atom: String): String? =
Regex("""Build from ([0-9a-f]{7,40})""", RegexOption.IGNORE_CASE).find(atom)?.groupValues?.get(1)
private fun sameCommit(a: String, b: String): Boolean {
val n = minOf(a.length, b.length)
return n >= 7 && a.take(n).equals(b.take(n), ignoreCase = true)
}
private fun isElf(f: File): Boolean = f.length() > 1_000_000L &&
f.inputStream().use { val h = ByteArray(4); it.read(h) == 4 && h.contentEquals(byteArrayOf(0x7f, 'E'.code.toByte(), 'L'.code.toByte(), 'F'.code.toByte())) }
private fun unzip(zip: File, dest: File) {
val root = dest.canonicalFile
ZipInputStream(zip.inputStream().buffered()).use { z ->
while (true) {
val e = z.nextEntry ?: break
val out = File(root, e.name).canonicalFile
if (!out.path.startsWith(root.path + File.separator)) error("zip slip")
if (e.isDirectory) out.mkdirs() else {
out.parentFile.mkdirs()
out.outputStream().use { z.copyTo(it) }
}
}
}
}
private fun open(url: String, readMs: Int): HttpURLConnection =
(URI(url).toURL().openConnection() as HttpURLConnection).apply {
connectTimeout = 15000
readTimeout = readMs
instanceFollowRedirects = true
setRequestProperty("User-Agent", "Graduately-desktop")
}
private fun get(url: String): String {
val c = open(url, 15000)
try {
if (c.responseCode !in 200..299) error("http ${c.responseCode}")
return c.inputStream.bufferedReader().use { it.readText() }
} finally {
c.disconnect()
}
}
private fun download(url: String, dest: File) {
val c = open(url, 60000)
try {
if (c.responseCode !in 200..299) error("http ${c.responseCode}")
c.inputStream.use { input -> dest.outputStream().use { input.copyTo(it) } }
} finally {
c.disconnect()
}
}
private companion object {
// Arguments: pid target source staging.
const val UNIX_SWAP = """#!/bin/sh
if [ -z "${'$'}GRADUATELY_UPDATE_DETACHED" ]; then
GRADUATELY_UPDATE_DETACHED=1
export GRADUATELY_UPDATE_DETACHED
nohup /bin/sh "${'$'}0" "${'$'}@" >/dev/null 2>&1 &
exit 0
fi
pid=${'$'}1; target=${'$'}2; source=${'$'}3; staging=${'$'}4
n=0
while kill -0 "${'$'}pid" 2>/dev/null && [ ${'$'}n -lt 600 ]; do
sleep 0.2; n=${'$'}((n+1))
done
if mv -f "${'$'}source" "${'$'}target" 2>/dev/null; then
chmod +x "${'$'}target"
fi
"${'$'}target" >/dev/null 2>&1 &
rm -rf "${'$'}staging"
rm -f "${'$'}0"
"""
// robocopy mirrors the new build over the old one (so nothing stale is
// left behind) but never touches progress/. ping is a sleep without a
// console window.
val WINDOWS_SWAP = """@echo off
if not defined GRADUATELY_UPDATE_DETACHED (
set GRADUATELY_UPDATE_DETACHED=1
start "" /b cmd /c "%~f0" %*
exit /b 0
)
set "PID=%~1"
set "TARGET=%~2"
set "SOURCE=%~3"
set "STAGING=%~4"
:wait
tasklist /FI "PID eq %PID%" 2>nul | find "%PID%" >nul
if not errorlevel 1 (
ping -n 2 127.0.0.1 >nul
goto wait
)
robocopy "%SOURCE%" "%TARGET%" /MIR /XD progress /R:5 /W:1 /NFL /NDL /NJH /NJS /NP >nul
start "" "%TARGET%\graduately.exe"
rmdir /s /q "%STAGING%"
(goto) 2>nul & del "%~f0"
""".replace("\n", "\r\n")
}
}

View file

@ -0,0 +1,46 @@
package org.maturita.maturita.desktop
import org.maturita.maturita.platform.Prefs
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
import java.util.Properties
/** Settings and progress in one properties file, rewritten atomically on every change. */
class FilePrefs(private val file: File) : Prefs {
private val values = Properties()
init {
runCatching { file.inputStream().use { values.load(it.reader(Charsets.UTF_8)) } }
}
override fun getBoolean(key: String, fallback: Boolean) =
values.getProperty(key)?.toBooleanStrictOrNull() ?: fallback
override fun getInt(key: String, fallback: Int) =
values.getProperty(key)?.toIntOrNull() ?: fallback
override fun getString(key: String, fallback: String?): String? =
values.getProperty(key) ?: fallback
override fun edit(): Prefs.Editor = Editor()
@Synchronized
private fun save(changes: Map<String, String>) {
values.putAll(changes)
runCatching {
file.parentFile?.mkdirs()
val tmp = File(file.path + ".tmp")
tmp.writer(Charsets.UTF_8).use { values.store(it, null) }
Files.move(tmp.toPath(), file.toPath(), StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE)
}
}
private inner class Editor : Prefs.Editor {
private val changes = LinkedHashMap<String, String>()
override fun putBoolean(key: String, value: Boolean) = apply { changes[key] = value.toString() }
override fun putInt(key: String, value: Int) = apply { changes[key] = value.toString() }
override fun putString(key: String, value: String) = apply { changes[key] = value }
override fun apply() = save(changes)
}
}

View file

@ -0,0 +1,96 @@
package org.maturita.maturita.desktop
import org.maturita.maturita.data.ThemeId
import org.maturita.maturita.data.themeNames
import org.maturita.maturita.platform.Prefs
import java.io.File
/**
* The GTK builds kept progress in `progress/<course>.conf` key files. Copy it
* once into the shared progress keys (`net.3`, `en.2.5`, `g.0.vocab` …); the
* shared ProgressStore then moves those onto lesson ids like on every
* platform. The .conf files stay, so nothing is lost if the import misreads.
*/
fun importGtkProgress(dir: File, prefs: Prefs) {
if (prefs.getBoolean("gtk_imported", false)) return
val edit = prefs.edit()
for (file in dir.listFiles { f -> f.name.endsWith(".conf") }.orEmpty()) {
val course = file.name.removeSuffix(".conf")
val groups = readKeyFile(file)
if (course == "settings") {
groups["ui"]?.let { importSettings(it, edit) }
continue
}
for ((key, value) in groups["done"].orEmpty()) {
if (value != "true") continue
gtkAddress(course, key)?.let { edit.putBoolean(it, true) }
}
}
edit.putBoolean("gtk_imported", true).apply()
}
/** GTK file and key to the address the apps use, or null for what has no equivalent. */
internal fun gtkAddress(course: String, key: String): String? {
val unit = Regex("""unit(\d)""").matchEntire(course)?.groupValues?.get(1)?.toInt()
return when {
// The vocabulary branch used the last slot of the unit.
unit != null -> key.toIntOrNull()?.let { if (it == 20) "g.${unit - 1}.vocab" else "g.${unit - 1}.$it" }
course == "english" -> yearKey("en", key)
course == "german-course" -> yearKey("de", key)
course == "mluvnice" -> key.toIntOrNull()?.let { "mluv.$it" }
// Book progress was one shared slot, not per book.
course == "cetba" -> null
else -> key.toIntOrNull()?.let { "$course.$it" }
}
}
private fun yearKey(prefix: String, key: String): String? {
val (year, lesson) = key.split("-").takeIf { it.size == 2 } ?: return null
if (year.toIntOrNull() == null || lesson.toIntOrNull() == null) return null
return "$prefix.$year.$lesson"
}
private fun importSettings(ui: Map<String, String>, edit: Prefs.Editor) {
ui["theme"]?.let { name ->
val i = themeNames.indexOfFirst { it.equals(name, ignoreCase = true) }
if (i >= 0) edit.putInt("theme", ThemeId.entries[i].ordinal)
}
ui["mode"]?.let { edit.putString("mode", if (it.lowercase() in setOf("light", "white")) "light" else "dark") }
ui["lang"]?.let { edit.putString("lang", if (it.lowercase() in setOf("en", "english")) "en" else "cs") }
ui["seen_commit"]?.takeIf { it.isNotBlank() }?.let { edit.putString("seen_commit", it) }
}
internal fun readKeyFile(file: File): Map<String, Map<String, String>> {
val groups = LinkedHashMap<String, MutableMap<String, String>>()
var current: MutableMap<String, String>? = null
runCatching { file.readLines() }.getOrDefault(emptyList()).forEach { raw ->
val line = raw.trim()
when {
line.isEmpty() || line.startsWith("#") -> {}
line.startsWith("[") && line.endsWith("]") ->
current = groups.getOrPut(line.substring(1, line.length - 1)) { LinkedHashMap() }
'=' in line -> current?.put(line.substringBefore('=').trim(), line.substringAfter('=').trim())
}
}
return groups
}
/**
* The GTK updater copied new packages over the install without deleting
* anything, so a Windows folder updated from GTK still carries its DLLs and
* data. style.css marks such a folder; jpackage never writes one.
*/
fun removeGtkLeftovers(install: Install) {
if (install.kind != Install.Kind.WindowsDir) return
val dir = install.target ?: return
val marker = File(dir, "style.css")
if (!marker.isFile) return
dir.listFiles().orEmpty().forEach { f ->
val name = f.name.lowercase()
val gtkDll = name.endsWith(".dll") && (name.startsWith("lib") || name == "zlib1.dll")
val stale = (f.isFile && (gtkDll || name == "changelog.txt")) ||
(f.isDirectory && name in setOf("lib", "share", "icons"))
if (stale) f.deleteRecursively()
}
marker.delete()
}

View file

@ -0,0 +1,40 @@
package org.maturita.maturita.desktop
import java.io.File
/**
* How this copy runs, which decides where progress lives and what an update
* replaces. Progress sits next to the app, as it did in the GTK builds, so
* existing students keep it: beside the AppImage on Linux, inside the install
* directory on Windows.
*/
class Install(
val kind: Kind,
/** The AppImage file, or the Windows directory holding graduately.exe. */
val target: File?,
val progressDir: File,
) {
enum class Kind { AppImage, WindowsDir, Unsupported }
companion object {
val isWindows = System.getProperty("os.name").orEmpty().startsWith("Windows")
fun detect(): Install {
val appImage = System.getenv("APPIMAGE")?.takeIf { it.isNotBlank() }?.let(::File)
val launcher = System.getProperty("jpackage.app-path")?.let(::File)
val windowsDir = launcher?.parentFile?.takeIf {
isWindows && launcher.name.equals("graduately.exe", ignoreCase = true) &&
File(it, "app").isDirectory && File(it, "runtime").isDirectory
}
val (kind, target) = when {
appImage != null && !isWindows -> Kind.AppImage to appImage
windowsDir != null -> Kind.WindowsDir to windowsDir
else -> Kind.Unsupported to null
}
val beside = target?.let { if (kind == Kind.AppImage) it.parentFile else it }
val home = File(System.getProperty("user.home"), ".graduately")
val root = beside?.takeIf { it.canWrite() } ?: home
return Install(kind, target, File(root, "progress"))
}
}
}

View file

@ -0,0 +1,72 @@
package org.maturita.maturita.desktop
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.toComposeImageBitmap
import androidx.compose.ui.input.key.Key
import androidx.compose.ui.input.key.KeyEventType
import androidx.compose.ui.input.key.isCtrlPressed
import androidx.compose.ui.input.key.isMetaPressed
import androidx.compose.ui.input.key.key
import androidx.compose.ui.input.key.type
import androidx.compose.ui.input.pointer.PointerButton
import androidx.compose.ui.input.pointer.PointerEventType
import androidx.compose.ui.input.pointer.onPointerEvent
import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import androidx.compose.ui.window.rememberWindowState
import org.maturita.maturita.AppViewModel
import org.maturita.maturita.MaturitaApp
import org.maturita.maturita.data.Content
import org.maturita.maturita.handleBack
import java.io.File
import java.util.Properties
import javax.imageio.ImageIO
private fun resourceText(name: String): String? =
Thread.currentThread().contextClassLoader.getResourceAsStream(name)?.bufferedReader()?.use { it.readText() }
@OptIn(ExperimentalComposeUiApi::class)
fun main() {
val install = Install.detect()
val prefs = FilePrefs(File(install.progressDir, "graduately.properties"))
importGtkProgress(install.progressDir, prefs)
removeGtkLeftovers(install)
val info = Properties().apply { resourceText("build-info.properties")?.let { load(it.reader()) } }
val platform = DesktopPlatform(info.getProperty("commit") ?: "dev", prefs, install)
val vm = AppViewModel(Content.parse(resourceText("content.json")), platform)
val icon = Thread.currentThread().contextClassLoader.getResourceAsStream("icon.png")
?.use { ImageIO.read(it) }?.let { BitmapPainter(it.toComposeImageBitmap()) }
application {
Window(
onCloseRequest = ::exitApplication,
title = "Graduately",
icon = icon,
state = rememberWindowState(width = 1100.dp, height = 780.dp),
onPreviewKeyEvent = { e ->
if (e.type != KeyEventType.KeyDown) return@Window false
val cmd = e.isCtrlPressed || e.isMetaPressed
when {
e.key == Key.Escape -> vm.handleBack()
cmd && e.key == Key.K -> { vm.searchOpen = true; true }
cmd && e.key == Key.Q -> { exitApplication(); true }
else -> false
}
},
) {
Box(
Modifier.fillMaxSize().onPointerEvent(PointerEventType.Press) { e ->
if (e.button == PointerButton.Back) vm.handleBack()
},
) {
MaturitaApp(vm)
}
}
}
}

View file

@ -0,0 +1,86 @@
package org.maturita.maturita
import org.json.JSONArray
import org.json.JSONObject
import org.maturita.maturita.data.answerAccepts
import org.maturita.maturita.data.answerIncomplete
import org.maturita.maturita.data.hasUmlaut
import org.maturita.maturita.data.mathAnswerOk
import org.maturita.maturita.data.mathDrawGiven
import org.maturita.maturita.data.mathDrawNeed
import org.maturita.maturita.data.mathDrawOk
import org.maturita.maturita.data.mathIsDraw
import org.maturita.maturita.data.netTxtEq
import org.maturita.maturita.data.normalizeAnswer
import java.io.File
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
/** Runs tests/answers.json, the cases the Swift apps check too (tests/swift). */
class AnswersTest {
private val cases = JSONObject(File(System.getProperty("graduately.repo"), "tests/answers.json").readText())
private fun rows(name: String): List<JSONArray> =
cases.getJSONArray(name).let { a -> (0 until a.length()).map { a.getJSONArray(it) } }
private fun JSONArray.str(i: Int): String? = if (isNull(i)) null else getString(i)
private fun JSONArray.points(i: Int): List<Pair<Int, Int>> =
getJSONArray(i).let { a -> (0 until a.length()).map { a.getJSONArray(it).let { p -> p.getInt(0) to p.getInt(1) } } }
@Test
fun normalize() = rows("normalize").forEach { assertEquals(it.str(1), normalizeAnswer(it.str(0)), "normalize ${it.str(0)}") }
@Test
fun accepts() = rows("accepts").forEach {
assertEquals(it.getBoolean(2), answerAccepts(normalizeAnswer(it.str(0)), it.str(1)), "accepts $it")
}
@Test
fun incomplete() = rows("incomplete").forEach {
assertEquals(it.getBoolean(2), answerIncomplete(normalizeAnswer(it.str(0)), it.str(1)), "incomplete $it")
}
@Test
fun netText() = rows("netTxtEq").forEach { assertEquals(it.getBoolean(2), netTxtEq(it.str(0), it.str(1)), "netTxtEq $it") }
@Test
fun umlaut() = rows("hasUmlaut").forEach { assertEquals(it.getBoolean(1), hasUmlaut(it.str(0)), "hasUmlaut $it") }
@Test
fun math() = rows("math").forEach { assertEquals(it.getBoolean(2), mathAnswerOk(it.str(0), it.str(1)), "math $it") }
@Test
fun draw() {
rows("drawNeed").forEach { assertEquals(it.getInt(1), mathDrawNeed(it.str(0)), "drawNeed $it") }
rows("drawGiven").forEach { assertEquals(it.points(1), mathDrawGiven(it.str(0)), "drawGiven $it") }
rows("draw").forEach { assertEquals(it.getBoolean(2), mathDrawOk(it.str(0), it.points(1)), "draw $it") }
}
/** Every worked answer in content/ must pass its own check, or students could never solve it. */
@Test
fun contentAnswersSolveThemselves() {
val root = JSONObject(javaClass.classLoader.getResource("content.json")!!.readText())
var checked = 0
for (section in listOf("mat0", "mat", "mat2", "mat3", "mat4")) {
val lessons = root.getJSONArray(section)
for (l in 0 until lessons.length()) {
val lesson = lessons.getJSONObject(l)
val problems = lesson.getJSONArray("problems")
for (p in 0 until problems.length()) {
val spec = problems.getJSONObject(p).getString("answer")
val where = "$section ${lesson.getString("key")}: $spec"
if (mathIsDraw(spec)) {
assertTrue(mathDrawNeed(spec) > 0, where)
} else {
val body = spec.substringAfter(':').substringBefore('|')
assertTrue(mathAnswerOk(spec, body), where)
}
checked++
}
}
}
assertTrue(checked > 100, "only $checked problems found")
}
}

View file

@ -0,0 +1,77 @@
package org.maturita.maturita
import org.json.JSONObject
import org.maturita.maturita.data.ColorMode
import org.maturita.maturita.data.Content
import org.maturita.maturita.data.ProgressStore
import org.maturita.maturita.data.ThemeId
import org.maturita.maturita.desktop.FilePrefs
import org.maturita.maturita.desktop.gtkAddress
import org.maturita.maturita.desktop.importGtkProgress
import java.nio.file.Files
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class ProgressTest {
private val content = Content.parse(javaClass.classLoader.getResource("content.json")!!.readText())
@Test
fun everyLegacyAddressHasALesson() {
val root = JSONObject(javaClass.classLoader.getResource("content.json")!!.readText())
val keys = root.getJSONObject("progress").getJSONObject("keys")
assertTrue(content.progressLegacy.isNotEmpty())
for ((old, id) in content.progressLegacy) {
assertTrue(keys.keySet().any { keys.getString(it) == id }, "$old -> $id has no lesson")
}
}
@Test
fun oldProgressMovesToLessonIds() {
val dir = Files.createTempDirectory("progress").toFile()
val prefs = FilePrefs(dir.resolve("p.properties"))
prefs.edit().putBoolean("fyz.1", true).putBoolean("g.0.vocab", true).putBoolean("en.2.3", true).apply()
val store = ProgressStore(prefs, content)
assertTrue(store.fyzDone(1))
assertTrue(store.vocabDone(0))
assertTrue(store.enDone(2, 3))
assertFalse(store.fyzDone(2))
assertTrue(prefs.getBoolean("done.fyzika-1-fyzikalni-veliciny", false))
store.markMat(1)
assertTrue(FilePrefs(dir.resolve("p.properties")).getBoolean("done.matematika-1-delitelnost-a-prvocisla", false))
}
@Test
fun gtkProgressIsImported() {
val dir = Files.createTempDirectory("gtk").toFile()
dir.resolve("net.conf").writeText("[done]\n1=true\n2=false\n")
dir.resolve("unit1.conf").writeText("[done]\n1=true\n20=true\n")
dir.resolve("english.conf").writeText("[done]\n2-3=true\n")
dir.resolve("settings.conf").writeText("[ui]\ntheme=Nord\nmode=dark\nlang=en\nseen_commit=abcdef1\n")
val prefs = FilePrefs(dir.resolve("graduately.properties"))
importGtkProgress(dir, prefs)
val store = ProgressStore(prefs, content)
assertTrue(store.netDone(1))
assertFalse(store.netDone(2))
assertTrue(store.germanDone(0, 1))
assertTrue(store.vocabDone(0))
assertTrue(store.enDone(2, 3))
assertEquals(ThemeId.Nord, store.themeId)
assertEquals(ColorMode.Dark, store.mode)
assertEquals("abcdef1", store.seenCommit)
}
@Test
fun gtkAddresses() {
assertEquals("g.2.5", gtkAddress("unit3", "5"))
assertEquals("de.4.6", gtkAddress("german-course", "4-6"))
assertEquals("mluv.7", gtkAddress("mluvnice", "7"))
assertEquals("hw3.2", gtkAddress("hw3", "2"))
assertEquals(null, gtkAddress("cetba", "1"))
}
}

View file

@ -16,3 +16,4 @@ dependencyResolutionManagement {
rootProject.name = "maturita" rootProject.name = "maturita"
include(":app") include(":app")
include(":desktop")

View file

@ -0,0 +1,580 @@
package org.maturita.maturita
import java.util.Locale
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInHorizontally
import androidx.compose.animation.slideOutHorizontally
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import org.maturita.maturita.data.ColorMode
import org.maturita.maturita.data.Palette
import org.maturita.maturita.ui.BookListScreen
import org.maturita.maturita.ui.BookScreen
import org.maturita.maturita.ui.CzechMapScreen
import org.maturita.maturita.ui.GermanExercise
import org.maturita.maturita.ui.HomeScreen
import org.maturita.maturita.ui.HwQuizScreen
import org.maturita.maturita.ui.HwYearsScreen
import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.FyzQuizScreen
import org.maturita.maturita.ui.MathPracticeScreen
import org.maturita.maturita.ui.MatYearsScreen
import org.maturita.maturita.ui.FyzYearsScreen
import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow
import org.maturita.maturita.ui.LitQuizScreen
import org.maturita.maturita.ui.SciMapScreen
import org.maturita.maturita.ui.SciQuizScreen
import org.maturita.maturita.ui.MluvExercise
import org.maturita.maturita.ui.NetQuizScreen
import org.maturita.maturita.ui.NetYearsScreen
import org.maturita.maturita.ui.On2QuizScreen
import org.maturita.maturita.ui.On3QuizScreen
import org.maturita.maturita.ui.On4QuizScreen
import org.maturita.maturita.ui.OnQuizScreen
import org.maturita.maturita.ui.OnYearsScreen
import org.maturita.maturita.ui.EnYearsScreen
import org.maturita.maturita.ui.ChoiceLessonScreen
import org.maturita.maturita.ui.DeHomeScreen
import org.maturita.maturita.ui.ItYearScreen
import org.maturita.maturita.ui.EngExScreen
import org.maturita.maturita.ui.PlotScreen
import org.maturita.maturita.ui.RoadmapScreen
import org.maturita.maturita.ui.SearchScreen
import org.maturita.maturita.ui.SettingsScreen
import org.maturita.maturita.ui.SlidesScreen
import org.maturita.maturita.ui.StatsScreen
import org.maturita.maturita.ui.UnitMapScreen
import org.maturita.maturita.ui.VocabExercise
import org.maturita.maturita.ui.appTypography
/** Material 3 colour roles from the shared theme palette. */
private fun schemeFor(p: Palette, dark: Boolean) = if (dark) {
darkColorScheme(
primary = p.tint,
onPrimary = p.onTint,
primaryContainer = p.accent,
onPrimaryContainer = p.onAccent,
secondary = p.accent2,
onSecondary = p.onAccent,
secondaryContainer = p.surface1,
onSecondaryContainer = p.text,
tertiary = p.warning,
onTertiary = p.crust,
background = p.base,
onBackground = p.text,
surface = p.base,
onSurface = p.text,
surfaceVariant = p.surface0,
onSurfaceVariant = p.subtext,
surfaceContainerLowest = p.crust,
surfaceContainerLow = p.mantle,
surfaceContainer = p.mantle,
surfaceContainerHigh = p.surface0,
surfaceContainerHighest = p.surface1,
outline = p.overlay,
outlineVariant = p.surface1,
error = p.error,
onError = Color.White,
errorContainer = p.error.copy(alpha = 0.22f),
onErrorContainer = p.text,
)
} else {
lightColorScheme(
primary = p.tint,
onPrimary = p.onTint,
primaryContainer = p.accent,
onPrimaryContainer = p.onAccent,
secondary = p.accent2,
onSecondary = Color.White,
secondaryContainer = p.surface1,
onSecondaryContainer = p.text,
tertiary = p.warning,
onTertiary = Color.White,
background = p.base,
onBackground = p.text,
surface = p.base,
onSurface = p.text,
surfaceVariant = p.surface1,
onSurfaceVariant = p.subtext,
surfaceContainerLowest = p.mantle,
surfaceContainerLow = p.mantle,
surfaceContainer = p.mantle,
surfaceContainerHigh = p.surface1,
surfaceContainerHighest = p.surface2,
outline = p.overlay,
outlineVariant = p.surface2,
error = p.error,
onError = Color.White,
errorContainer = p.error.copy(alpha = 0.14f),
onErrorContainer = p.text,
)
}
@Composable
fun MaturitaApp(vm: AppViewModel) {
MaterialTheme(colorScheme = schemeFor(vm.palette, vm.mode == ColorMode.Dark), typography = appTypography()) {
Surface(Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background) {
MaturitaAppBody(vm)
}
}
}
/** Back gesture, Escape or the mouse back button: close search first, then walk up. */
fun AppViewModel.handleBack(): Boolean {
if (searchOpen) {
searchOpen = false
return true
}
if (!canGoBack) return false
back()
return true
}
@Composable
private fun MaturitaAppBody(vm: AppViewModel) {
LaunchedEffect(Unit) { vm.checkUpdate(false) }
Box(Modifier.fillMaxSize()) {
AnimatedContent(
targetState = vm.route,
modifier = Modifier.fillMaxSize(),
transitionSpec = {
val tabs = targetState.isRoot && initialState.isRoot
if (tabs) {
fadeIn(tween(180)) togetherWith fadeOut(tween(140))
} else if (vm.navigatingForward) {
(fadeIn(tween(220)) + slideInHorizontally(tween(220)) { it / 5 })
.togetherWith(fadeOut(tween(150)) + slideOutHorizontally(tween(150)) { -it / 8 })
} else {
(fadeIn(tween(220)) + slideInHorizontally(tween(220)) { -it / 8 })
.togetherWith(fadeOut(tween(150)) + slideOutHorizontally(tween(150)) { it / 5 })
}
},
label = "screen",
) { route ->
when (val r = route) {
Route.Welcome, Route.Subjects -> HomeScreen(vm)
Route.Stats -> StatsScreen(vm)
Route.Settings -> SettingsScreen(vm)
Route.DeHome -> DeHomeScreen(vm)
is Route.DeMap -> LessonListScreen(
vm, vm.tr("de_year${r.year}"), vm.tr("de_y${r.year}_sub"),
vm.content.deYear(r.year).map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.deDone(r.year, id), Route.DeLesson(r.year, id))
},
)
is Route.DeLesson -> {
val l = vm.content.deLesson(r.year, r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.DeEx(r.year, r.id))
}
}
is Route.DeEx -> EngExScreen(
vm, r.year, r.id, Locale.GERMANY, vm.content.deLesson(r.year, r.id),
) { vm.markDe(r.year, r.id) }
Route.Roadmap -> RoadmapScreen(vm)
is Route.UnitMap -> UnitMapScreen(vm, r.unitId)
is Route.GermanEx -> GermanExercise(vm, r.unitId, r.ex)
is Route.Vocab -> VocabExercise(vm, r.unitId)
Route.NetYears -> NetYearsScreen(vm)
Route.NetMap -> LessonListScreen(
vm, vm.tr("net_year1"), vm.tr("net_sub"),
vm.content.netLessons.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.netDone(id), Route.NetLesson(id))
},
)
is Route.NetLesson -> {
val l = vm.content.netLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.NetEx(r.id))
}
}
is Route.NetEx -> NetQuizScreen(vm, r.id)
Route.Net2Map -> ItYearScreen(vm, "net2", "net_year2", "net2_sub") { Route.Net2Lesson(it) }
is Route.Net2Lesson -> {
val l = vm.content.itLesson("net2", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net2Ex(r.id))
}
}
is Route.Net2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net2", r.id)) { vm.markCourse("net2", r.id) }
Route.Net3Map -> ItYearScreen(vm, "net3", "net_year3", "net3_sub") { Route.Net3Lesson(it) }
is Route.Net3Lesson -> {
val l = vm.content.itLesson("net3", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net3Ex(r.id))
}
}
is Route.Net3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net3", r.id)) { vm.markCourse("net3", r.id) }
Route.Net4Map -> ItYearScreen(vm, "net4", "net_year4", "net4_sub") { Route.Net4Lesson(it) }
is Route.Net4Lesson -> {
val l = vm.content.itLesson("net4", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Net4Ex(r.id))
}
}
is Route.Net4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("net4", r.id)) { vm.markCourse("net4", r.id) }
Route.HwYears -> HwYearsScreen(vm)
Route.HwMap -> LessonListScreen(
vm, vm.tr("hw_year1"), vm.tr("hw_sub"),
vm.content.hw.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.hwDone(id), Route.HwLesson(id))
},
)
is Route.HwLesson -> {
val l = vm.content.hwLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.HwEx(r.id))
}
}
is Route.HwEx -> HwQuizScreen(vm, r.id)
Route.Hw2Map -> ItYearScreen(vm, "hw2", "hw_year2", "hw2_sub") { Route.Hw2Lesson(it) }
is Route.Hw2Lesson -> {
val l = vm.content.itLesson("hw2", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw2Ex(r.id))
}
}
is Route.Hw2Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw2", r.id)) { vm.markCourse("hw2", r.id) }
Route.Hw3Map -> ItYearScreen(vm, "hw3", "hw_year3", "hw3_sub") { Route.Hw3Lesson(it) }
is Route.Hw3Lesson -> {
val l = vm.content.itLesson("hw3", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw3Ex(r.id))
}
}
is Route.Hw3Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw3", r.id)) { vm.markCourse("hw3", r.id) }
Route.Hw4Map -> ItYearScreen(vm, "hw4", "hw_year4", "hw4_sub") { Route.Hw4Lesson(it) }
is Route.Hw4Lesson -> {
val l = vm.content.itLesson("hw4", r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Hw4Ex(r.id))
}
}
is Route.Hw4Ex -> ChoiceLessonScreen(vm, vm.content.itLesson("hw4", r.id)) { vm.markCourse("hw4", r.id) }
Route.EnYears -> EnYearsScreen(vm)
is Route.EnMap -> LessonListScreen(
vm, vm.tr("en_year${r.year}"), vm.tr("en_y${r.year}_sub"),
vm.content.enYear(r.year).map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.enDone(r.year, id), Route.EnLesson(r.year, id))
},
)
is Route.EnLesson -> {
val l = vm.content.enLesson(r.year, r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.EnEx(r.year, r.id))
}
}
is Route.EnEx -> EngExScreen(vm, r.year, r.id)
Route.OnYears -> OnYearsScreen(vm)
Route.OnMap -> LessonListScreen(
vm, vm.tr("on_year1"), vm.tr("on_sub"),
vm.content.on.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.onDone(id), Route.OnLesson(id))
},
)
is Route.OnLesson -> {
val l = vm.content.onLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.OnEx(r.id))
}
}
is Route.OnEx -> OnQuizScreen(vm, r.id)
Route.On2Map -> LessonListScreen(
vm, vm.tr("on_year2"), vm.tr("on2_sub"),
vm.content.on2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on2Done(id), Route.On2Lesson(id))
},
)
is Route.On2Lesson -> {
val l = vm.content.on2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On2Ex(r.id))
}
}
is Route.On2Ex -> On2QuizScreen(vm, r.id)
Route.On3Map -> LessonListScreen(
vm, vm.tr("on_year3"), vm.tr("on3_sub"),
vm.content.on3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on3Done(id), Route.On3Lesson(id))
},
)
is Route.On3Lesson -> {
val l = vm.content.on3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On3Ex(r.id))
}
}
is Route.On3Ex -> On3QuizScreen(vm, r.id)
Route.On4Map -> LessonListScreen(
vm, vm.tr("on_year4"), vm.tr("on4_sub"),
vm.content.on4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.on4Done(id), Route.On4Lesson(id))
},
)
is Route.On4Lesson -> {
val l = vm.content.on4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.On4Ex(r.id))
}
}
is Route.On4Ex -> On4QuizScreen(vm, r.id)
Route.LitYears -> LitYearsScreen(vm)
Route.LitMap -> LessonListScreen(
vm, vm.tr("lit_year1"), vm.tr("lit_sub"),
vm.content.lit.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.litDone(id), Route.LitLesson(id))
},
)
is Route.LitLesson -> {
val l = vm.content.litLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.LitEx(r.id))
}
}
is Route.LitEx -> LitQuizScreen(vm, 1, r.id)
Route.Lit2Map -> LessonListScreen(
vm, vm.tr("lit_year2"), vm.tr("lit2_sub"),
vm.content.lit2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit2Done(id), Route.Lit2Lesson(id))
},
)
is Route.Lit2Lesson -> {
val l = vm.content.lit2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit2Ex(r.id))
}
}
is Route.Lit2Ex -> LitQuizScreen(vm, 2, r.id)
Route.Lit3Map -> LessonListScreen(
vm, vm.tr("lit_year3"), vm.tr("lit3_sub"),
vm.content.lit3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit3Done(id), Route.Lit3Lesson(id))
},
)
is Route.Lit3Lesson -> {
val l = vm.content.lit3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit3Ex(r.id))
}
}
is Route.Lit3Ex -> LitQuizScreen(vm, 3, r.id)
Route.Lit4Map -> LessonListScreen(
vm, vm.tr("lit_year4"), vm.tr("lit4_sub"),
vm.content.lit4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.lit4Done(id), Route.Lit4Lesson(id))
},
)
is Route.Lit4Lesson -> {
val l = vm.content.lit4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Lit4Ex(r.id))
}
}
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id)
Route.MatYears -> MatYearsScreen(vm)
Route.Mat0Map -> LessonListScreen(
vm, vm.tr("mat0_year"), vm.tr("mat0_sub"),
vm.content.mat0.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat0Done(id), Route.Mat0Lesson(id))
},
)
is Route.Mat0Lesson -> {
val l = vm.content.mat0Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat0Ex(r.id))
}
}
is Route.Mat0Ex -> MathPracticeScreen(vm, 0, r.id)
Route.MatMap -> LessonListScreen(
vm, vm.tr("mat_year1"), vm.tr("mat_sub"),
vm.content.mat.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.matDone(id), Route.MatLesson(id))
},
)
is Route.MatLesson -> {
val l = vm.content.matLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.MatEx(r.id))
}
}
is Route.MatEx -> MathPracticeScreen(vm, 1, r.id)
Route.Mat2Map -> LessonListScreen(
vm, vm.tr("mat_year2"), vm.tr("mat2_sub"),
vm.content.mat2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat2Done(id), Route.Mat2Lesson(id))
},
)
is Route.Mat2Lesson -> {
val l = vm.content.mat2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat2Ex(r.id))
}
}
is Route.Mat2Ex -> MathPracticeScreen(vm, 2, r.id)
Route.Mat3Map -> LessonListScreen(
vm, vm.tr("mat_year3"), vm.tr("mat3_sub"),
vm.content.mat3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat3Done(id), Route.Mat3Lesson(id))
},
)
is Route.Mat3Lesson -> {
val l = vm.content.mat3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat3Ex(r.id))
}
}
is Route.Mat3Ex -> MathPracticeScreen(vm, 3, r.id)
Route.Mat4Map -> LessonListScreen(
vm, vm.tr("mat_year4"), vm.tr("mat4_sub"),
vm.content.mat4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.mat4Done(id), Route.Mat4Lesson(id))
},
)
is Route.Mat4Lesson -> {
val l = vm.content.mat4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Mat4Ex(r.id))
}
}
is Route.Mat4Ex -> MathPracticeScreen(vm, 4, r.id)
Route.FyzYears -> FyzYearsScreen(vm)
Route.FyzMap -> LessonListScreen(
vm, vm.tr("fyz_year1"), vm.tr("fyz_sub"),
vm.content.fyz.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyzDone(id), Route.FyzLesson(id))
},
)
is Route.FyzLesson -> {
val l = vm.content.fyzLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.FyzEx(r.id))
}
}
is Route.FyzEx -> FyzQuizScreen(vm, 1, r.id)
Route.Fyz2Map -> LessonListScreen(
vm, vm.tr("fyz_year2"), vm.tr("fyz2_sub"),
vm.content.fyz2.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz2Done(id), Route.Fyz2Lesson(id))
},
)
is Route.Fyz2Lesson -> {
val l = vm.content.fyz2Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz2Ex(r.id))
}
}
is Route.Fyz2Ex -> FyzQuizScreen(vm, 2, r.id)
Route.Fyz3Map -> LessonListScreen(
vm, vm.tr("fyz_year3"), vm.tr("fyz3_sub"),
vm.content.fyz3.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz3Done(id), Route.Fyz3Lesson(id))
},
)
is Route.Fyz3Lesson -> {
val l = vm.content.fyz3Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz3Ex(r.id))
}
}
is Route.Fyz3Ex -> FyzQuizScreen(vm, 3, r.id)
Route.Fyz4Map -> LessonListScreen(
vm, vm.tr("fyz_year4"), vm.tr("fyz4_sub"),
vm.content.fyz4.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.fyz4Done(id), Route.Fyz4Lesson(id))
},
)
is Route.Fyz4Lesson -> {
val l = vm.content.fyz4Lesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.Fyz4Ex(r.id))
}
}
is Route.Fyz4Ex -> FyzQuizScreen(vm, 4, r.id)
Route.SciMap -> SciMapScreen(vm)
Route.ChemMap -> LessonListScreen(
vm, vm.tr("Chemie"), vm.tr("chem_sub"),
vm.content.chem.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.chemDone(id), Route.ChemLesson(id))
},
)
is Route.ChemLesson -> {
val l = vm.content.chemLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.ChemEx(r.id))
}
}
is Route.ChemEx -> SciQuizScreen(vm, "chem", r.id)
Route.BioMap -> LessonListScreen(
vm, vm.tr("Biologie"), vm.tr("bio_sub"),
vm.content.bio.map { l ->
val id = l.int("id")
LessonRow(vm.tr(l.str("titleKey")), vm.progress.bioDone(id), Route.BioLesson(id))
},
)
is Route.BioLesson -> {
val l = vm.content.bioLesson(r.id)
if (l != null) SlidesScreen(vm, vm.tr(l.str("titleKey")), vm.tr(l.str("subKey")), l.arr("slides")) {
vm.replace(Route.BioEx(r.id))
}
}
is Route.BioEx -> SciQuizScreen(vm, "bio", r.id)
Route.CzechMap -> CzechMapScreen(vm)
Route.Mluvnice -> LessonListScreen(
vm, vm.tr("Mluvnice"), vm.tr("mluv_sub"),
vm.content.mluvnice.map { m ->
val n = m.int("id")
LessonRow(m.str("name"), vm.progress.mluvDone(n), Route.MluvEx(n))
},
)
is Route.MluvEx -> MluvExercise(vm, r.n)
Route.ReadingList -> BookListScreen(vm)
is Route.Book -> BookScreen(vm, r.id)
is Route.BookQuiz -> LitQuizScreen(vm, r.id)
is Route.BookPlot -> PlotScreen(vm, r.id)
}
}
AnimatedVisibility(
visible = vm.searchOpen,
enter = fadeIn(tween(160)),
exit = fadeOut(tween(120)),
) {
Box(Modifier.fillMaxSize()) { SearchScreen(vm) }
}
}
}

View file

@ -1,24 +1,22 @@
package org.maturita.maturita package org.maturita.maturita
import android.app.Application
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.lifecycle.AndroidViewModel
import org.maturita.maturita.data.ColorMode import org.maturita.maturita.data.ColorMode
import org.maturita.maturita.data.Content import org.maturita.maturita.data.Content
import org.maturita.maturita.data.ProgressStore import org.maturita.maturita.data.ProgressStore
import org.maturita.maturita.data.ThemeId import org.maturita.maturita.data.ThemeId
import org.maturita.maturita.data.UiLang import org.maturita.maturita.data.UiLang
import org.maturita.maturita.data.themePalette import org.maturita.maturita.data.themePalette
import org.maturita.maturita.update.UpdateState import org.maturita.maturita.platform.Platform
import org.maturita.maturita.update.Updater import org.maturita.maturita.platform.UpdateState
class AppViewModel(app: Application) : AndroidViewModel(app) { /** App state shared by every platform; the platform only supplies [Platform]. */
val content = Content.load(app) class AppViewModel(val content: Content, val platform: Platform) {
val progress = ProgressStore(app) val progress = ProgressStore(platform.prefs, content)
val updater = Updater(app) private val updater = platform.updater
var themeId by mutableStateOf(progress.themeId) var themeId by mutableStateOf(progress.themeId)
private set private set
@ -30,7 +28,7 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
var searchQuery by mutableStateOf("") var searchQuery by mutableStateOf("")
var tick by mutableStateOf(0) var tick by mutableStateOf(0)
private set private set
var update by mutableStateOf(UpdateState()) var update by mutableStateOf(UpdateState(messageArg = platform.commit.take(7)))
private set private set
var showChangelog by mutableStateOf(shouldShowChangelog()) var showChangelog by mutableStateOf(shouldShowChangelog())
private set private set
@ -268,13 +266,13 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
} }
fun dismissChangelog() { fun dismissChangelog() {
progress.seenCommit = BuildConfig.COMMIT progress.seenCommit = platform.commit
showChangelog = false showChangelog = false
} }
private fun shouldShowChangelog(): Boolean { private fun shouldShowChangelog(): Boolean {
if (content.changelog.isEmpty()) return false if (content.changelog.isEmpty()) return false
return !sameBuild(BuildConfig.COMMIT, progress.seenCommit) return !sameBuild(platform.commit, progress.seenCommit)
} }
} }

View file

@ -1,6 +1,5 @@
package org.maturita.maturita.data package org.maturita.maturita.data
import android.content.Context
import org.json.JSONObject import org.json.JSONObject
class Content(root: JSONObject) { class Content(root: JSONObject) {
@ -65,6 +64,8 @@ class Content(root: JSONObject) {
val mluvnice = raw.arr("mluvnice") val mluvnice = raw.arr("mluvnice")
val books = raw.arr("books") val books = raw.arr("books")
val changelog = raw.arr("changelog") val changelog = raw.arr("changelog")
val progressKeys = stringMap(root.optJSONObject("progress")?.optJSONObject("keys"))
val progressLegacy = stringMap(root.optJSONObject("progress")?.optJSONObject("legacy"))
fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id } fun germanUnit(id: Int) = german.firstOrNull { it.int("id") == id }
fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id } fun netLesson(id: Int) = netLessons.firstOrNull { it.int("id") == id }
@ -135,13 +136,13 @@ class Content(root: JSONObject) {
} }
companion object { companion object {
fun load(context: Context): Content { private fun stringMap(o: JSONObject?): Map<String, String> =
return try { o?.keys()?.asSequence()?.associateWith { o.optString(it) } ?: emptyMap()
val text = context.assets.open("content.json").bufferedReader().use { it.readText() }
Content(JSONObject(text)) fun parse(text: String?): Content = try {
Content(JSONObject(text ?: "{}"))
} catch (_: Exception) { } catch (_: Exception) {
Content(JSONObject()) Content(JSONObject())
} }
} }
}
} }

View file

@ -1,9 +1,28 @@
package org.maturita.maturita.data package org.maturita.maturita.data
import android.content.Context import org.maturita.maturita.platform.Prefs
class ProgressStore(context: Context) { class ProgressStore(private val prefs: Prefs, content: Content) {
private val prefs = context.getSharedPreferences("maturita", Context.MODE_PRIVATE) private val ids = content.progressKeys
init {
migrate(content.progressLegacy)
}
/** Progress is stored under each lesson's id from content/, so lessons can be
* reordered or renamed without losing it. `address` is the in-app position. */
private fun key(address: String) = "done." + (ids[address] ?: address)
private fun done(address: String) = prefs.getBoolean(key(address), false)
private fun mark(address: String) { prefs.edit().putBoolean(key(address), true).apply() }
private fun migrate(legacy: Map<String, String>) {
if (prefs.getBoolean("progress_ids_v2", false)) return
val edit = prefs.edit()
for ((old, id) in legacy) {
if (prefs.getBoolean(old, false)) edit.putBoolean("done.$id", true)
}
edit.putBoolean("progress_ids_v2", true).apply()
}
var themeId: ThemeId var themeId: ThemeId
get() = ThemeId.entries.getOrElse(prefs.getInt("theme", 0)) { ThemeId.Catppuccin } get() = ThemeId.entries.getOrElse(prefs.getInt("theme", 0)) { ThemeId.Catppuccin }
@ -21,75 +40,75 @@ class ProgressStore(context: Context) {
get() = prefs.getString("seen_commit", "") ?: "" get() = prefs.getString("seen_commit", "") ?: ""
set(v) { prefs.edit().putString("seen_commit", v).apply() } set(v) { prefs.edit().putString("seen_commit", v).apply() }
fun germanDone(unit: Int, ex: Int) = prefs.getBoolean("g.$unit.$ex", false) fun germanDone(unit: Int, ex: Int) = done("g.$unit.$ex")
fun markGerman(unit: Int, ex: Int) { prefs.edit().putBoolean("g.$unit.$ex", true).apply() } fun markGerman(unit: Int, ex: Int) { mark("g.$unit.$ex") }
fun vocabDone(unit: Int) = prefs.getBoolean("g.$unit.vocab", false) fun vocabDone(unit: Int) = done("g.$unit.vocab")
fun markVocab(unit: Int) { prefs.edit().putBoolean("g.$unit.vocab", true).apply() } fun markVocab(unit: Int) { mark("g.$unit.vocab") }
fun netDone(id: Int) = prefs.getBoolean("net.$id", false) fun netDone(id: Int) = done("net.$id")
fun markNet(id: Int) { prefs.edit().putBoolean("net.$id", true).apply() } fun markNet(id: Int) { mark("net.$id") }
fun hwDone(id: Int) = prefs.getBoolean("hw.$id", false) fun hwDone(id: Int) = done("hw.$id")
fun markHw(id: Int) { prefs.edit().putBoolean("hw.$id", true).apply() } fun markHw(id: Int) { mark("hw.$id") }
fun onDone(id: Int) = prefs.getBoolean("on.$id", false) fun onDone(id: Int) = done("on.$id")
fun markOn(id: Int) { prefs.edit().putBoolean("on.$id", true).apply() } fun markOn(id: Int) { mark("on.$id") }
fun on2Done(id: Int) = prefs.getBoolean("on2.$id", false) fun on2Done(id: Int) = done("on2.$id")
fun markOn2(id: Int) { prefs.edit().putBoolean("on2.$id", true).apply() } fun markOn2(id: Int) { mark("on2.$id") }
fun on3Done(id: Int) = prefs.getBoolean("on3.$id", false) fun on3Done(id: Int) = done("on3.$id")
fun markOn3(id: Int) { prefs.edit().putBoolean("on3.$id", true).apply() } fun markOn3(id: Int) { mark("on3.$id") }
fun on4Done(id: Int) = prefs.getBoolean("on4.$id", false) fun on4Done(id: Int) = done("on4.$id")
fun markOn4(id: Int) { prefs.edit().putBoolean("on4.$id", true).apply() } fun markOn4(id: Int) { mark("on4.$id") }
fun enDone(year: Int, id: Int) = prefs.getBoolean("en.$year.$id", false) fun enDone(year: Int, id: Int) = done("en.$year.$id")
fun markEn(year: Int, id: Int) { prefs.edit().putBoolean("en.$year.$id", true).apply() } fun markEn(year: Int, id: Int) { mark("en.$year.$id") }
fun deDone(year: Int, id: Int) = prefs.getBoolean("de.$year.$id", false) fun deDone(year: Int, id: Int) = done("de.$year.$id")
fun markDe(year: Int, id: Int) { prefs.edit().putBoolean("de.$year.$id", true).apply() } fun markDe(year: Int, id: Int) { mark("de.$year.$id") }
fun courseDone(course: String, id: Int) = prefs.getBoolean("$course.$id", false) fun courseDone(course: String, id: Int) = done("$course.$id")
fun markCourse(course: String, id: Int) { prefs.edit().putBoolean("$course.$id", true).apply() } fun markCourse(course: String, id: Int) { mark("$course.$id") }
fun litDone(id: Int) = prefs.getBoolean("lit.$id", false) fun litDone(id: Int) = done("lit.$id")
fun markLit(id: Int) { prefs.edit().putBoolean("lit.$id", true).apply() } fun markLit(id: Int) { mark("lit.$id") }
fun lit2Done(id: Int) = prefs.getBoolean("lit2.$id", false) fun lit2Done(id: Int) = done("lit2.$id")
fun markLit2(id: Int) { prefs.edit().putBoolean("lit2.$id", true).apply() } fun markLit2(id: Int) { mark("lit2.$id") }
fun lit3Done(id: Int) = prefs.getBoolean("lit3.$id", false) fun lit3Done(id: Int) = done("lit3.$id")
fun markLit3(id: Int) { prefs.edit().putBoolean("lit3.$id", true).apply() } fun markLit3(id: Int) { mark("lit3.$id") }
fun lit4Done(id: Int) = prefs.getBoolean("lit4.$id", false) fun lit4Done(id: Int) = done("lit4.$id")
fun markLit4(id: Int) { prefs.edit().putBoolean("lit4.$id", true).apply() } fun markLit4(id: Int) { mark("lit4.$id") }
fun chemDone(id: Int) = prefs.getBoolean("chem.$id", false) fun chemDone(id: Int) = done("chem.$id")
fun markChem(id: Int) { prefs.edit().putBoolean("chem.$id", true).apply() } fun markChem(id: Int) { mark("chem.$id") }
fun bioDone(id: Int) = prefs.getBoolean("bio.$id", false) fun bioDone(id: Int) = done("bio.$id")
fun markBio(id: Int) { prefs.edit().putBoolean("bio.$id", true).apply() } fun markBio(id: Int) { mark("bio.$id") }
fun fyzDone(id: Int) = prefs.getBoolean("fyz.$id", false) fun fyzDone(id: Int) = done("fyz.$id")
fun markFyz(id: Int) { prefs.edit().putBoolean("fyz.$id", true).apply() } fun markFyz(id: Int) { mark("fyz.$id") }
fun fyz2Done(id: Int) = prefs.getBoolean("fyz2.$id", false) fun fyz2Done(id: Int) = done("fyz2.$id")
fun markFyz2(id: Int) { prefs.edit().putBoolean("fyz2.$id", true).apply() } fun markFyz2(id: Int) { mark("fyz2.$id") }
fun fyz3Done(id: Int) = prefs.getBoolean("fyz3.$id", false) fun fyz3Done(id: Int) = done("fyz3.$id")
fun markFyz3(id: Int) { prefs.edit().putBoolean("fyz3.$id", true).apply() } fun markFyz3(id: Int) { mark("fyz3.$id") }
fun fyz4Done(id: Int) = prefs.getBoolean("fyz4.$id", false) fun fyz4Done(id: Int) = done("fyz4.$id")
fun markFyz4(id: Int) { prefs.edit().putBoolean("fyz4.$id", true).apply() } fun markFyz4(id: Int) { mark("fyz4.$id") }
fun mat0Done(id: Int) = prefs.getBoolean("mat0.$id", false) fun mat0Done(id: Int) = done("mat0.$id")
fun markMat0(id: Int) { prefs.edit().putBoolean("mat0.$id", true).apply() } fun markMat0(id: Int) { mark("mat0.$id") }
fun matDone(id: Int) = prefs.getBoolean("mat.$id", false) fun matDone(id: Int) = done("mat.$id")
fun markMat(id: Int) { prefs.edit().putBoolean("mat.$id", true).apply() } fun markMat(id: Int) { mark("mat.$id") }
fun mat2Done(id: Int) = prefs.getBoolean("mat2.$id", false) fun mat2Done(id: Int) = done("mat2.$id")
fun markMat2(id: Int) { prefs.edit().putBoolean("mat2.$id", true).apply() } fun markMat2(id: Int) { mark("mat2.$id") }
fun mat3Done(id: Int) = prefs.getBoolean("mat3.$id", false) fun mat3Done(id: Int) = done("mat3.$id")
fun markMat3(id: Int) { prefs.edit().putBoolean("mat3.$id", true).apply() } fun markMat3(id: Int) { mark("mat3.$id") }
fun mat4Done(id: Int) = prefs.getBoolean("mat4.$id", false) fun mat4Done(id: Int) = done("mat4.$id")
fun markMat4(id: Int) { prefs.edit().putBoolean("mat4.$id", true).apply() } fun markMat4(id: Int) { mark("mat4.$id") }
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false) fun mluvDone(n: Int) = done("mluv.$n")
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() } fun markMluv(n: Int) { mark("mluv.$n") }
fun bookQuiz(id: String) = prefs.getBoolean("book.$id.quiz", false) fun bookQuiz(id: String) = done("book.$id.quiz")
fun markBookQuiz(id: String) { prefs.edit().putBoolean("book.$id.quiz", true).apply() } fun markBookQuiz(id: String) { mark("book.$id.quiz") }
fun bookPlot(id: String) = prefs.getBoolean("book.$id.plot", false) fun bookPlot(id: String) = done("book.$id.plot")
fun markBookPlot(id: String) { prefs.edit().putBoolean("book.$id.plot", true).apply() } fun markBookPlot(id: String) { mark("book.$id.plot") }
} }
data class ProgressSum(var doneEx: Int = 0, var totalEx: Int = 0, var doneUnits: Int = 0, var openUnits: Int = 0) data class ProgressSum(var doneEx: Int = 0, var totalEx: Int = 0, var doneUnits: Int = 0, var openUnits: Int = 0)

View file

@ -0,0 +1,69 @@
package org.maturita.maturita.platform
import java.util.Locale
/**
* Everything the shared app needs from the system it runs on. Android and the
* desktop (Linux, Windows) each provide one; the screens never touch platform
* APIs directly.
*/
interface Platform {
/** Source commit baked into the build, or "dev" for local builds. */
val commit: String
val versionName: String
/** Text key of the welcome card blurb, which names the device. */
val welcomeKey: String
val prefs: Prefs
val updater: AppUpdater
/** A speech engine for listening exercises, or null when there is none. */
fun speaker(): Speaker?
}
/** The subset of Android's SharedPreferences the app uses. */
interface Prefs {
fun getBoolean(key: String, fallback: Boolean): Boolean
fun getInt(key: String, fallback: Int): Int
fun getString(key: String, fallback: String?): String?
fun edit(): Editor
interface Editor {
fun putBoolean(key: String, value: Boolean): Editor
fun putInt(key: String, value: Int): Editor
fun putString(key: String, value: String): Editor
fun apply()
}
}
data class UpdateState(
val status: String = "idle",
val remote: String? = null,
val messageKey: String = "update_current",
val messageArg: String? = null,
val canInstall: Boolean = false,
)
/** Callbacks always arrive on the UI thread. */
interface AppUpdater {
fun check(interactive: Boolean, cb: (UpdateState) -> Unit)
fun install(cb: (UpdateState) -> Unit)
}
interface Speaker {
/**
* Reads [text] aloud. [onRange] gets the offset of the word being spoken
* where the engine reports it; [onEnd] gets false when speaking failed.
* Both run on the UI thread.
*/
fun speak(
text: String,
locale: Locale,
rate: Float,
volume: Float,
onRange: (Int) -> Unit,
onEnd: (Boolean) -> Unit,
)
fun stop()
fun close()
}

View file

@ -37,16 +37,10 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.speech.tts.TextToSpeech
import android.speech.tts.UtteranceProgressListener
import androidx.compose.material3.Slider import androidx.compose.material3.Slider
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.SideEffect import androidx.compose.runtime.SideEffect
import androidx.compose.ui.platform.LocalContext
import java.util.Locale import java.util.Locale
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
@ -1423,18 +1417,9 @@ fun EngExScreen(
var revealed by remember(year, id) { mutableStateOf(false) } var revealed by remember(year, id) { mutableStateOf(false) }
var showModel by remember(year, id) { mutableStateOf(false) } var showModel by remember(year, id) { mutableStateOf(false) }
var fb by remember(year, id) { mutableStateOf("" to "") } var fb by remember(year, id) { mutableStateOf("" to "") }
val context = LocalContext.current val tts = remember { vm.platform.speaker() }
var tts by remember { mutableStateOf<TextToSpeech?>(null) } DisposableEffect(tts) {
DisposableEffect(context) { onDispose { tts?.close() }
var engine: TextToSpeech? = null
engine = TextToSpeech(context) { status ->
if (status == TextToSpeech.SUCCESS) engine?.language = locale
}
tts = engine
onDispose {
engine?.stop()
engine?.shutdown()
}
} }
fun picksOk(questions: List<J>, picks: List<Int>) = fun picksOk(questions: List<J>, picks: List<Int>) =
@ -1490,32 +1475,18 @@ fun EngExScreen(
val start = index.coerceIn(0, listening.length) val start = index.coerceIn(0, listening.length)
val id = "en-${System.nanoTime()}" val id = "en-${System.nanoTime()}"
utterance = id utterance = id
engine.language = locale val rest = listening.substring(start).ifEmpty { listening }
engine.setSpeechRate(speed) engine.speak(
engine.setOnUtteranceProgressListener(object : UtteranceProgressListener() { rest, locale, speed, volume.coerceIn(0f, 1f),
override fun onStart(utteranceId: String?) {} onRange = { offset -> if (utterance == id) cursor = start + offset },
override fun onRangeStart(utteranceId: String?, rangeStart: Int, rangeEnd: Int, frame: Int) { onEnd = { ok ->
if (utteranceId == id) cursor = start + rangeStart if (utterance == id) {
}
override fun onDone(utteranceId: String?) {
if (utteranceId != utterance) return
Handler(Looper.getMainLooper()).post {
phase = 0 phase = 0
cursor = 0 if (ok) cursor = 0
showScript = true showScript = true
} }
} },
@Deprecated("Deprecated in Java") )
override fun onError(utteranceId: String?) {
if (utteranceId != utterance) return
Handler(Looper.getMainLooper()).post { showScript = true; phase = 0 }
}
})
val params = Bundle().apply {
putFloat(TextToSpeech.Engine.KEY_PARAM_VOLUME, volume.coerceIn(0f, 1f))
}
val rest = listening.substring(start).ifEmpty { listening }
engine.speak(rest, TextToSpeech.QUEUE_FLUSH, params, id)
phase = 1 phase = 1
} }
Text(vm.tr("en_sec_listen"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp)) Text(vm.tr("en_sec_listen"), style = MaterialTheme.typography.titleMedium, modifier = Modifier.padding(top = 8.dp))

View file

@ -74,7 +74,6 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.maturita.maturita.AppViewModel import org.maturita.maturita.AppViewModel
import org.maturita.maturita.BuildConfig
import org.maturita.maturita.Route import org.maturita.maturita.Route
import org.maturita.maturita.data.ColorMode import org.maturita.maturita.data.ColorMode
import org.maturita.maturita.data.J import org.maturita.maturita.data.J
@ -110,7 +109,7 @@ fun HomeScreen(vm: AppViewModel) {
) )
Spacer(Modifier.height(8.dp)) Spacer(Modifier.height(8.dp))
Text( Text(
vm.tr("welcome_body_android"), vm.tr(vm.platform.welcomeKey),
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f), color = MaterialTheme.colorScheme.onPrimaryContainer.copy(alpha = 0.8f),
) )
@ -433,7 +432,7 @@ fun SettingsScreen(vm: AppViewModel) {
item { item {
ListItem( ListItem(
headlineContent = { Text("Build") }, headlineContent = { Text("Build") },
supportingContent = { Text("${BuildConfig.VERSION_NAME} · ${BuildConfig.COMMIT.take(7)}") }, supportingContent = { Text("${vm.platform.versionName} · ${vm.platform.commit.take(7)}") },
colors = ListItemDefaults.colors(containerColor = Color.Transparent), colors = ListItemDefaults.colors(containerColor = Color.Transparent),
modifier = Modifier.padding(top = 8.dp), modifier = Modifier.padding(top = 8.dp),
) )

319
content/README.md Normal file
View file

@ -0,0 +1,319 @@
# Obsah aplikace
Všechny texty lekcí, cvičení, kvízů a knih jsou tady v Markdownu. Aplikace pro
Android, iOS, macOS, Linux i Windows z nich při sestavení vyrobí jeden soubor
`content.json` (skript `scripts/build-content.py`). Na kód se kvůli obsahu sahat
nemusí.
Kontrola po úpravě:
```sh
python3 scripts/build-content.py
```
Skript vypíše počet lekcí, nebo chyby s cestou a číslem řádku
(`content/fyzika/1-rocnik/02-pohyb.md:41: …`). Při chybě se aplikace nesestaví.
## Kde co je
| Složka | Obsah |
|---|---|
| `pocitacove-site/`, `technicke-vybaveni/`, `obcanska-nauka/`, `fyzika/` | `N-rocnik/`, v nich lekce |
| `prirodni-vedy/chemie`, `prirodni-vedy/biologie` | lekce |
| `cestina/literatura/N-rocnik` | lekce literatury |
| `cestina/mluvnice` | cvičení z mluvnice |
| `cestina/cetba` | knihy k maturitní četbě |
| `matematika/0-opakovani`, `matematika/N-rocnik` | lekce s příklady |
| `anglictina/N-rocnik`, `nemcina/N-rocnik` | lekce s výkladem, čtením, poslechem, psaním |
| `nemcina/ucebnice` | učebnice němčiny po kapitolách |
| `aplikace/texty.md` | texty rozhraní (tlačítka, nadpisy obrazovek) |
| `aplikace/predmety.md` | dlaždice předmětů na úvodní obrazovce |
| `aplikace/novinky.txt` | seznam změn zobrazovaný v aplikaci |
Jedna lekce je jeden soubor `NN-nazev.md`. Pořadí v aplikaci určuje číslo na
začátku názvu. Soubory, které začínají `_`, nejsou lekce (např. `_lekce.md`
v učebnici popisuje kapitolu).
## Angličtina
Anglická verze leží vedle české jako `NN-nazev.en.md`. Co v ní chybí, zobrazí
se česky. Obsahuje:
- hlavičku se stejnými poli jako česká (`nazev`, `popis`, …), ale anglicky,
- volitelně tabulku `# Překlady`, kde je každý český text z lekce přeložený:
```md
---
nazev: Physical quantities
popis: SI, scalar and vector, measurement error.
---
# Překlady
| Česky | Anglicky |
|---|---|
| Fyzikální veličina má číselnou hodnotu a jednotku. | A physical quantity has a value and a unit. |
```
Český text musí v tabulce sedět přesně (včetně interpunkce). Pole `id`
a `puvodni` do `.en.md` nepatří.
## Společná pravidla zápisu
Hlavička mezi `---` obsahuje pole `klíč: hodnota`. Pak následují oddíly `#`,
položky `##` a podle potřeby `###`.
| Zápis | Význam |
|---|---|
| `- text` | odrážka; v kvízu možnost |
| `- [x] text` | správná možnost |
| `1. text` | číslovaná položka (např. části děje) |
| `> text` | vysvětlení, nápověda, tip nebo význam (podle oddílu) |
| `= odpověď` | správná odpověď; více variant odděluje `\|`: `= hasn't\|has not` |
| `[odpověď]` | mezera k doplnění uvnitř věty: `Ich [bin\|heiße] Anna.` |
| `\| a \| b \|` | tabulka; v buňce se píše `\|` jako `\\|` a nový řádek jako `\n` |
| `<!-- … -->` | poznámka, do aplikace se nedostane |
Hranaté závorky, které nemají být mezerou, se píšou `\[` a `\]`. Řádek, který
by jinak vypadal jako značka (začíná `-`, `>`, `=` …), se uvede zpětným
lomítkem: `\- tohle je obyčejný text`.
## ID a postup žáků
```md
id: fyzika-1-fyzikalni-veliciny
puvodni: fyz.1
```
- **`id`** je trvalé jméno lekce. Pod ním se ukládá, co má žák hotové. Musí být
v celém obsahu jedinečné. **Po vydání ho neměňte**, jinak se žákům postup
v lekci ztratí. Název souboru, číslo pořadí i nadpis měnit lze.
- **`puvodni`** je adresa lekce ze starších verzí aplikace. Podle ní se při
prvním spuštění nové verze převede starý postup. Neměňte ji a u nových lekcí
ji nepište.
Nová lekce: zkopírujte podobný soubor, dejte mu číslo podle pořadí, nové `id`
a smažte `puvodni`.
## Běžná lekce
Sítě, hardware, občanka, literatura, chemie, biologie, fyzika.
```md
---
id: fyzika-1-fyzikalni-veliciny
nazev: Fyzikální veličiny
popis: SI, skalár a vektor, chyba měření.
nazev_cviceni: Kvíz: veličiny
nadpis_kvizu: Ověřte si jednotky a chyby
---
# Výklad
## Veličina a jednotka
*Měření*
- Fyzikální veličina má číselnou hodnotu a jednotku.
- Soustava SI má sedm základních jednotek.
> Tip: základní jednotka hmotnosti je kilogram, ne gram.
# Kvíz
## Která jednotka je základní jednotka SI pro hmotnost?
- Gram
- [x] Kilogram
- Newton
> Gram je tisícina kilogramu. Newton je jednotka síly.
```
Každá `##` ve výkladu je jeden snímek. Řádek `*Měření*` je štítek snímku,
počítadlo „1 / 3“ doplní aplikace sama.
V první lekci sítí je navíc oddíl `# Úlohy na podsítě`: `## Úloha N` se
zadáním, `### Řešení` s textem řešení a `### Kontrola` s tabulkou
`Prefix | Síť | Broadcast | První adresa | Poslední adresa`.
## Matematika
Stejná hlavička a `# Výklad` jako běžná lekce, místo kvízu `# Příklady`:
```md
# Příklady
## Jaký je největší společný dělitel čísel 24 a 36?
= num:12
> Společní dělitelé jsou 1, 2, 3, 4, 6 a 12.
```
Druhy odpovědí:
| Odpověď | Kdy je správně |
|---|---|
| `num:12`, `num:1/2` | číslo nebo výraz (zlomek, odmocnina, π); stačí stejná hodnota v jakémkoli tvaru |
| `set:2;3` | všechna čísla v libovolném pořadí |
| `pair:2;-1` | čísla v daném pořadí (např. x; y) |
| `word:ano\|yes` | jedno ze slov |
| `square:2,3;5,3`, `right:…`, `mid:…`, `para:…`, `points:1,4;fix:1,1` | rýsování do mřížky 10 × 8; `fix:` jsou body předem zakreslené |
## Angličtina a němčina po ročnících
```md
---
id: anglictina-1-ja-a-moje-rodina
nazev: Já a moje rodina
popis: Sloveso be, have got. Doplňování a kvíz.
---
# Výklad
(snímky jako u běžné lekce)
# Čtení
Text článku jedním odstavcem.
## The bus is ___ than the train.
- faster
- [x] slower
> cheaper, but slower.
# Poslech
Přepis nahrávky, pak otázky stejně jako u čtení.
# Doplňování
## My sister ___ sixteen.
= is
> Věk: be, ne have.
# Psaní
Zadání.
## Vzorová odpověď
Text.
## Musí obsahovat
- would
- because
## Minimum slov
35
# Kvíz
(jako u běžné lekce)
```
Oddíly, které lekce nepotřebuje, se vynechají.
## Mluvnice (`cestina/mluvnice`)
Hlavička: `id`, `typ`, `zkratka` (krátký název na mapě), `nazev`, `popis`,
volitelně `poznamka`. Úlohy jsou v oddílu `# Úlohy`, podle typu:
```md
typ: doplnovani
## Stará bab_čka sed_la na lav_čce.
- i/í/y/ý = babička
- i/í/y/ý = seděla|sedla
> babička, seděla, lavičce
```
Každá odrážka je jedno políčko: vlevo nápověda, za `=` správné odpovědi.
```md
typ: vyber
## Narodil se v ______ v malé vesnici.
- [x] Čechách
- čechách
```
```md
typ: reseni
## napsali
= na- / ps- / -a- / -li
```
U `reseni` si žák řešení jen odkryje.
## Četba (`cestina/cetba`)
```md
---
id: 1984
autor: George Orwell
nazev: 1984
zanr: Próza
preklad: Šimečková
---
# O knize
- Autor: George Orwell
# Postavy
- Winston Smith – hlavní hrdina.
# Kvíz
## Kdo je hlavní hrdina?
- [x] Winston Smith
- O'Brien
> Vysvětlení.
# Děj
## Přetáhněte části příběhu do správného pořadí:
1. První část děje.
2. Druhá část děje.
> Volitelně význam celého děje.
```
Všechny oddíly kromě `Kvíz` a `Děj` jsou poznámky ke knize, zobrazí se
v uvedeném pořadí. Kvíz i děj jsou nepovinné. Volitelná pole v hlavičce:
`popis`, `kviz_nadpis`, `kviz_popis`, `dej_nadpis`, `dej_popis` (jinak se
použijí obecné texty) a `stranka`/`plna` pro knihy s vlastní obrazovkou.
## Učebnice němčiny (`nemcina/ucebnice`)
Každá kapitola je složka `NN-nazev/` se souborem `_lekce.md`:
```md
---
id: ucebnice-1
nazev: Neue Freunde
popis: Vyberte cvičení a dokončete je.
zamceno: ano
---
```
(`zamceno: ano` kapitolu v aplikaci zamkne.) V kapitole jsou cvičení
`NN-nazev.md` a slovíčka `slovicka.md`. Každé cvičení má v hlavičce `typ`.
Nejčastější typy:
| Typ | Oddíly |
|---|---|
| `choice` | `# Otázky` jako kvíz |
| `typed` | `# Úlohy`: tabulka `Zadání \| Odpověď \| Význam` |
| `verb`, `fill` | `# Nabídka` (odrážky slov), `# Úlohy`: tabulka s větami a mezerami `[…]` |
| `dialog` | `# Nabídka`, `# Rozhovory` s `## názvem` a tabulkou `Kdo \| Věta` |
| `assign` | `# Skupiny` (odrážky), `# Slova`: tabulka `Obrázek \| Slovo \| Skupina` |
| `free` | `# Otázky`: tabulka `Otázka \| Překlad otázky \| Vzorová odpověď \| Překlad odpovědi` |
| `vocab` | `# Slovíčka`, `## Strana N` a tabulka `Německy \| Odpověď \| Význam` |
Další typy (`assembly`, `number`, `count`, `seq`, `verbclue`, `profile`,
`hangman`, `ordne`, `letters`, `ex2`, `table`, `kw` …) mají vždy aspoň jeden
vzor v existujících kapitolách; nejjednodušší je zkopírovat ho. Oddíl
`# Překlad` obsahuje české překlady položek ve stejném pořadí.
Volitelná pole hlavičky cvičení: `tip`, `ukazka` (vzorový příklad),
`vyznam_predem`, `preklad_predem`, `odhalit_nemcinu`, `ukazat_nemcinu`
(`ano`/`ne`) a `poznamka_k_odpovedim`.
## Texty rozhraní a předměty
`aplikace/texty.md` obsahuje tabulky `Klíč | Česky | Anglicky`. Klíč používá
kód aplikace, neměňte ho; texty ve sloupcích měnit lze. `%s` a `%d` jsou místa,
kam aplikace doplní hodnotu.
`aplikace/predmety.md` má tabulku
`Klíč | Česky | Anglicky | Ikona | Cíl | Otevřeno`. `Cíl` je obrazovka, která se
otevře, `Otevřeno: ne` dlaždici zamkne.

View file

@ -0,0 +1,4 @@
---
nazev: Me and my family
popis: The verb be, have got and possessives. Gaps and a quiz.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-1-ja-a-moje-rodina
puvodni: en.1.1
nazev: Já a moje rodina
popis: Sloveso be, have got a přivlastňovací zájmena. Doplňování a kvíz.
---
# Výklad
## Kdo jsem
*Be*
- I am, you are, he/she/it is, we/you/they are.
- Zápor a otázka: She isn't tall. Are you from Prague?
- Věk, povolání i nálada: I am 16. My mum is a nurse. I am fine.
- Přivlastňovací zájmena: my, your, his, her, its, our, their.
> Tip: věk se říká slovesem be. „She has 16 years“ je čechismus.
## Co mám
*Have got*
- I've got a younger brother. She hasn't got a pet.
- Have you got any cousins? — Yes, I have. / No, I haven't.
- V americké angličtině stačí have: I have a brother.
- Rodina: parents, siblings, only child, get on well, look like.
> Tip: have got je britská hovorová vazba pro vlastnictví, ne pro věk.
# Doplňování
## My sister ___ sixteen.
= is
> Věk: be, ne have.
## ___ you got a bike?
= Have
> Otázka s have got.
## This is ___ grandmother. She lives with us.
= our
> Přivlastňovací zájmeno.
## They ___ not from Brno. They are from Olomouc.
= are
> They + are.
## He ___ got a dog, but he has got two cats.
= hasn't|has not
> Zápor have got.
# Kvíz
## Which sentence is correct?
- She has 16 years.
- [x] She is 16 years old.
- She is 16 years.
- She has 16 years old.
> Věk: be + years old.
## I ___ two cousins and one aunt.
- am
- [x] have got
- has got
- is
> Vlastnictví v 1. osobě: have got.
## Eva is my sister. ___ room is next to mine.
- She
- [x] Her
- His
- Hers
> Před podstatným jménem je her, ne she.
## ___ your parents at home?
- Is
- [x] Are
- Have
- Do
> Parents je množné číslo: Are.

View file

@ -0,0 +1,4 @@
---
nazev: A school day
popis: The present simple. Reading and gaps.
---

View file

@ -0,0 +1,80 @@
---
id: anglictina-1-skolni-den
puvodni: en.1.2
nazev: Školní den
popis: Přítomný čas prostý. Čtení a doplňování.
---
# Výklad
## Co dělám pravidelně
*Present simple*
- I get up at seven. He gets up at six.
- Zápor: I don't like maths. She doesn't eat meat.
- Otázka: Do you walk to school? Does she play the piano?
- Příslovce četnosti stojí před slovesem: I usually walk. She is always late.
> Tip: ve 3. osobě jednotného čísla přibývá -s/-es: she watches.
## Rozvrh a přestávky
*Škola*
- lessons, break, canteen, homework, timetable, subject.
- Čas: at eight, at half past twelve, in the morning, on Monday.
- because vysvětluje důvod: I like biology because we do experiments.
- never / sometimes / often / usually / always.
> Tip: předmět ve škole je bez členu, když mluvíme obecně: I like biology.
# Čtení
My school day starts early. I get up at half past six, have breakfast and catch the tram. Lessons begin at eight. On Monday we have maths, English and biology. I like biology because we do experiments, but I don't like maths. We have lunch at half past twelve in the canteen. After school I do my homework or meet friends. I never go to bed after ten on a school night.
## What time do lessons begin?
- At half past six
- [x] At eight
- At half past twelve
- At ten
> Lessons begin at eight.
## Why does the writer like biology?
- Because it is easy
- [x] Because they do experiments
- Because the teacher is funny
- Because there is no homework
> because we do experiments.
## Where do they have lunch?
- At home
- On the tram
- [x] In the canteen
- In the park
> in the canteen.
## On a school night the writer goes to bed…
- after eleven
- at midnight
- [x] never after ten
- before six
> never go to bed after ten.
# Doplňování
## She ___ (watch) a series every evening.
= watches
> 3. osoba: watches.
## We ___ (not go) to school on Sunday.
= don't go|do not go
> Zápor v 1. osobě množného čísla.
## ___ he play football on Fridays?
= Does
> Otázka ve 3. osobě.
## I usually ___ (get) up at seven.
= get
> Usually nemění tvar slovesa.

View file

@ -0,0 +1,4 @@
---
nazev: A weekend in town
popis: The present continuous for arrangements. Listening.
---

View file

@ -0,0 +1,80 @@
---
id: anglictina-1-vikend-ve-meste
puvodni: en.1.3
nazev: Víkend ve městě
popis: Přítomný čas průběhový pro domluvené plány. Poslech.
---
# Výklad
## Co právě probíhá a co je domluvené
*Continuous*
- Tvar: am/is/are + -ing. I'm waiting. She isn't coming.
- Teď: Look, it is raining. Please be quiet, the baby is sleeping.
- Blízký plán: We're having lunch on Saturday. I'm playing volleyball.
- Stavová slovesa obvykle průběhový čas nemají: I know, I like, I want.
> Tip: I'm meeting Eva at ten je plán, ne jen „potkávám“.
## Jak poslouchat vzkaz
*Poslech*
- Chyťte jména, časy a místa. Ta se v otázkách opakují.
- Nenechte se zastavit jedním slovem. Poslouchejte dál.
- I'm meeting, we're going, I'm playing jsou v tomhle vzkazu klíčové.
- Na konci bývá prosba: Call me back.
> Tip: nejdřív si přečtěte otázky, pak teprve pusťte nahrávku.
# Poslech
Hi Tom, it's Anna. Are you free on Saturday? I'm meeting Eva at the station at ten. We're going to the market first, and then we're having lunch in that new café by the river. In the afternoon I'm playing volleyball with my class in the park. Do you want to come to the café? Call me back tonight.
## Who is Anna meeting?
- Tom
- [x] Eva
- Her class
- Her mum
> I'm meeting Eva.
## Where are they meeting?
- At school
- [x] At the station
- In the park
- By the river only
> at the station at ten.
## What is Anna doing in the afternoon?
- Going to the market
- Having lunch
- [x] Playing volleyball
- Sleeping
> I'm playing volleyball.
## What should Tom do?
- Buy tickets
- [x] Call her back
- Come to school
- Bring a map
> Call me back tonight.
# Doplňování
## Listen, the phone ___.
= is ringing
> Děje se to teď.
## We ___ lunch with Eva on Saturday.
= are having|'re having
> Domluvený plán.
## I ___ not coming to the match.
= am|'m
> Zápor: am not.
## She ___ volleyball this afternoon.
= is playing|'s playing
> Domluvená aktivita.

View file

@ -0,0 +1,10 @@
---
nazev: My room
popis: There is / there are and prepositions. A short description and a quiz.
---
# Překlady
| Česky | Anglicky |
|---|---|
| on | he |

View file

@ -0,0 +1,78 @@
---
id: anglictina-1-muj-pokoj
puvodni: en.1.4
nazev: Můj pokoj
popis: There is / there are a předložky místa. Krátký popis a kvíz.
---
# Výklad
## Co v pokoji je
*There is*
- There is a desk under the window. There are two chairs.
- Zápor: There isn't a TV. There aren't any posters.
- Otázka: Is there a lamp? Are there any books?
- Předložky: in, on, under, next to, between, behind, in front of.
> Tip: there is pro jedno, there are pro více. There je formální podmět.
## Popis místa
*Psaní*
- Začněte velikostí a světlem: small but bright.
- Každá věta ať má věc a místo: The bag is under the chair.
- Střídejte there is a there are, ať text není seznam.
- Na konci jedna osobní věta: I like it because it is quiet.
> Tip: jděte od velkého k malému, od dveří k oknu.
# Psaní
Describe your room in at least 30 words. Say what is in it and where things are. Use on and a piece of furniture such as a bed, and mention a window.
## Vzorová odpověď
My room is small but bright. There is a bed next to the window and a wooden desk under a shelf. My books are on the desk and my school bag is under the chair. There are two posters on the wall. I like the room because it is quiet in the evening.
## Musí obsahovat
- bed
- window
- on
## Minimum slov
30
# Kvíz
## The lamp is ___ the desk.
- under
- [x] on
- between
- at
> Lampa stojí na desce: on.
## There ___ a big window.
- are
- [x] is
- be
- have
> Jedno okno: there is.
## There ___ two posters on the wall.
- is
- [x] are
- has
- been
> Dva plakáty: there are.
## The cat is sleeping ___ the bed.
- [x] under
- next
- between to
- at to
> Pod postelí: under the bed.

View file

@ -0,0 +1,4 @@
---
nazev: At the market
popis: Countability, some and any, how much and how many. Reading and gaps.
---

View file

@ -0,0 +1,80 @@
---
id: anglictina-1-na-trhu
puvodni: en.1.5
nazev: Na trhu
popis: Počitatelnost, some a any, how much a how many. Čtení a doplňování.
---
# Výklad
## Počitatelné a nepočitatelné
*Množství*
- Počitatelné: an apple, some apples. Nepočitatelné: some bread, some milk.
- How many apples? How much milk?
- a little milk, a few rolls, a lot of cheese.
- V obchodě: How much is it? It's four euros a kilo.
> Tip: some v kladné větě, any v otázce a záporu. Nabídka je výjimka: Would you like some tea?
## Zdvořilá žádost
*Dialog*
- Have you got any bread? — Sorry, we haven't got any today.
- We've got some rolls instead.
- How many would you like? — Six, please.
- Anything else? — No, that's all. Thank you.
> Tip: Can I have… a I'd like… zní v obchodě přirozeně.
# Čtení
On Saturday morning the market by the river is busy. A woman asks, "How much is this cheese?" The seller says, "It's four euros a kilo." Then she asks, "Have you got any bread?" He answers, "Sorry, we haven't got any bread today, but we've got some fresh rolls." "How many rolls do you want?" "Six, please, and a little milk." She also buys a few apples. "Anything else?" "No, that's all."
## How much is the cheese?
- Four euros a piece
- [x] Four euros a kilo
- Six euros
- A little
> four euros a kilo.
## What hasn't the seller got?
- Rolls
- Milk
- [x] Bread
- Apples
> haven't got any bread today.
## How many rolls does the woman want?
- Four
- A few
- [x] Six
- A kilo
> Six, please.
## Which phrase is an offer of more shopping?
- That's all.
- [x] Anything else?
- How much is it?
- A little milk.
> Anything else? nabízí další nákup.
# Doplňování
## We haven't got ___ bread today.
= any
> Zápor: any.
## There are ___ rolls in the bag.
= some
> Kladná věta: some.
## How ___ apples do you want?
= many
> Apples se dají počítat.
## How ___ milk is in the bottle?
= much
> Milk je nepočitatelné.

View file

@ -0,0 +1,4 @@
---
nazev: Last summer
popis: The past simple, regular and irregular verbs. Listening and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-1-minule-leto
puvodni: en.1.6
nazev: Minulé léto
popis: Minulý čas prostý, pravidelná i nepravidelná slovesa. Poslech a kvíz.
---
# Výklad
## Co se stalo a je dokončené
*Past simple*
- Pravidelná: play → played, visit → visited, try → tried, stop → stopped.
- Nepravidelná: go → went, take → took, swim → swam, buy → bought.
- I didn't stay in a hotel. Did you visit the island?
- Signály: yesterday, last July, ago, in 2019, when I was ten.
> Tip: zápor a otázka používají did, sloveso zůstává v základním tvaru.
## Jedna událost za druhou
*Vyprávění*
- Začněte kdy a kam: Last July we went to Croatia.
- Pak doprava, počasí a jedna konkrétní příhoda.
- Nepravidelná slovesa se neučí po jednom u testu, ale ve větě.
- Na konci dojem: It was the best week of the year.
> Tip: then, after that a one day drží příběh pohromadě.
# Poslech
Last July we went to Croatia. We travelled by train and then by bus because the flight was too expensive. The weather was hot every day. I swam in the morning and my brother tried surfing. He fell off the board a lot, but he loved it. One day we visited a small island. We didn't stay in a hotel. We stayed with our uncle near the beach. I took hundreds of photos.
## Where did they go?
- Italy
- [x] Croatia
- Greece
- Spain
> went to Croatia.
## How did they travel?
- By plane
- [x] By train and bus
- By car
- By ferry only
> by train and then by bus.
## What did the brother try?
- Swimming
- [x] Surfing
- Cooking
- Driving
> tried surfing.
## Where did they stay?
- In a hotel
- On the island
- [x] With their uncle
- On the train
> stayed with our uncle.
# Kvíz
## go → ___
- goed
- [x] went
- gone
- going
> go, went, gone.
## I ___ a lot of photos.
- taked
- [x] took
- taken
- token
> take, took, taken.
## She ___ in the sea every morning.
- swimmed
- [x] swam
- swum
- swims
> Minulý tvar swim je swam.
## We ___ stay in a hotel.
- don't
- [x] didn't
- wasn't
- haven't
> Zápor minulého času: didn't + sloveso.

View file

@ -0,0 +1,4 @@
---
nazev: A message to a friend
popis: Will and going to. A short informal message and gaps.
---

View file

@ -0,0 +1,66 @@
---
id: anglictina-1-zprava-kamaradovi
puvodni: en.1.7
nazev: Zpráva kamarádovi
popis: Will a going to. Krátká neformální zpráva a doplňování.
---
# Výklad
## Plán, nebo rozhodnutí teď
*Budoucnost*
- We've got tickets. We're going to leave at six. To je plán.
- Look at those clouds. It's going to rain. To je důkaz.
- The phone is ringing. I'll answer it. Rozhodnutí teď.
- I think I'll stay at home. Will jako názor, ne jistý plán.
> Tip: going to má důkaz nebo plán. Will je rozhodnutí v tu chvíli nebo odhad.
## Neformální styl
*Zpráva*
- Začněte Hi a jménem, ne Dear Sir.
- Napište kdy, co a proč. Jedna věta s because stačí.
- I'm going to… je v takové zprávě přirozené.
- Konec: Want to come? Write back.
> Tip: Hi, kontraktované tvary a na konci otázka kamarádovi.
# Doplňování
## Look at those clouds. It ___ rain.
= is going to|'s going to
> Je vidět důkaz: going to.
## I think I ___ stay at home tonight.
= will|'ll
> Názor v tu chvíli: will.
## The tickets are in my bag. We ___ leave at six.
= are going to|'re going to
> Už je to domluvené.
## Don't worry. I ___ help you with the bags.
= will|'ll
> Rozhodnutí teď: will.
# Psaní
Write a short message to a friend about next weekend. Use at least 25 words. Say what you are going to do, mention the weekend, and give a reason with because.
## Vzorová odpověď
Hi Lena, next weekend I am going to visit my cousins in Pilsen. We are going to the cinema on Saturday because a new film is on. On Sunday we are going to cook lunch together. Do you want to come with me? Write back soon.
## Musí obsahovat
- going
- weekend
- because
## Minimum slov
25

View file

@ -0,0 +1,4 @@
---
nazev: Year 1 test
popis: Reading, listening, grammar and a short piece of writing. Level A2.
---

View file

@ -0,0 +1,129 @@
---
id: anglictina-1-test-1-rocniku
puvodni: en.1.8
nazev: Test 1. ročníku
popis: Čtení, poslech, gramatika a krátké psaní. Úroveň A2.
---
# Výklad
## A2 v jedné sadě
*Co test bere*
- Čtení: najděte v textu čas, místo a důvod, nehádejte z jednoho slova.
- Poslech: jména a čísla si poznamenejte hned.
- Gramatika: be, přítomné časy, some/any, minulý čas, will a going to.
- Psaní: krátký souvislý text, ne jen odrážky.
> Tip: nejdřív otázky, pak text nebo nahrávku. U psaní počítejte slova.
## Nejčastější čechismy
*Jak opravovat*
- I am 16, ne I have 16 years.
- She likes, ne she like.
- Did you went je špatně. Did you go.
- There is a book, ne It is a book on the table, když věc uvádíte.
> Tip: po kontrole si přečtěte nápovědy, i když je věta správně.
# Čtení
Klara lives in a flat with her parents and a small dog. On weekdays she walks to school because it is only ten minutes away. She loves art. Last Friday her class visited a gallery and she bought a poster. This Saturday she is meeting her cousin at the station. They are going to a concert in the evening.
## How does Klara get to school?
- By tram
- [x] She walks
- Her dad drives
- By train
> she walks to school.
## What did she buy at the gallery?
- A dog
- A ticket
- [x] A poster
- A flat
> she bought a poster.
## Who is she meeting on Saturday?
- Her parents
- Her art teacher
- [x] Her cousin
- The singer
> meeting her cousin.
# Poslech
Hi, it's Marek. I missed you after school. I'm at the sports centre. The match starts at four, not at three, so don't hurry. Can you bring my blue jacket? It's on the chair in the kitchen. See you there.
## Where is Marek?
- At school
- [x] At the sports centre
- In the kitchen
- On the tram
> at the sports centre.
## What time does the match start?
- At three
- [x] At four
- At five
- At ten
> starts at four, not at three.
## What should his friend bring?
- A ticket
- [x] A blue jacket
- Lunch
- A poster
> bring my blue jacket.
# Psaní
Write at least 30 words about your family. Use is or are, and the words family and because.
## Vzorová odpověď
My family is not very big. I live with my parents and my younger sister. She is twelve and she is funny. We get on well because we both like films. My dad is a driver and my mum is a nurse. I am happy at home.
## Musí obsahovat
- family
- because
## Minimum slov
30
# Kvíz
## She ___ coffee.
- don't like
- [x] doesn't like
- not like
- isn't like
> 3. osoba: doesn't like.
## There ___ any milk.
- [x] isn't
- aren't
- hasn't
- don't
> Milk je jednotné: there isn't.
## Yesterday we ___ to the lake.
- go
- [x] went
- gone
- going
> Minulý tvar go je went.
## I've got the tickets. We ___ leave at noon.
- will
- [x] are going to
- did
- are
> Plán s důkazem: going to.

View file

@ -0,0 +1,4 @@
---
nazev: On the road
popis: Comparison and the past continuous. Gaps and a quiz.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-2-na-cestach
puvodni: en.2.1
nazev: Na cestách
popis: Stupňování a minulý čas průběhový. Doplňování a kvíz.
---
# Výklad
## Porovnat dvě věci i celou skupinu
*Stupňování*
- cheap → cheaper → the cheapest. Expensive → more expensive → the most expensive.
- Nepravidelné: good → better → the best, bad → worse → the worst, far → further → the furthest.
- as … as: The bus is as fast as the train today.
- than stojí po komparativu: This hostel is cheaper than the hotel.
> Tip: kratší přídavná jména berou -er/-est, delší more/most.
## Děje, který probíhal, když se stalo něco jiného
*Past continuous*
- I was reading when the train stopped.
- While we were waiting, it started to rain.
- Zápor: We weren't sleeping. Otázka: Were you driving?
- Nepleťte si ho s minulým prostým: I walked home popisuje celou cestu.
> Tip: průběhový čas je pozadí, prostý je krátká událost.
# Doplňování
## This ticket is ___ (cheap) than the night train.
= cheaper
> Krátké přídavné jméno: -er.
## It was the ___ (bad) hotel in the town.
= worst
> bad, worse, the worst.
## Prague is not as ___ as London.
= big
> as + základní tvar + as.
## I ___ (read) when the bus suddenly stopped.
= was reading
> Pozadí: past continuous.
## While we ___ (wait), a street musician started to play.
= were waiting
> While + průběhový děj.
# Kvíz
## far → ___ → the furthest
- farer
- [x] further
- more far
- farthest only
> far, further, the furthest. Farthest je taky možné, ale further je v učebnici první.
## Which sentence is correct?
- This café is more cheap than that one.
- [x] This café is cheaper than that one.
- This café is the cheaper than that one.
- This café is cheap than that one.
> cheap → cheaper than.
## She ___ a map when she missed the stop.
- looked
- [x] was looking
- looks
- is looking
> Dělo se to v okamžiku, kdy zmeškala zastávku.
## good → better → ___
- the goodest
- the better
- [x] the best
- the most good
> the best.

View file

@ -0,0 +1,4 @@
---
nazev: At the clinic
popis: Should, must and have to. A leaflet and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-2-u-lekare
puvodni: en.2.2
nazev: U lékaře
popis: Should, must a have to. Čtení letáku a kvíz.
---
# Výklad
## Rada, povinnost, zákaz
*Modální slovesa*
- You should rest. You shouldn't go to school tomorrow. To je rada.
- I must call the doctor. Mluvčí cítí, že to je nutné.
- You have to show your insurance card. To chce ordinace.
- Mustn't je zákaz. Don't have to znamená, že to není nutné.
> Tip: must je silný pocit mluvčího, have to je pravidlo zvenku.
## Jak to říct u přepážky
*Tělo a příznaky*
- a headache, a sore throat, a cough, a temperature, a rash.
- It hurts when I swallow. I feel dizzy.
- How long have you had it? — Since Monday. / For three days.
- Take these tablets twice a day. Drink plenty of water.
> Tip: I've got a sore throat zní přirozeněji než I have pain in throat.
# Čtení
The school clinic is open from seven to three. If you feel ill, you should tell your teacher first. You have to bring your insurance card. You must not come to school with a high temperature. Students with a cough should wear a mask in the waiting room. You don't have to make an appointment for a small cut, but you should come before noon. The nurse can give you a painkiller. She can't give you antibiotics.
## What do you have to bring?
- A mask
- [x] An insurance card
- Antibiotics
- An appointment
> have to bring your insurance card.
## What is forbidden?
- [x] Coming with a high temperature
- Telling the teacher
- Wearing a mask
- Coming before noon
> must not come with a high temperature.
## You don't have to make an appointment if…
- you need antibiotics
- [x] you have a small cut
- you have a temperature
- you arrive at four
> for a small cut.
## What can't the nurse give you?
- Water
- A painkiller
- [x] Antibiotics
- A mask
> She can't give you antibiotics.
# Kvíz
## You ___ shout in the waiting room. It's a rule.
- don't have to
- [x] mustn't
- shouldn't to
- haven't
> Zákaz: mustn't.
## You ___ wear a tie. Nobody minds.
- mustn't
- [x] don't have to
- must
- should to
> Není to nutné: don't have to.
## I've got a headache. You ___ drink some water and rest.
- [x] should
- must to
- have
- are
> Rada: should.
## Visitors ___ show a card. The clinic asks for it.
- [x] have to
- should to
- don't must
- are have
> Pravidlo zvenku: have to.

View file

@ -0,0 +1,4 @@
---
nazev: A first job
popis: The present perfect, for and since. A job interview and gaps.
---

View file

@ -0,0 +1,84 @@
---
id: anglictina-2-prvni-brigada
puvodni: en.2.3
nazev: První brigáda
popis: Předpřítomný čas, for a since. Poslech pohovoru a doplňování.
---
# Výklad
## Zkušenost a děj až do teď
*Present perfect*
- I have worked in a café. She has never had a paid job.
- Ever v otázce, never v záporném významu: Have you ever used a till?
- For three months = jak dlouho. Since May = od kdy.
- Just, already, yet: I've just finished. I haven't finished yet.
> Tip: have/has + příčestí. Otázka na zkušenost začíná Have you ever…?
## Krátké odpovědi
*Pohovor*
- I'm reliable and I get on well with people.
- I have looked after my neighbours' children.
- I can work on Saturdays, but I can't work on Sunday mornings.
- Could you tell me what the shift is?
> Tip: odpovězte a přidejte jednu konkrétní větu, ať to není jen yes.
# Poslech
Manager: Have you ever worked in a shop? Tereza: Not in a shop, but I have helped in my uncle's bakery since May. Manager: What do you do there? Tereza: I have served customers and I have learned to use the till. I haven't baked bread yet. Manager: Can you start next Saturday? Tereza: Yes. I have already asked my uncle, and he says it's fine. I can't work on Sunday mornings because I have a match.
## Where has Tereza helped?
- In a supermarket
- [x] In her uncle's bakery
- In a hotel
- At school
> in my uncle's bakery.
## Since when?
- Since March
- [x] Since May
- Since Saturday
- For a year
> since May.
## What hasn't she done yet?
- Served customers
- Used the till
- [x] Baked bread
- Asked her uncle
> I haven't baked bread yet.
## Why can't she work on Sunday mornings?
- The bakery is closed
- [x] She has a match
- She is ill
- She has no till
> because I have a match.
# Doplňování
## She ___ (live) here since 2019.
= has lived|'s lived
> Since + předpřítomný čas.
## I have known him ___ three years.
= for
> For + délka.
## Have you ___ (be) to London?
= been
> Been to = mít zkušenost.
## We haven't finished ___.
= yet
> Yet v záporu na konci.
## He has ___ eaten, so he isn't hungry.
= already|just
> Already nebo just.

View file

@ -0,0 +1,4 @@
---
nazev: A film review
popis: Relative clauses with who, which and that. A short review and a quiz.
---

View file

@ -0,0 +1,78 @@
---
id: anglictina-2-recenze-filmu
puvodni: en.2.4
nazev: Recenze filmu
popis: Vztažné věty who, which a that. Krátká recenze a kvíz.
---
# Výklad
## Která osoba, která věc
*Vztažné věty*
- The actor who plays the captain is brilliant.
- A lighthouse which stands on a black rock.
- V definující větě se čárka nepíše: The boy who lives next door.
- Nedefinující věta čárku má a that se v ní nepoužívá: Prague, which I love, …
> Tip: who pro lidi, which pro věci. That jde často pro obojí v definující větě.
## Názor, ne jen děj
*Recenze*
- Jedna věta o ději, jedna o postavě, jedna o tom, co se povedlo.
- I would recommend it to anyone who likes quiet films.
- The ending, which I will not describe, stays with you.
- Hodnocení ať je konkrétní: the music is too loud, the script is sharp.
> Tip: neprozrazujte konec. Napište, pro koho film je.
# Psaní
Write a short review of a film or a book in at least 40 words. Use who once and which once, and say who you would recommend it to.
## Vzorová odpověď
The Lighthouse Keeper is a quiet film which follows a woman on a small island. The actor who plays her hardly speaks, but her face tells the story. The sea, which is almost a character, is beautiful and dangerous. I would recommend it to anyone who likes slow films. The ending is sad but honest.
## Musí obsahovat
- who
- which
- recommend
## Minimum slov
40
# Kvíz
## The nurse ___ helped me was very calm.
- which
- [x] who
- where
- whose it
> Člověk: who.
## The app ___ crashed is useless.
- who
- [x] which
- where
- whom
> Věc: which.
## Prague, ___ is my home city, is full of tourists in July.
- that
- [x] which
- who
- what
> S čárkou that nepatří. Which.
## That's the boy ___ dog ran into the road.
- who
- which
- [x] whose
- that his
> Čí: whose.

View file

@ -0,0 +1,4 @@
---
nazev: Weather and a trip
popis: Too and enough, will and going to in a forecast. Reading and gaps.
---

View file

@ -0,0 +1,80 @@
---
id: anglictina-2-pocasi-a-vylet
puvodni: en.2.5
nazev: Počasí a výlet
popis: Too a enough, will a going to v předpovědi. Čtení a doplňování.
---
# Výklad
## Příliš a dost
*Too / enough*
- It's too cold to swim. The water isn't warm enough.
- Too many people, too much rain.
- Enough + podstatné jméno: enough water. Nebo za přídavným jménem: old enough.
- Not enough time je častější než too little time.
> Tip: too stojí před přídavným jménem, enough za ním.
## Co říct, když se díváte z okna
*Předpověď*
- Those clouds are black. It's going to storm.
- I think it will be sunny on Sunday. To je jen odhad.
- If it rains, we'll stay in the cabin.
- Slovník: foggy, chilly, pouring, a heatwave, a breeze.
> Tip: mraky, které vidíte, vedou k going to. Odhad bez důkazu k will.
# Čtení
We wanted to cycle to the lake, but the morning was too foggy to start. By ten the sun was out and the path was dry enough. There were too many cyclists near the dam, so we took a quieter road. We didn't have enough water, which was a mistake. At the lake a guard said, "Look at that cloud. It's going to pour. I think the storm will last an hour." We were not fast enough. We got soaked.
## Why didn't they start early?
- The bikes were broken
- [x] It was too foggy
- The lake was closed
- They had no map
> too foggy to start.
## Why did they change the road?
- [x] There were too many cyclists
- They were lost
- It was pouring
- The guard sent them back
> too many cyclists near the dam.
## What mistake did they make?
- They forgot the bikes
- [x] They didn't have enough water
- They started at night
- They swam
> didn't have enough water.
## What did the guard predict from the cloud?
- Fog
- A heatwave
- [x] Rain
- Snow
> It's going to pour.
# Doplňování
## The rucksack is ___ heavy to carry.
= too
> Too + přídavné jméno.
## She isn't tall ___ to reach the shelf.
= enough
> Enough za přídavným jménem.
## There is too ___ sugar in this tea.
= much
> Nepočitatelné: too much.
## Look at the sky. It ___ snow.
= is going to|'s going to
> Viditelný důkaz.

View file

@ -0,0 +1,4 @@
---
nazev: A festival
popis: Present perfect versus past simple. Listening and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-2-festival
puvodni: en.2.6
nazev: Festival
popis: Předpřítomný čas proti minulému. Poslech a kvíz.
---
# Výklad
## Kdy je děj uzavřený
*Dva časy*
- I have been to the festival three times. Neříkám kdy.
- I went in 2023. Tehdy pršelo. To už je uzavřené.
- Have you ever slept in a tent? — Yes. I slept in one last June.
- Been to znamená, že už je člověk zpátky. Gone to znamená, že je pořád pryč.
> Tip: jakmile zazní yesterday, last year nebo in 2019, patří minulý čas.
## Co se děje na místě
*Reportáž*
- The gates have just opened.
- Last night the main band played for two hours.
- Have you seen the lanterns yet?
- A local baker has made a cake in the shape of a violin.
> Tip: reportér míchá „už se stalo“ a „bylo to včera večer“.
# Poslech
This is Nora at the river festival. The gates have just opened and hundreds of people have already come in. I have never seen so many lanterns. Last night the main band played for two hours, but tonight's singer hasn't arrived yet. A local baker has made a cake in the shape of a violin. I tried a piece yesterday. It was delicious. Have you ever been here? I came for the first time in 2022, and I have come back every summer since then.
## What has just happened?
- The band has left
- [x] The gates have opened
- It has started to snow
- The baker has closed
> The gates have just opened.
## When did the main band play?
- Just now
- [x] Last night
- In 2022
- Every summer
> Last night the main band played.
## What hasn't happened yet?
- People have come in
- [x] Tonight's singer has arrived
- Nora tried the cake
- The gates opened
> hasn't arrived yet.
## When did Nora come for the first time?
- Yesterday
- Last night
- [x] In 2022
- This morning
> for the first time in 2022.
# Kvíz
## I ___ this play in 2019.
- have seen
- [x] saw
- seen
- have saw
> Rok uzavírá děj: saw.
## She has ___ to Paris. She is still there.
- been
- [x] gone
- went
- going
> Pořád je pryč: gone.
## ___ you ever slept in a tent?
- Did
- [x] Have
- Do
- Were
> Ever + present perfect: Have you ever…?
## We ___ the lanterns yesterday.
- have seen
- [x] saw
- seen
- have watch
> Yesterday → past simple.

View file

@ -0,0 +1,4 @@
---
nazev: If I could
popis: First and second conditionals. Writing and gaps.
---

View file

@ -0,0 +1,66 @@
---
id: anglictina-2-kdyby
puvodni: en.2.7
nazev: Kdyby
popis: První a druhá podmínková věta. Psaní a doplňování.
---
# Výklad
## Reálná a hypotetická
*Podmínky*
- 1. kondicionál: If I finish early, I will call you. Je to možné.
- 2. kondicionál: If I had a car, I would drive. Teď auto nemám.
- V formální druhé podmínce je were: If I were you, I would apologise.
- Unless = if not: We'll miss the bus unless we leave now.
> Tip: v if větě není will. If it rains, we will stay in.
## Jedna reálná, jedna vysněná
*Vlastní věta*
- Napište situaci, která se může stát tento týden.
- Pak situaci, která teď pravda není.
- Would like není druhá podmínka. To je jen přání: I'd like a tea.
- Krátký text ať má oba typy, ať je rozdíl vidět.
> Tip: would se v if větě druhé podmínky neopakuje na obou stranách špatně. If I would have je chyba.
# Doplňování
## If it ___ (rain), we will stay in the tent.
= rains
> 1. podmínka: přítomný čas v if.
## If I ___ (be) you, I would tell the truth.
= were|was
> Were je přesnější, was se taky slyší.
## She would travel more if she ___ (have) time.
= had
> 2. podmínka: minulý tvar.
## We'll be late ___ we hurry.
= unless
> Unless = if not.
# Psaní
Write at least 35 words. Include one real plan with if and will, and one unreal dream with would. Use the word if twice.
## Vzorová odpověď
If I finish my project tonight, I will go climbing with Eva on Saturday. We will take the early bus if the weather is fine. If I had more free time, I would learn the guitar. I would also travel by night train if tickets were cheaper. If is a small word with two very different lives.
## Musí obsahovat
- if
- will
- would
## Minimum slov
35

View file

@ -0,0 +1,4 @@
---
nazev: Year 2 test
popis: Reading, listening, B1 grammar and a short piece of writing.
---

View file

@ -0,0 +1,129 @@
---
id: anglictina-2-test-2-rocniku
puvodni: en.2.8
nazev: Test 2. ročníku
popis: Čtení, poslech, gramatika B1 a krátké psaní.
---
# Výklad
## Co už má držet pohromadě
*B1*
- Stupňování, past continuous, modální slovesa rady a povinnosti.
- Present perfect proti past simple.
- Who a which, too a enough, první a druhá podmínka.
- Psaní: krátký souvislý názor, ne odrážkový seznam.
> Tip: u předpřítomného času hledejte ever, yet, since. U minulého yesterday a rok.
## Jedna věta navíc
*Strategie*
- U čtení škrtněte odpověď, která v textu vůbec není.
- U poslechu si čísla zapište číslicí.
- Mustn't není don't have to.
- If I would have time je chyba. If I had time, I would…
> Tip: v psaní ať je důvod. Because nebo so text zvedne.
# Čtení
The night bus to Brno is cheaper than the train, but it is also slower. Last Friday I was sleeping when the driver stopped at a dark petrol station. A woman who had missed her connection asked if she could sit next to me. She has travelled this route since March because her new job starts at six. If the bus is late, she has to run. She says she would move closer if flats were cheaper.
## The bus is ___ than the train.
- faster and cheaper
- [x] cheaper and slower
- the best
- as fast
> cheaper, but slower.
## What was the writer doing when the bus stopped?
- Reading
- [x] Sleeping
- Running
- Driving
> I was sleeping when the driver stopped.
## Why has the woman travelled this route since March?
- She likes petrol stations
- [x] Her job starts early
- Flats are cheap
- She missed a concert
> her new job starts at six.
# Poslech
Doctor: How long have you had this cough? Adam: Since Sunday. I have also had a sore throat for two days. Doctor: You should rest and drink water. You don't have to stay in bed all day, but you mustn't play football this week. If you still feel ill on Friday, come back.
## Since when has Adam had the cough?
- Since Friday
- [x] Since Sunday
- For a month
- Since March
> Since Sunday.
## What mustn't he do?
- Drink water
- Rest
- [x] Play football
- Come back
> mustn't play football.
## When should he come back?
- [x] If he is still ill on Friday
- Every day
- Never
- On Sunday morning
> If you still feel ill on Friday.
# Psaní
Write at least 35 words about a trip or a job you would like. Use would and because.
## Vzorová odpověď
I would like to work in a small bookshop by the station because I love quiet mornings. I would talk to customers and I would also read in the breaks. If I had the job, I would cycle there. I have never worked in a shop, but I have helped at school events.
## Musí obsahovat
- would
- because
## Minimum slov
35
# Kvíz
## This hostel is ___ than the hotel.
- [x] cheaper
- more cheap
- the cheapest
- cheap
> cheap → cheaper.
## I have lived here ___ 2020.
- for
- [x] since
- ago
- during
> Since + bod v čase.
## You ___ wear a helmet. It's the law.
- must to
- [x] have to
- don't have
- should to
> Pravidlo: have to.
## If I ___ richer, I would take the train.
- am
- will be
- [x] were
- would be
> 2. podmínka: were.

View file

@ -0,0 +1,4 @@
---
nazev: How things are made
popis: The present and past passive. Gaps and a quiz.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-3-jak-se-to-vyrabi
puvodni: en.3.1
nazev: Jak se to vyrábí
popis: Trpný rod v přítomnosti a minulosti. Doplňování a kvíz.
---
# Výklad
## Když je důležitější děj než původce
*Passive*
- English is spoken here. The window was broken last night.
- Původce s by, jen když na něm záleží: The book was written by a nurse.
- Modální trpný rod: The form must be signed. It can be recycled.
- Nelze trpně vyjádřit každé sloveso. Happen trpný rod nemá.
> Tip: be + příčestí. Čas nese sloveso be, ne příčestí.
## Od suroviny k věci
*Popis procesu*
- The beans are picked by hand. They are dried in the sun.
- The jars were labelled yesterday.
- Waste paper is collected every Thursday.
- A new bridge is being built. To už je průběhový trpný rod, zatím stačí ho poznat.
> Tip: first, then, after that, finally. Původce často chybí, protože je obecný.
# Doplňování
## This bread ___ (bake) every morning.
= is baked
> Přítomný trpný rod.
## The letters ___ (send) yesterday.
= were sent
> Minulý trpný rod, množné číslo.
## The poem ___ (write) by a student.
= was written
> Write → written.
## These bottles can ___ (recycle).
= be recycled
> Modální sloveso + be + příčestí.
## The window ___ (not break) by the wind. Someone hit it.
= wasn't broken|was not broken
> Zápor minulého trpného rodu.
# Kvíz
## Which sentence is passive?
- Someone stole my bike.
- [x] My bike was stolen.
- My bike stole.
- Someone was steal my bike.
> was + stolen.
## The news ___ at ten.
- is reading
- are read
- [x] is read
- read
> News je jednotné: is read.
## You ___ wear a badge. The factory requires it, in the passive of must.
- must be worn a badge
- [x] A badge must be worn
- A badge must worn
- A badge is must wear
> A badge must be worn.
## They built the school in 1998. → The school ___ in 1998.
- built
- [x] was built
- was build
- has built
> was built.

View file

@ -0,0 +1,4 @@
---
nazev: A school people talk about
popis: Reported statements. Reading and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-3-skola-o-ktere-se-mluvi
puvodni: en.3.2
nazev: Škola, o které se mluví
popis: Nepřímá řeč oznamovací. Čtení a kvíz.
---
# Výklad
## Posun času
*Reported speech*
- „I am tired.“ → She said she was tired.
- „I will help.“ → He said he would help.
- „I have finished.“ → She told me she had finished.
- „I can't come.“ → He said he couldn't come.
> Tip: said that, told me that. Tell potřebuje osobu, say ne.
## Pořád to platí
*Kdy čas neposouvat*
- The teacher said that water boils at 100 degrees.
- She said she lives in Kolín, a pořád tam bydlí.
- Zájmena se mění podle smyslu: „my book“ → he said his book.
- Today → that day, tomorrow → the next day, yesterday → the day before. V testu to bude jen u jasných vět.
> Tip: obecné pravdy a situace, které trvají, často zůstávají v přítomném čase.
# Čtení
Our head teacher told the local paper that the school was changing. She said that students would start an hour later next term. One pupil told the reporter that he was pleased because he hated early buses. A parent said that she was worried. She said her daughter had joined three clubs and had very little time. The cook said that lunch would still be at noon. He added that the menu was going to include more vegetables.
## What did the head teacher say about the start of school?
- [x] It would start an hour later
- It would start at night
- It had already closed
- Clubs would end
> students would start an hour later.
## Why was the pupil pleased?
- He liked buses
- [x] He hated early buses
- He wanted less lunch
- He had three clubs
> because he hated early buses.
## What had the daughter done?
- She had left school
- [x] She had joined three clubs
- She had become a cook
- She had bought a bus
> had joined three clubs.
## Lunch, according to the cook, …
- would move to the evening
- [x] would still be at noon
- had been cancelled
- was only vegetables
> would still be at noon.
# Kvíz
## „I am hungry.“ → She said she ___ hungry.
- is
- [x] was
- has
- were be
> am → was.
## „I will call.“ → He said he ___ call.
- will
- [x] would
- called
- has
> will → would.
## Which verb needs a person?
- say
- [x] tell
- speak that
- talk that
> tell somebody.
## She said, „I can't swim.“ → She said she ___.
- can't swim
- [x] couldn't swim
- doesn't swim
- hasn't swum
> can → could. V nepřímé řeči o minulém rozhovoru.

View file

@ -0,0 +1,4 @@
---
nazev: The last beech
popis: Conditionals and environment vocabulary. A podcast and gaps.
---

View file

@ -0,0 +1,80 @@
---
id: anglictina-3-posledni-buk
puvodni: en.3.3
nazev: Poslední buk
popis: Podmínky a slovník prostředí. Poslech podcastu a doplňování.
---
# Výklad
## Konkrétní slova, ne jen nature
*Prostředí*
- A drought is a long time without rain. Soil is the earth plants grow in.
- Unless it rains, the young trees will die.
- If the town banned cars, the air would be cleaner. Teď je nezakázalo, proto would.
- If people keep using the road, the soil will wash away. To se může stát.
> Tip: drought, soil, volunteer, ban, shortcut. U maturitního tématu zabírají víc než slovo pollution samo.
## Kdo co řekl
*Podcast*
- Ranger = strážce lesa.
- Young trees were planted, but many may still die.
- A shortcut šetří čas a někdy ničí místo.
- Čísla si zapište: two thousand.
> Tip: u nepřímé řeči v poslechu si pamatujte sloveso řekl/řekla a pak obsah.
# Poslech
Last month our class recorded a podcast called The Last Beech. A ranger said that the trees were thirsty because the summers had become hotter. She told us that volunteers had planted two thousand young trees, but she added that many would die unless it rained. If the town banned cars from the forest road, the air would be cleaner. If people keep using it as a shortcut, the soil will wash away.
## What did the ranger say about the trees?
- [x] They were thirsty
- They had been cut down
- They were a shortcut
- They banned cars
> the trees were thirsty.
## How many young trees had volunteers planted?
- Two hundred
- [x] Two thousand
- Twenty
- None
> two thousand.
## The young trees will die unless…
- people drive there
- [x] it rains
- the podcast ends
- the soil is sold
> unless it rained / unless it rains. V textu: unless it rained v nepřímé řeči.
## What would make the air cleaner?
- [x] Banning cars from the forest road
- Using the road as a shortcut
- Cutting the beeches
- Washing the soil
> If the town banned cars.
# Doplňování
## If we ___ (ban) cars here, the air would be cleaner.
= banned
> 2. podmínka.
## The soil will wash away ___ people stop using the shortcut.
= unless
> Unless = if not. Pozor: věta chce if not ve významu dokud nepřestanou. Unless people stop = if they don't stop.
## Many young trees ___ (plant) last month.
= were planted
> Minulý trpný rod.
## She said the summers ___ (become) hotter.
= had become
> Posun předpřítomného času.

View file

@ -0,0 +1,4 @@
---
nazev: For and against
popis: Essay structure and linking words. An essay and a quiz.
---

View file

@ -0,0 +1,78 @@
---
id: anglictina-3-pro-a-proti
puvodni: en.3.4
nazev: Pro a proti
popis: Stavba úvahy a spojovací výrazy. Esej a kvíz.
---
# Výklad
## Úvod, dvě strany, závěr
*Čtyři kroky*
- Úvod: o čem text je a že existují dva pohledy.
- Jeden odstavec pro výhody, jeden pro nevýhody.
- On the one hand … On the other hand …
- Závěr: váš názor. In my opinion, I believe. Nepřidávejte úplně nový argument.
> Tip: v úvodu otázku neopakujte doslova. Převeďte ji vlastními slovy.
## However není because
*Spojky*
- School uniforms are cheaper. However, they limit personality.
- Although uniforms save time, many students hate them.
- Because dává důvod. So dává výsledek.
- In conclusion shrnuje. Nepíše se In the conclusion.
> Tip: however stojí mezi větami a odděluje se čárkou. Although spojuje věty v jednu.
# Psaní
Write a for-and-against paragraph of at least 50 words about school uniforms. Use however and because, and finish with your opinion using believe.
## Vzorová odpověď
School uniforms are a common argument. They are practical because students do not have to decide what to wear, and families spend less money. However, a uniform hides personality and can feel childish at eighteen. Although it makes mornings faster, it does not make lessons better. I believe schools should keep a simple dress code, but they should not force one jacket on everyone.
## Musí obsahovat
- however
- because
- believe
## Minimum slov
50
# Kvíz
## Which linker shows contrast?
- because
- [x] however
- so
- for example
> However staví protiklad.
## ___ uniforms save time, students still complain.
- However
- [x] Although
- So
- Believe
> Although spojuje dvě části jedné věty.
## Choose the best ending of an essay.
- In conclusion, I have no idea.
- [x] In conclusion, I believe a simple rule is enough.
- Because however so.
- To begin with, the essay starts now.
> Závěr má názor, ne nový zmatek.
## Because introduces…
- a result only
- [x] a reason
- a list of names
- a question
> Because = důvod.

View file

@ -0,0 +1,4 @@
---
nazev: It must have been
popis: Modals of deduction. A short mystery and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-3-kdo-to-musel-byt
puvodni: en.3.5
nazev: Kdo to musel být
popis: Modální slovesa dedukce. Čtení záhady a kvíz.
---
# Výklad
## Jisté, možné, vyloučené
*Dedukce*
- The lights are on. She must be at home.
- He might be in the lab. I'm not sure.
- That can't be Eva. Eva is in Vienna this week.
- O minulosti: must have left, might have forgotten, can't have seen it.
> Tip: must je skoro jistota, might je možnost, can't je „to přece nejde“.
## Čtěte důkazy, ne nadpis
*Vynález*
- A notebook with today's date is stronger than a guess.
- Can't have znamená, že to odporuje faktu.
- Might have nechává dvě možnosti otevřené.
- Nepoužívejte can pro dedukci v kladné větě. Can umí, must musí být.
> Tip: u must have hledejte důkaz, který už nejde rozumně popřít.
# Čtení
Someone left a strange machine in the physics lab. It was warm, so it must have been switched on recently. The only key can't have been used by the caretaker, because he is in hospital. Two students, Nora and Adam, know the code. Nora might have come back after the club, but her train ticket shows she was in Pardubice. Adam's notebook is on the bench and the handwriting matches the label. He must have built it.
## Why must the machine have been on recently?
- It was noisy
- [x] It was warm
- It was new
- It was labelled
> It was warm.
## Why can't the caretaker have used the key?
- He lost it
- [x] He is in hospital
- He hates physics
- He was in Pardubice
> he is in hospital.
## Where was Nora?
- In the lab
- [x] In Pardubice
- In hospital
- On the bench
> her ticket shows Pardubice.
## Who must have built the machine?
- The caretaker
- Nora
- [x] Adam
- Nobody
> The notebook and the handwriting point to Adam.
# Kvíz
## The window is broken and his bag is gone. He ___ left in a hurry.
- [x] must have
- must
- can have
- should
> Minulá skoro jistota: must have left.
## That ___ be Lea. Lea is abroad.
- must
- [x] can't
- should
- is
> Odporuje faktu: can't.
## I'm not sure. They ___ have missed the bus.
- must
- can't
- [x] might
- are
> Možnost: might have.
## Which sentence is a deduction, not a duty?
- You must wear gloves.
- [x] She must be tired.
- You must sign this.
- Visitors must wait.
> She must be tired je úsudek.

View file

@ -0,0 +1,4 @@
---
nazev: The news desk
popis: Reported questions. An interview and a quiz.
---

View file

@ -0,0 +1,92 @@
---
id: anglictina-3-zpravy-dne
puvodni: en.3.6
nazev: Zprávy dne
popis: Nepřímé otázky. Poslech rozhovoru a kvíz.
---
# Výklad
## Otázka se v nepřímé řeči narovná
*Reported questions*
- „Where do you live?“ → She asked me where I lived.
- „Did you see it?“ → He asked if I had seen it.
- Whether a if jsou u zjišťovacích otázek obě možné.
- Tázací výraz zůstává: what, where, why, how long.
> Tip: žádný otazník a pořadí podmětu a slovesa jako v oznamovací větě.
## Novinář se ptá starosty
*Redakce*
- Asked if the bridge was safe.
- Wondered how long the repairs would take.
- The mayor said he didn't know yet.
- Slovesa ask, wonder, want to know.
> Tip: v poslechu odlište otázku reportéra od odpovědi starosty.
# Poslech
Reporter: Mayor, is the old bridge safe? Mayor: I can't promise that. An engineer told me that two cables were weak. Reporter: How long will the repairs take? Mayor: She asked the firm the same question yesterday. They said they didn't know yet. Reporter: Did anyone complain? Mayor: A shopkeeper asked me why the bus stop had moved. I asked her if she could wait until Friday. Reporter: Will you close the bridge? Mayor: We might. I have asked the police whether they can send officers tonight.
## What did the engineer tell the mayor?
- The bridge was new
- [x] Two cables were weak
- The shop was closed
- Friday was impossible
> two cables were weak.
## What didn't the firm know?
- The mayor's name
- [x] How long the repairs would take
- Where the bus stop was
- Who the police were
> They said they didn't know yet. Šlo o délku oprav.
## What did the shopkeeper ask?
- [x] Why the bus stop had moved
- If the mayor could swim
- Where the cables were made
- Whether Friday was a holiday
> why the bus stop had moved.
## Who might come tonight?
- The firm
- The shopkeeper
- [x] Police officers
- The engineer only
> whether they can send officers tonight.
# Kvíz
## „Where do you live?“ → He asked me where I ___.
- did live
- [x] lived
- do live
- living
> Pořadí oznamovací věty: where I lived.
## „Are you ready?“ → She asked if I ___.
- [x] was ready
- am I ready
- ready was
- did ready
> if I was ready.
## Which sentence is wrong?
- He asked where she was.
- [x] He asked where was she.
- He asked if she was tired.
- He wanted to know why she had left.
> V nepřímé otázce není inverze.
## Whether means about the same as…
- because
- [x] if
- however
- whose
> Whether ≈ if v nepřímé zjišťovací otázce.

View file

@ -0,0 +1,4 @@
---
nazev: Phrasal verbs in a story
popis: Common phrasal verbs. Gaps and a quiz.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-3-frazova-slovesa-v-pribehu
puvodni: en.3.7
nazev: Frázová slovesa v příběhu
popis: Běžná frázová slovesa. Doplňování a kvíz.
---
# Výklad
## Look není look after
*Částice mění význam*
- look after = starat se, look for = hledat, look forward to = těšit se.
- give up = vzdát se, find out = zjistit, run out of = dojít zásoba.
- turn off = vypnout, take off = sundat nebo vzlétnout, get on = nastoupit i vycházet.
- Look forward to je následované -ing: I look forward to seeing you.
> Tip: frázové sloveso se učte jako jednu jednotku, s českým protějškem ve větě.
## Jedno sloveso, jedna situace
*Příběh*
- The bus took off není o oblečení. U letadla ano, u autobusu spíš pulled away.
- Take off a coat = sundat. Take off = vzlétnout. Kontext rozhoduje.
- Get on with somebody = vycházet s někým.
- Run out of time je u testu častější než run out time.
> Tip: když částici neznáte, představte si gesto. Off je pryč, up je do konce.
# Doplňování
## Can you look ___ my dog this weekend?
= after
> Look after = postarat se.
## We are looking ___ to the trip.
= forward
> Look forward to.
## Don't give ___. The last chapter is the best.
= up
> Give up = vzdát to.
## I need to find ___ who left the note.
= out
> Find out = zjistit.
## We've run ___ of milk.
= out
> Run out of.
# Kvíz
## Please turn ___ the lights.
- [x] off
- after
- forward
- out of
> Turn off = vypnout.
## She gets ___ well with her neighbours.
- [x] on
- off
- up
- after
> Get on with = vycházet.
## He took ___ his wet coat.
- [x] off
- up to
- after
- forward
> Sundat: take off.
## I look forward to ___ you.
- see
- [x] seeing
- saw
- seen
> To je předložka, následuje -ing.

View file

@ -0,0 +1,4 @@
---
nazev: Year 3 test
popis: Reading, listening, B1+ grammar and a short argument.
---

View file

@ -0,0 +1,129 @@
---
id: anglictina-3-test-3-rocniku
puvodni: en.3.8
nazev: Test 3. ročníku
popis: Čtení, poslech, gramatika B1+ a krátká úvaha.
---
# Výklad
## Trpný rod, řeč, podmínky, dedukce
*B1+*
- was written, must be signed, is spoken.
- said she would, asked where I lived.
- If they banned cars, the air would be cleaner.
- must have, might have, can't have.
> Tip: u trpného rodu hledejte be. U nepřímé otázky žádnou inverzi.
## However a because v jednom textu
*Úvaha ve zkratce*
- Neopakujte zadání první větou.
- Phrasal verbs nechte ve správné částici. Look after není look for.
- Told me, ne said me.
- In conclusion je až na konci, ne v úvodu.
> Tip: jedna výhoda, jeden háček, jedna věta s názorem.
# Čtení
A repair café opened in a former bakery. Broken radios are mended there on Saturdays. The founder said that people would rather repair than buy if they were shown how. She told a reporter that twenty kettles had been saved in March. Visitors have to bring the broken object, but they don't have to pay. If the café closed, a lot of working parts would be thrown away.
## What happens to radios there?
- They are sold
- [x] They are mended
- They are banned
- They are thrown away at once
> are mended.
## How many kettles had been saved in March?
- Twelve
- [x] Twenty
- Two thousand
- None
> twenty kettles.
## What would happen if the café closed?
- [x] Parts would be thrown away
- Radios would be cheaper
- People would pay more
- The bakery would reopen as a shop
> working parts would be thrown away.
# Poslech
Editor: Did the mayor answer you? Journalist: I asked him whether the bridge was safe. He said he didn't know. I also asked how long the repairs would take. He told me that the firm hadn't decided. A resident asked me why the paper hadn't printed a map. I said we would print one tomorrow.
## What did the journalist ask about safety?
- [x] Whether the bridge was safe
- If the paper was closed
- Where the mayor lived
- Why kettles break
> whether the bridge was safe.
## What hadn't the firm done?
- [x] Decided
- Printed a map
- Closed the paper
- Asked the resident
> hadn't decided.
## What did the journalist promise?
- To close the bridge
- [x] To print a map tomorrow
- To repair the cables
- To interview a baker
> we would print one tomorrow.
# Psaní
In at least 40 words, give one reason for repairing things and one objection. Use because and however.
## Vzorová odpověď
Repairing things matters because a kettle can work for years after a small fix. However, some people do not have the tools or the time, so a café where volunteers help is a fair idea. I believe every town should have one quiet room for broken objects. It is slower than shopping, but it is kinder.
## Musí obsahovat
- because
- however
## Minimum slov
40
# Kvíz
## The cake ___ by a student.
- baked
- [x] was baked
- was bake
- has bake
> was baked.
## She asked me where I ___.
- did live
- [x] lived
- do live
- living
> where I lived.
## He ___ be at school. I just saw him there.
- can't
- [x] must
- mustn't
- should to
> Skoro jistota: must.
## Please look ___ the tickets. I can't find them.
- [x] for
- forward
- after
- up of
> Look for = hledat. Look after by bylo hlídat.

View file

@ -0,0 +1,4 @@
---
nazev: The speaking card
popis: Speaking-exam strategy and personal-topic collocations. A quiz and gaps.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-4-maturitni-karticka
puvodni: en.4.1
nazev: Maturitní kartička
popis: Strategie ústní zkoušky a kolokace osobních témat. Kvíz a doplňování.
---
# Výklad
## Čtyři minuty nejsou román
*Ústní zkouška*
- Úvod jednou větou: I'd like to talk about my town, which is smaller than it looks.
- Obecné plus osobní: Many people commute. I cycle, because the centre is close.
- Když slovo chybí, opis: the thing you use to open a tin.
- Na obrázku: In the foreground, in the background, it looks as if.
> Tip: katalog CERMAT chce souvislý projev, popis obrázku, porovnání a reakci na otázky. Ne seznam slov.
## Make, do, have, take
*Kolokace*
- Make a decision, make friends, make the bed.
- Do the washing-up, do a course, do someone a favour.
- Take part in, take care of, take an exam.
- Have a look, have a word with, have trouble with.
> Tip: české „dělat“ se rozpadá. Do homework, make a mistake, take a photo, have a rest.
# Doplňování
## I need to ___ a decision before Friday.
= make
> Make a decision.
## She ___ her homework on the train.
= does|did
> Do homework.
## Can I ___ a photo of the bridge?
= take
> Take a photo.
## Let's ___ a rest. We've walked for hours.
= have
> Have a rest.
## He ___ part in the school play every year.
= takes|took
> Take part in.
# Kvíz
## You don't know the word kettle. What helps?
- Stop the exam
- [x] Paraphrase it
- Switch to Czech for the rest
- Invent a Latin word and insist
> Opis je lepší než ticho.
## In the foreground means…
- at the back
- [x] at the front of the picture
- outside the frame
- in the past
> Foreground = popředí.
## Which collocation is right?
- do a mistake
- [x] make a mistake
- take a mistake
- have a mistake
> Make a mistake.
## A strong speaking answer…
- is a list of single words
- [x] adds a personal example
- translates the question
- avoids all verbs
> Obecné plus vlastní příklad.

View file

@ -0,0 +1,4 @@
---
nazev: A formal email
popis: A request and a complaint. Writing and a register quiz.
---

View file

@ -0,0 +1,78 @@
---
id: anglictina-4-formalni-e-mail
puvodni: en.4.2
nazev: Formální e-mail
popis: Žádost a stížnost. Psaní a kvíz rejstříku.
---
# Výklad
## Komu, proč, co chcete, jak se loučíte
*Kostra*
- I am writing to apply for… / to complain about…
- I would be grateful if you could…
- Kontrakce v čistě formálním textu omezte: I am, not I'm.
- Na konci celé jméno. Žádné Cheers ani xoxo.
> Tip: neznámé jméno → Dear Sir or Madam a Yours faithfully. Známé jméno → Dear Ms Novák a Yours sincerely.
## Fakta, dopad, žádost
*Stížnost*
- The train on 3 May, which I had booked, arrived 90 minutes late.
- As a result I missed the interview.
- I would like a refund or an explanation.
- I look forward to your reply.
> Tip: zlost text oslabuje. Datum, číslo spoje a klidná žádost ho posílí.
# Psaní
Write a formal email of at least 55 words to a railway company. Complain about a delay. Start with Dear, use would, and end with sincerely or faithfully.
## Vzorová odpověď
Dear Sir or Madam, I am writing to complain about the night train to Berlin on 3 May. It arrived ninety minutes late, so I missed a morning interview. I would be grateful if you could explain the delay and offer a refund. I have attached my ticket. Please reply to this address. Yours faithfully, Tereza Malá.
## Musí obsahovat
- dear
- would
- faithfully
## Minimum slov
55
# Kvíz
## You do not know the person's name. You end with…
- [x] Yours faithfully
- Yours sincerely
- Cheers
- Love
> Neznámé jméno: faithfully. Po Dear Sir or Madam.
## Dear Ms Novák, … ___
- Yours faithfully
- [x] Yours sincerely
- Hi again
- Bye
> Jméno znáte: sincerely.
## Which opening is formal?
- Hey!!!
- [x] I am writing to apply for the post.
- I wanna the job.
- What's up
> I am writing to…
## A formal request sounds like…
- Give me a refund now.
- [x] I would be grateful if you could look into this.
- You guys messed up.
- Send money pls.
> I would be grateful if you could…

View file

@ -0,0 +1,4 @@
---
nazev: The night train
popis: A longer reading in the style of the written paper. Four questions.
---

View file

@ -0,0 +1,68 @@
---
id: anglictina-4-nocni-vlak
puvodni: en.4.3
nazev: Noční vlak
popis: Delší čtení ve stylu didaktického testu. Čtyři otázky k článku.
---
# Výklad
## Nečtěte každé slovo stejně pomalu
*Čtení u maturity*
- Hlavní myšlenka bývá v úvodu nebo v závěru, ne v jednom příkladu.
- Distraktor často opakuje slovo z textu, ale v jiném vztahu.
- Which surprises… uvádí postoj, ne jen fakt o ceně.
- Not given v tomhle cvičení není. Jedna možnost je opřená o text.
> Tip: u výběru ze čtyř možností nejdřív zjistěte, na co se otázka ptá, a teprve pak hledejte odstavec.
## Boarding gate a carriage
*Slovník z kontextu*
- A boarding gate je letištní východ k letadlu. Text ho staví proti posteli ve vlaku.
- A carriage je vůz vlaku.
- Demand = poptávka. If demand stays high, přidají vozy.
- Deleted journey by byl let, který člověk chce mít co nejkratší.
> Tip: neznámé slovo ohraničte větou před ním a za ním.
# Čtení
When the night train from Prague to Berlin returned last spring, stations filled with people who prefer a bed to a boarding gate. A single ticket is often more expensive than a flight, which surprises anyone who thinks trains are always the cheap choice. Still, passengers say they arrive calmer. They can read, talk and avoid the bright shops of an airport. Critics point out that a delay of two hours is common and that the cabins are small. The railway has promised that new carriages will be added if demand stays high. A student I spoke to had taken the train three times. She said she would fly only if she missed the evening departure. For her, the journey is part of the trip, not a problem to be deleted.
## What is the main point?
- Flights have been cancelled
- [x] Some people choose the night train even when it costs more
- Berlin has closed its station
- Students may not travel
> Lístek bývá dražší než let, a přesto lidé vlak volí.
## What surprises some readers?
- [x] That a train ticket can cost more than a flight
- That stations are empty
- That nobody reads
- That the train is free
> more expensive than a flight, which surprises…
## What do critics mention?
- Free meals
- [x] Delays and small cabins
- Too many books
- A lack of stations
> a delay of two hours, cabins are small.
## The student would fly only if…
- the ticket was cheap
- [x] she missed the evening departure
- the cabins were small
- she hated reading
> only if she missed the evening departure.
## New carriages will be added if…
- critics agree
- [x] demand stays high
- flights are banned
- students complain
> if demand stays high.

View file

@ -0,0 +1,4 @@
---
nazev: A camp in Wales
popis: A longer listening: an interview. Five questions.
---

View file

@ -0,0 +1,68 @@
---
id: anglictina-4-tabor-ve-walesu
puvodni: en.4.4
nazev: Tábor ve Walesu
popis: Delší poslech: pohovor. Pět otázek.
---
# Výklad
## Dvě přehrání stačí, když víte co
*Poslech u maturity*
- Otázky si přečtěte dřív. Podtrhněte tázací slovo.
- Čísla a never/already mění význam celé věty.
- Wouldn't force není couldn't.
- I didn't give him any medicine je důležité právě proto, že je zápor.
> Tip: při prvním poslechu chyťte kostru, při druhém doplňte past, podmínku a zápor.
## Slovník pohovoru
*Práce s dětmi*
- For two years je délka, ne bod v čase.
- A quieter task je alternativa, ne trest.
- Although přiznává nevýhodu a názor nemění.
- Boots jsou důkaz, že s deštěm počítá. Proto going to / already.
> Tip: deal with an emergency, refuse, offer, look forward to.
# Poslech
Interviewer: Why did you apply to the summer camp in Wales? Hana: I have worked with children for two years, and I wanted a job where English is the only language. Interviewer: What would you do if a child refused to join an activity? Hana: I wouldn't force them. I would ask why, and I might offer a quieter task. Interviewer: Have you ever dealt with an emergency? Hana: Yes. Last August a boy cut his foot. I cleaned the cut and called the nurse. I didn't give him any medicine. Interviewer: The camp is rainy. Is that a problem? Hana: Not really. I have already bought boots. I look forward to the hills, although I know the work will be tiring.
## Why does Hana want the job?
- She has never met children
- [x] She wants a job where only English is spoken
- She hates hills
- She wants to sell boots
> where English is the only language.
## How long has she worked with children?
- For two weeks
- [x] For two years
- Since last August only
- She hasn't
> for two years.
## What would she do if a child refused an activity?
- Force them
- Send them home
- [x] Ask why and maybe offer a quieter task
- Give them medicine
> I wouldn't force them. I might offer a quieter task.
## What didn't she do in the emergency?
- Clean the cut
- Call the nurse
- [x] Give medicine
- Help the boy
> I didn't give him any medicine.
## How does she feel about the rain?
- [x] She has already bought boots and still looks forward to the hills
- She will refuse the job
- She has never seen rain
- She hates children
> already bought boots, look forward to the hills.

View file

@ -0,0 +1,4 @@
---
nazev: An opinion essay
popis: Opinion-essay structure. An essay and a quiz about paragraphs.
---

View file

@ -0,0 +1,78 @@
---
id: anglictina-4-nazorova-esej
puvodni: en.4.5
nazev: Názorová esej
popis: Struktura názoru. Esej a kvíz o odstavcích.
---
# Výklad
## Nejdřív stanovisko, pak důvody
*Názor*
- I believe that… / In my opinion…
- First reason, second reason, a short answer to the other side.
- Although some people argue that…, I still think…
- Závěr ať zní jako závěr, ne jako třetí nový důvod.
> Tip: u názorové eseje čtenář nemá hádat, na čí straně jste. Řekněte to v úvodu.
## Radši čtyři hutné věty než deset prázdných
*Rozsah*
- Každý důvod ať má příklad z běžného života.
- Nepoužívejte etc. Místo toho jedno konkrétní slovo.
- Spojky: firstly, in addition, on the other hand, in conclusion.
- Kontraktované tvary jsou v názorové eseji v pořádku, když text není stížnost úřadu.
> Tip: u písemné práce se počítají slova. Opakování zadání se nepočítá jako myšlenka.
# Psaní
Write an opinion of at least 60 words: should students have a gap year before university? Use believe, although and in conclusion.
## Vzorová odpověď
I believe a gap year can help, but only when it has a plan. Although some students waste the time, others work, travel or look after a relative and return calmer. A year in a job also shows whether a degree is really necessary. On the other hand, a long break can make it harder to study again. In conclusion, a gap year is useful if the person can explain what they will do with it.
## Musí obsahovat
- believe
- although
- conclusion
## Minimum slov
60
# Kvíz
## Where should your opinion appear in this kind of essay?
- Only in a footnote
- [x] In the introduction and again at the end
- Never
- Only in the title
> Úvod i závěr.
## Although introduces…
- [x] a concession
- a list of books
- a greeting
- a date
> Ústupka: something true that does not win.
## In conclusion should…
- start a brand-new topic
- [x] sum up
- replace the introduction
- ask the examiner a question
> Shrnutí.
## Which is the best topic sentence?
- Gap year gap year gap year.
- [x] A gap year is worth it when it has a clear plan.
- Hello my name is and I will write.
- Etc. etc. etc.
> Věta, která už nese názor.

View file

@ -0,0 +1,4 @@
---
nazev: Topic vocabulary
popis: Collocations for the exam topics. Gaps and a quiz.
---

View file

@ -0,0 +1,82 @@
---
id: anglictina-4-slovni-zasoba-okruhu
puvodni: en.4.6
nazev: Slovní zásoba okruhů
popis: Kolokace k maturitním tématům. Doplňování a kvíz.
---
# Výklad
## Ne izolovaná slovíčka
*Okruhy*
- Heavy rain, strong coffee, fast food, junk food. Ne strong rain.
- A balanced diet, processed food, a food allergy.
- Public transport, rush hour, a traffic jam, a return ticket.
- Renewable energy, a carbon footprint, a heatwave, a drought.
> Tip: ústní zkouška sahá na osobu, rodinu, domov, školu, volný čas, kulturu, sport, cestování, zdraví, jídlo, služby, práci, společnost a přírodu.
## Fráze, které unesou odstavec
*Společnost a práce*
- A gap year, an apprenticeship, a degree, tuition fees.
- Freedom of speech, a stereotype, volunteer work.
- Break the law, pay a fine, a witness.
- Mental health, a check-up, be in pain. Ne have pain in the leg jako jediná možnost: My leg hurts.
> Tip: apply for a job, hand in a notice, earn a living, from home.
# Doplňování
## There was heavy ___ all night.
= rain
> Heavy rain, ne strong rain.
## I'd like a ___ ticket to Brno.
= return
> Return ticket = zpáteční.
## She applied ___ the post of guide.
= for
> Apply for.
## Solar power is ___ energy.
= renewable
> Renewable energy.
## He has a nut ___, so he can't eat the cake.
= allergy
> A food allergy.
# Kvíz
## Which pair is right?
- strong rain / heavy coffee
- [x] heavy rain / strong coffee
- fast coffee / junk rain
- a traffic diet
> Heavy rain, strong coffee.
## Rush hour is…
- a quiet morning
- [x] the busiest time to travel
- a kind of ticket
- a school subject
> Špička.
## An apprenticeship is…
- a fine
- [x] training for a job
- a heatwave
- a stereotype
> Praktická příprava na řemeslo nebo práci.
## My leg ___.
- [x] hurts
- is pain
- has hurted
- does hurtness
> My leg hurts.

View file

@ -0,0 +1,4 @@
---
nazev: Grammar clinic
popis: Mixed points for the language paper. A quiz and gaps.
---

View file

@ -0,0 +1,96 @@
---
id: anglictina-4-gramaticka-ambulance
puvodni: en.4.7
nazev: Gramatická ambulance
popis: Smíšené jevy k didaktickému testu. Kvíz a doplňování.
---
# Výklad
## To, co se v testu sype
*Články a časy*
- I have lived here since May. I lived there in 2018.
- If I were you, I would wait. If it rains, we will wait.
- The book was written in 1962. People speak English here → English is spoken here.
- Used to: I used to walk. Zvyk, který už neplatí. Didn't use to.
> Tip: the night train, a night train, night trains. Člen podle toho, jestli čtenář ví který.
## Slovosled a shoda
*Věta*
- A little time, a few minutes. Much v záporu a otázce, a lot of v kladné větě je bezpečné.
- Suggest + -ing nebo suggest that. Suggest me to go je chyba.
- Enjoy, avoid, mind, can't help + -ing.
- Want, decide, promise, hope + to.
> Tip: never dávejte před sloveso, ale za am/is/are: She is never late. She never arrives late.
# Doplňování
## I ___ (live) in this flat since May.
= have lived|'ve lived
> Since → present perfect.
## She suggested ___ (take) the night train.
= taking
> Suggest + -ing.
## If I ___ you, I would book earlier.
= were|was
> If I were you.
## These letters must ___ (sign) today.
= be signed
> Must be + příčestí.
## I ___ to get up at five. I don't anymore.
= used
> Used to.
# Kvíz
## She enjoys ___.
- to swim
- [x] swimming
- swim
- swam
> Enjoy + -ing.
## I have ___ finished.
- yet
- [x] just
- since
- ago
> Just before the participle. Yet belongs at the end in negatives.
## English ___ all over the building.
- speaks
- [x] is spoken
- is spoke
- speak
> Trpný rod.
## There isn't ___ time.
- many
- [x] much
- a few
- several
> Time je nepočitatelné: much.
## He asked me where ___.
- did I live
- [x] I lived
- do I lived
- lived I
> Bez inverze.
## You ___ shout. The baby is asleep. It is forbidden.
- don't have to
- [x] mustn't
- might
- used to
> Zákaz: mustn't.

View file

@ -0,0 +1,4 @@
---
nazev: A practice paper
popis: Reading, listening, language in use and a short piece of writing.
---

View file

@ -0,0 +1,143 @@
---
id: anglictina-4-cvicna-maturita
puvodni: en.4.8
nazev: Cvičná maturita
popis: Čtení, poslech, jazyková kompetence a krátká písemná práce.
---
# Výklad
## Tři části, jeden mozek
*Celá zkouška*
- Nejdřív přečtěte zadání psaní, ať víte, kolik času si necháte.
- U čtení nehledejte stejná slova. Hledejte stejný význam.
- U poslechu zápor bývá ta správná odpověď.
- Když váháte mezi dvěma, vyhoďte tu, která přidává informaci v textu neslyšenou.
> Tip: didaktický test míchá poslech, čtení a jazykovou kompetenci. Písemná práce je zvlášť. Tady je zmenšený model.
## Účel, čtenář, rozsah
*Písemná práce*
- Řekněte proč píšete hned v první větě.
- Dejte tam čas a místo, ať text není mlha.
- Jedna věta s because a jedna s if textu pomůžou.
- Na konci otázka, která zní jako opravdový zájem.
> Tip: e-mail kamarádovi a stížnost úřadu se nepíšou stejným jazykem. Tady jde o kamaráda.
# Čtení
The town library now lends tools as well as books. Hammers, sewing machines and a cake tin can be borrowed for a week. The idea came from a librarian who was tired of people buying things they would use once. She said the workshop had been visited by more teenagers than the reading room. Critics argue that a library should stay quiet. She replies that a library should be useful. If the experiment works, other branches will copy it. Users have to return each object clean, and they must not lend it to anyone else.
## What can people borrow besides books?
- Cars
- [x] Tools
- Flats
- Tickets to Berlin
> Hammers, sewing machines, a cake tin.
## Why did the idea start?
- [x] People were buying things they would use once
- The reading room was too popular
- Teenagers hated books
- The tools were illegal
> buying things they would use once.
## Who has visited the workshop more, according to her?
- Critics
- [x] Teenagers, more than the reading room
- Other branches
- Nobody
> more teenagers than the reading room.
## What must users not do?
- Return the object
- Clean it
- [x] Lend it to someone else
- Visit the library
> must not lend it to anyone else.
# Poslech
Welcome to the night-train podcast. The 22:40 to Berlin has been delayed by forty minutes because a tree is on the line near Ústí. Passengers who already have a seat can wait in carriage four, where tea is being served. If you have not checked in, please do it on your phone. We would be grateful if you kept the corridor clear. The delay might be shorter. We will know more at ten.
## How long is the delay?
- 14 minutes
- [x] 40 minutes
- 4 hours
- 22 minutes
> forty minutes.
## Why?
- Snow
- [x] A tree on the line
- A missing driver
- A festival
> a tree is on the line.
## Where can passengers with a seat wait?
- [x] Carriage four
- The airport
- Ústí station only
- The corridor
> carriage four.
## What should people without a check-in do?
- Buy tea
- [x] Check in on their phone
- Leave the train
- Call the mayor
> do it on your phone.
# Psaní
Write at least 45 words to a friend. Invite them to borrow something from a library of things. Use because and if.
## Vzorová odpověď
Hi Adam, the library now lends tools, and I think you would like it because you always need a drill for one afternoon. If you come on Saturday, I will show you how it works. You have to bring your card and return everything clean. We could borrow a cake tin and bake something after. Let me know if you are free.
## Musí obsahovat
- because
- if
## Minimum slov
45
# Kvíz
## Tea ___ in carriage four.
- serves
- [x] is being served
- is serving
- has serve
> Průběhový trpný rod.
## We would be grateful if you ___ the corridor clear.
- keep
- [x] kept
- keeping
- to keep
> Would be grateful if + minulý tvar.
## She said the workshop ___ by more teenagers.
- visited
- [x] had been visited
- has visit
- is visiting
> Had been visited.
## Users ___ return the tools clean.
- [x] have to
- must to
- don't must
- are have
> Have to.

View file

@ -0,0 +1,160 @@
# User-facing notes, newest first. Folded into content.json for every app.
# After an update the home screen shows the latest blocks until dismissed.
#
# --- YYYY-MM-DD
# cs: bullet
# en: bullet
--- 2026-10-07
cs: Sítě a technické vybavení: doplněné 2. až 4. ročníky, v každém 8 lekcí
cs: Výklad a kvíz od VLAN a sestavení počítače po maturitu
en: Networks and hardware: years 2 to 4 are filled in, eight lessons each
en: Slides and a quiz, from VLANs and PC assembly through to the exam
--- 2026-10-07
cs: Němčina: čtyři ročníky, 24 lekcí s poslechem, čtením a psaním
cs: Učebnice Neue Freunde zůstává, k ní přibyl kurz od A1 k B1
en: German: four years, 24 lessons with listening, reading and writing
en: The Neue Freunde textbook stays, and a course from A1 to B1 sits beside it
--- 2026-10-07
cs: Angličtina: čtyři ročníky, 32 lekcí podle osnov gymnázia
cs: Poslech, čtení, psaní, doplňování, kvízy a testy od A2 k maturitě
en: English: four years, 32 lessons from the gymnasium syllabus
en: Listening, reading, writing, gap-fill, quizzes and tests from A2 to the exam
--- 2026-10-06
cs: Matematika: opakování základní školy a čtyři ročníky, 40 lekcí
cs: Příklady se píšou a kontrolují, u geometrie se rýsuje na mřížku
en: Mathematics: elementary review and four years, 40 lessons
en: Examples are typed and checked, and some geometry is drawn on a grid
--- 2026-10-06
cs: Fyzika: čtyři ročníky, 32 lekcí podle osnov středních škol
cs: Od mechaniky přes teplo a elektřinu až po optiku, jádro a astrofyziku
en: Physics: four years, 32 lessons from the secondary-school syllabus
en: From mechanics through heat and electricity to optics, the nucleus and astrophysics
--- 2026-10-06
cs: Základy přírodopisných věd: chemie a biologie, v každé 8 lekcí podle osnov
cs: Každá lekce má výklad a kvíz se čtyřmi otázkami
en: Natural sciences: chemistry and biology, eight lessons each, from the syllabus
en: Every lesson has an overview and a four-question quiz
cs: Český jazyk: literatura ve čtyřech ročnících podle osnov středních škol
cs: 32 lekcí od starověku po současnost, každá s výkladem a kvízem
en: Czech: literature across four years, following the secondary-school syllabus
en: 32 lessons from antiquity to the present, each with an overview and a quiz
--- 2026-10-03
cs: Občanská nauka: 4. ročník, 16 lekcí (hospodářství, svět a filozofie)
en: Civics: year 4, 16 lessons (the economy, the world and philosophy)
--- 2026-10-02
cs: Občanská nauka: 1. ročník, 17 lekcí (člověk v lidském společenství)
cs: Občanská nauka: 2. ročník, 16 lekcí (člověk jako občan)
cs: Občanská nauka: 3. ročník, 16 lekcí (člověk a právo)
en: Civics: year 1, 17 lessons (the individual and society)
en: Civics: year 2, 16 lessons (the citizen in a democratic state)
en: Civics: year 3, 16 lessons (the individual and the law)
--- 2026-09-25
cs: Technické vybavení: pět lekcí o digitalizaci zvuku (vzorkování, kvantování, PCM)
cs: Technické vybavení: skříň, zdroj, deska, CPU, karty, disky a vstupní zařízení
cs: Technické vybavení: dosavadní lekce jsou 1. ročník, další ročníky jsou zatím zamčené
en: Hardware: five lessons on digitizing sound (sampling, quantization, PCM)
en: Hardware: case, PSU, board, CPU, cards, drives and input devices
en: Hardware: current lessons are year 1; later years are still locked
--- 2026-09-23
cs: Repozitář se jmenuje Graduately; instalátory a updater míří tam
cs: Balíčky a instalátory se jmenují graduately-* (už ne maturita-*)
cs: Aplikace se jmenuje Graduately; úvod stále říká „Maturita? To dáme.“
cs: Kompletní maturitní četba (82 knih) se zápisky, kvízem a sestavením děje
cs: Studium knih: nejdřív procvičení (kvíz / sestavte děj), pak zápisky
cs: Kvíz a sestavení děje u všech knih i na GTK (Linux / Windows)
cs: Větší a čitelnější rozhraní na Macu
cs: Opravy výběru slov v dialogu a otevření knihy z vyhledávání
en: Repository renamed to Graduately; installers and updater follow
en: Packages and installers are now graduately-* (no more maturita-*)
en: The app is now Graduately; the welcome line still says “Maturita? We've got this.”
en: Full maturita reading list (82 books) with notes, quiz, and plot ordering
en: Book study flow: practice first (quiz / order the plot), then notes
en: Quiz and plot assembly for every book on GTK too (Linux / Windows)
en: Larger, more readable Mac interface
en: Fixes for dialogue word picking and opening a book from search
--- 2026-09-18
cs: Po aktualizaci se na hlavní stránce ukáže, co je nového
cs: Android aplikace se stejnými cvičeními a funkčním updaterem
cs: iOS aplikace nativně na iOS 26 (Liquid Glass) z Maturita.xcworkspace
en: After an update the home screen shows what is new
en: Android app with the same exercises and a working updater
en: Native iOS 26 app (Liquid Glass) from Maturita.xcworkspace
--- 2026-09-17
cs: Vestavěný updater a instalátory, které stáhnou nejnovější build
cs: Vyhledávání předmětů, lekcí a cvičení
cs: Nový vzhled rozhraní a publikování do větve builds
en: Built-in updater and installers that fetch the newest build
en: Search for subjects, lessons and exercises
en: A new interface look and publishing to the builds branch
--- 2026-09-16
cs: Samostatné balíčky pro Linux, macOS a Windows
en: Self-contained packages for Linux, macOS and Windows
--- 2026-09-15
cs: Cvičení z české mluvnice
cs: Ikona aplikace na macOS, Windows i Linuxu
en: Czech grammar exercises
en: App icon on macOS, Windows and Linux
--- 2026-09-14
cs: Česká literatura: 1984 a Spalovač mrtvol
cs: Sítě: ročníky 1–4 a jednotky až po Wi-Fi
cs: Slovní zásoba pro Deutsch jednotky 2 a 3
en: Czech literature: 1984 and The Cremator
en: Networks: years 1–4 and units through Wi-Fi
en: Vocabulary for Deutsch units 2 and 3
--- 2026-09-11
cs: Technické vybavení: von Neumann, historie počítačů, bity a bajty
cs: Deset jednotek sítí s kvízy
cs: Slovní zásoba v Deutsch jednotce 1
en: Hardware: von Neumann, computer history, bits and bytes
en: Ten network units with quizzes
en: Vocabulary in Deutsch unit 1
--- 2026-09-10
cs: Sestavení na Windows
en: Windows build support
--- 2026-09-09
cs: Otevřený předmět sítí
cs: Statistiky postupu po jednotkách
en: Networks subject unlocked
en: Progress statistics by unit
--- 2026-09-08
cs: Deutsch jednotka 3 a vylepšený hangman
en: Deutsch unit 3 and a better hangman
--- 2026-09-07
cs: Z němčiny na více předmětů
cs: Deutsch jednotka 2
cs: Témata, světlý režim a čeština / angličtina
en: From German-only to multiple subjects
en: Deutsch unit 2
en: Themes, light mode and Czech / English
--- 2026-09-06
cs: Cesta cvičení a překlady po kontrole
en: Exercise path and translations after checking
--- 2026-09-05
cs: Cvičení v Deutsch jednotce 1
en: Exercises in Deutsch unit 1
--- 2026-09-03
cs: První verze Graduately
en: First version of Graduately

View file

@ -0,0 +1,17 @@
# Předměty
| Klíč | Česky | Anglicky | Ikona | Cíl | Otevřeno |
|---|---|---|---|---|---|
| Deutsch | Deutsch | Deutsch | de | dehome | ano |
| Správa počítačových sítí | Správa počítačových sítí | Computer Network Administration | wifi | netyears | ano |
| Technické vybavení | Technické vybavení | Computer Hardware | chip | hwyears | ano |
| Český jazyk a literatura | Český jazyk a literatura | Czech Language and Literature | cz | czechmap | ano |
| Občanská nauka | Občanská nauka | Civics | people | onyears | ano |
| English | Angličtina | English | uk | enyears | ano |
| Matematika | Matematika | Mathematics | function | matyears | ano |
| Fyzika | Fyzika | Physics | bolt | fyzyears | ano |
| Základy Přírodopisných věd | Základy Přírodopisných věd | Fundamentals of Natural Sciences | flask | scimap | ano |
| Technická grafika | Technická grafika | Technical Drawing | lock | | ne |
| Prezentační grafika | Prezentační grafika | Presentation Graphics | lock | | ne |
| Programování | Programování | Programming | lock | | ne |
| Programové vybavení | Programové vybavení | Software | lock | | ne |

514
content/aplikace/texty.md Normal file
View file

@ -0,0 +1,514 @@
# Nastavení a aktualizace
| Klíč | Česky | Anglicky |
|---|---|---|
| back | Zpět | Back |
| settings | Nastavení | Settings |
| settings_title | Nastavení | Settings |
| mode | REŽIM | MODE |
| mode_dark | Tmavý | Dark |
| mode_light | Světlý | Light |
| theme | TÉMA | THEME |
| language | JAZYK | LANGUAGE |
| updates | AKTUALIZACE | UPDATES |
| update_current | Build %s | Build %s |
| update_checking | Hledám aktualizace… | Checking for updates… |
| update_uptodate | Máte nejnovější build (%s). | You are up to date (%s). |
| update_available | Je dostupný novější build (%s). | A newer build is available (%s). |
| update_downloading | Stahuji aktualizaci… | Downloading the update… |
| update_staged | Aktualizace je připravená — restartujte aplikaci. | The update is ready — restart the app to finish. |
| update_check | Zkontrolovat | Check |
| update_install | Aktualizovat | Update |
| update_restart | Restartovat | Restart |
| update_later | Později | Later |
| update_failed | Aktualizace se nepovedla. | The update failed. |
| update_err_network | Nepodařilo se zjistit nejnovější verzi. | Could not find out the latest version. |
| update_err_download | Stahování se nepovedlo. | The download failed. |
| update_err_curl | V systému chybí curl, aktualizace nejsou dostupné. | curl is missing, so updates are unavailable. |
| update_err_unsupported | Pro tuto platformu se build nepublikuje. | No build is published for this platform. |
| update_err_readonly | Do umístění aplikace nelze zapisovat. Přesuňte ji třeba do Aplikací. | The app's location is not writable. Move it somewhere else, for example to Applications. |
| update_err_stage | Aktualizaci nešlo připravit. | Could not prepare the update. |
| update_err_devbuild | Tento build nepochází z repozitáře (dev). | This build did not come from the repository (dev). |
| update_ios_sideload | Nový build je na GitHubu. Nainstalujte ho znovu přes Xcode nebo AltStore (%s). | A newer build is on GitHub. Reinstall it with Xcode or AltStore (%s). |
| update_android_allow | Povolte této aplikaci instalovat aplikace a klepněte znovu na Aktualizovat. | Allow this app to install apps, then tap Update again. |
| update_android_installing | Systém teď nabídne instalaci nové verze (%s). | The system will now offer to install the new version (%s). |
| update_err_badapk | Stažený soubor není platný build Graduately. | The downloaded file is not a valid Graduately build. |
# Úvod a novinky
| Klíč | Česky | Anglicky |
|---|---|---|
| welcome_kicker | Graduately | Graduately |
| welcome_title | Maturita?\nTo dáme. | Maturita?\nWe've got this. |
| welcome_body | Procvičování k maturitě v jednom okně. Česky i anglicky, napsané v C studenty ze SSŠVT. | Exam practice in one window. Czech and English, written in C by students from SSŠVT. |
| welcome_body_android | Procvičování k maturitě v telefonu. Česky i anglicky, stejný obsah jako na počítači. | Exam practice on your phone. Czech and English, the same content as on the computer. |
| welcome_body_desktop | Procvičování k maturitě na počítači. Česky i anglicky, stejný obsah jako v telefonu. | Exam practice on your computer. Czech and English, the same content as on your phone. |
| welcome_body_ios | Nativní procvičování na iPhonu. Česky i anglicky, stejný obsah, rozhraní podle iOS. | Native exam practice on iPhone. Czech and English, the same content, adapted to iOS. |
| welcome_body_macos | Nativní procvičování na Macu. Česky i anglicky, stejný obsah, rozhraní podle macOS. | Native exam practice on the Mac. Czech and English, the same content, adapted to macOS. |
| changelog_title | Co je nového | What's new |
| changelog_dismiss | Rozumím | Got it |
| welcome_feat1_title | Efektivní procvičování | Focused practice |
| welcome_feat1_body | Cvičení, nápovědy a vokabeltraining. Aplikace si pamatuje, kde jste skončili. | Exercises, hints and vocabulary drills. The app remembers where you left off. |
| welcome_feat1_link | Začít procvičovat | Start practicing |
| welcome_feat2_title | Osm otevřených předmětů | Eight open subjects |
| welcome_feat2_body | Deutsch, sítě, hardware, čeština, občanská nauka, matematika, fyzika a přírodní vědy. Ostatní předměty čekají na svůj obsah. | German, networks, hardware, Czech, civics, mathematics, physics and natural sciences. The other subjects are still locked. |
| welcome_feat2_link | Jaké předměty jsou otevřené? | Which subjects are open? |
| welcome_stat_value | 8 | 8 |
| welcome_stat_label | otevřené\npředměty | open\nsubjects |
| continue | Pokračuj | Continue |
| roadmap_title | Učební plán | Learning path |
| roadmap_sub | Vyberte jednotku na cestě a začněte procvičovat. | Pick a unit on the path and start practicing. |
# Cvičení
| Klíč | Česky | Anglicky |
|---|---|---|
| check | Zkontrolovat | Check |
| finish | Dokončit | Finish |
| show_sample | Ukázat vzor | Show sample |
| your_answer | Vaše odpověď… | Your answer… |
| feedback_ok | Výborně! | Great! |
| feedback_retry | Ještě to není správně. Zkuste to znovu. | Not quite right yet. Try again. |
| feedback_retry_short | Ještě to není správně. | Not quite right yet. |
| feedback_sentences | Některé věty nejsou správně. Zkuste to znovu. | Some sentences are not correct. Try again. |
| hint_umlauts | Na klávesnici nemáte německé znaky? Můžete psát ä jako ae, ö jako oe, ü jako ue a ß jako ss. | No German keyboard? You can type ä as ae, ö as oe, ü as ue and ß as ss. |
| sample_fmt | Otázka: %s\nVzor: %s (česky: %s) | Question: %s\nSample: %s (English: %s) |
| sub_translate | Přeložte slovo. Špatná odpověď se vrátí na konec. | Translate the word. Wrong answers come back at the end. |
| trans_wrong | Špatně. Správně: %s | Wrong. Correct: %s |
| trans_incomplete | Nepřesné – něco chybí. | Almost – something is missing. |
| trans_progress | Hotovo: %d / %d | Done: %d / %d |
| trans_done | Hotovo! Zvládl(a) jsi všechna slova. | Done! You mastered all the words. |
| assign_hint | Vyberte skupinu a klikněte na kartu. Kliknutím na kartu ve skupině ji vrátíte zpět. | Pick a group and click a card. Click a card inside a group to send it back. |
| wordbank | Nabídka slov: | Word bank: |
| sample_line | Příklad: %s | Sample: %s |
| hm_progress | Slovo %d / %d | Word %d / %d |
| hm_hint | Nápověda | Hint |
| hm_wrong | Slovo je uhodnuto! | Word guessed! |
| hm_fail | Oběšenec! Slovo bylo „%s“. | Hanged! The word was “%s”. |
| hm_retry | Zkusit znovu | Try again |
| hm_done | Výborně! Uhodli jste všechna povolání. | Great! You guessed all the jobs. |
| hm_guess | Hádejte písmeno | Guess a letter |
# Statistiky, hledání a předměty
| Klíč | Česky | Anglicky |
|---|---|---|
| stats | Statistiky | Statistics |
| stats_title | Statistiky | Statistics |
| stats_sub | Přehled vašeho pokroku. | Overview of your progress. |
| stats_ex_label | Cvičení | Exercises |
| stats_pct_label | Hotovo | Done |
| stats_units_label | Jednotky | Units |
| stats_section | Předměty | Subjects |
| stats_locked | Zamčeno | Locked |
| stats_complete | Hotovo | Complete |
| stats_ex_fmt | %d / %d | %d / %d |
| stats_pct_fmt | %d %% | %d %% |
| stats_units_fmt | %d / %d | %d / %d |
| search | Hledat | Search |
| search_placeholder | Hledat předmět, lekci nebo cvičení | Search a subject, lesson or exercise |
| search_empty | Nic se nenašlo | No matches |
| search_hint | Zkuste předmět, lekci nebo název cvičení | Try a subject, lesson or exercise name |
| search_subject | Předmět | Subject |
| search_unit | Jednotka | Unit |
| search_lesson | Lekce | Lesson |
| search_exercise | Cvičení | Exercise |
| search_book | Četba | Reading |
| search_page | Stránka | Page |
| search_home | Úvod | Welcome |
| subjects_title | Předměty | Subjects |
| subjects_sub | Otevřený předmět spustí učební cestu. Zamčené zatím jen čekají. | An open subject opens its path. Locked ones are placeholders for now. |
# Čeština
| Klíč | Česky | Anglicky |
|---|---|---|
| mluv_sub | Cvičení z mluvnice – styl maturita / přijímačky z ČJL | Grammar exercises in the style of the maturita and entrance exams |
| lit_years_sub | Literatura podle osnov středních škol. Vyberte ročník. | Literature from the secondary-school syllabus. Pick a year. |
| lit_year1 | 1. ročník | Year 1 |
| lit_year2 | 2. ročník | Year 2 |
| lit_year3 | 3. ročník | Year 3 |
| lit_year4 | 4. ročník | Year 4 |
| lit_sub | Od literární teorie po osvícenství. | From literary theory to the Enlightenment. |
| lit2_sub | Od národního obrození k moderně. | From the National Revival to the modern movement. |
| lit3_sub | První polovina 20. století. | The first half of the 20th century. |
| lit4_sub | Od roku 1945 k interpretaci textu. | From 1945 to reading a text. |
| czech_sub | Vyberte oblast a začněte procvičovat. | Pick an area and start practicing. |
| Literatura | Literatura | Literature |
| Mluvnice | Mluvnice | Grammar |
| reading_sub | Vyberte knihu, kterou chcete studovat. | Pick a book to study. |
| book_study_hint | Nejdřív si projděte zápisky. Pak si látku ověřte v kvízu a seřaďte děj. | Read the study notes first. Then check yourself with the quiz and order the plot. |
| book_notes | Zápisky k maturitě | Study notes |
| book_practice | Procvičení | Practice |
| book_done | Hotovo | Done |
| book_open | Otevřít | Open |
| book_quiz_heading | Kvíz | Quiz |
| book_1984_sub | Antiutopický román • 1949 | Dystopian novel • 1949 |
| cetba1984_sub | Přehled knihy, klíčové pojmy a cvičení. | Book overview, key terms and exercises. |
| cetba_entry_sub | Údaje ze školního seznamu maturitní četby. | Details from the school maturita reading list. |
| cetba_stub_soon | Podrobný přehled a cvičení zatím nejsou k dispozici. | A detailed overview and exercises are not available yet. |
| lit_quiz_sub | U každé otázky vyberte jednu správnou odpověď. | Pick one correct answer for each question. |
| lit_plot_title | Sestavte děj | Order the plot |
| lit_plot_sub | Přetáhněte části příběhu do správného pořadí. | Drag the story parts into the correct order. |
| lit_next | Další | Next |
| lit_show_result | Zobrazit výsledek | Show result |
| lit_again | Zkusit znovu | Try again |
| lit_question_fmt | Otázka %d / %d | Question %d / %d |
| lit_score_fmt | Skóre: %d / %d | Score: %d / %d |
| lit_finished | Hotovo! | Done! |
| lit_finished_text | Prošli jste všemi otázkami. | You have finished all the questions. |
# Počítačové sítě a hardware
| Klíč | Česky | Anglicky |
|---|---|---|
| net_sub | Vyberte lekci na cestě a začněte procvičovat. | Pick a unit on the path and start practicing. |
| net_years_title | Správa počítačových sítí | Computer Network Administration |
| net_years_sub | Vyberte ročník a začněte procvičovat. | Pick a school year and start practicing. |
| net_year1 | 1. ročník | Year 1 |
| net_year2 | 2. ročník | Year 2 |
| net_year3 | 3. ročník | Year 3 |
| net_year4 | 4. ročník | Year 4 |
| net_year_locked_sub | Zatím bez obsahu | No content yet |
| net_ex_title | Cvičení: výpočet IP adres | Exercise: IP address calculation |
| net_quiz_intro | Vyberte u každé otázky jednu správnou odpověď a stiskněte Zkontrolovat. | Choose one correct answer for each question and press Check. |
| net_slide_next | Další | Next |
| net_slide_prev | Zpět | Back |
| net_slide_start | Přejít na cvičení | Go to the exercise |
| net_solution | Ukázat řešení | Show solution |
| net_infeasible | Toto zadání se do sítě /24 nevejde – vyzkoušej „Ukázat řešení“ a přečti si proč. | This assignment does not fit into a /24 network — tap “Show solution” to see why. |
| hw_sub | Vyberte lekci na cestě a začněte procvičovat. | Pick a unit on the path and start practicing. |
| hw_years_sub | Vyberte ročník a začněte procvičovat. | Pick a school year and start practicing. |
| hw_year1 | 1. ročník | Year 1 |
| hw_year2 | 2. ročník | Year 2 |
| hw_year3 | 3. ročník | Year 3 |
| hw_year4 | 4. ročník | Year 4 |
| hw_year_locked_sub | Zatím bez obsahu | No content yet |
| net2_sub | VLAN, STP, DHCP, DNS, NAT, IPv6, statické směrování a ACL. | VLANs, STP, DHCP, DNS, NAT, IPv6, static routing and ACLs. |
| net3_sub | OSPF, směrování mezi VLAN, WAN, VPN, firewall, QoS a dohled. | OSPF, inter-VLAN routing, WAN, VPN, firewalls, QoS and monitoring. |
| net4_sub | Návrh adres, redundance, 802.1X, hledání chyb, cloud a maturita. | Address plans, redundancy, 802.1X, troubleshooting, cloud and the exam. |
| hw2_sub | Monitory, tisk, sběrnice, chlazení, sestavení, UEFI a diagnostika. | Displays, printing, buses, cooling, assembly, UEFI and diagnostics. |
| hw3_sub | RAID, NAS, servery, virtualizace, zálohy, UPS a firmware. | RAID, NAS, servers, virtualisation, backups, UPS and firmware. |
| hw4_sub | Údržba, TPM, nákup, mobily, periferie, ekologie a maturita. | Maintenance, TPM, buying, mobiles, peripherals, disposal and the exam. |
# Občanská nauka
| Klíč | Česky | Anglicky |
|---|---|---|
| on_sub | Člověk v lidském společenství. Vyberte lekci na cestě. | The individual and society. Pick a lesson on the path. |
| on_years_sub | Vyberte ročník a začněte procvičovat. | Pick a school year and start practicing. |
| on_year1 | 1. ročník | Year 1 |
| on_year2 | 2. ročník | Year 2 |
| on_year3 | 3. ročník | Year 3 |
| on_year4 | 4. ročník | Year 4 |
| on_year_locked_sub | Zatím bez obsahu | No content yet |
| on2_sub | Člověk jako občan v demokratickém státě. Vyberte lekci na cestě. | The citizen in a democratic state. Pick a lesson on the path. |
| on3_sub | Člověk a právo. Vyberte lekci na cestě. | The individual and the law. Pick a lesson on the path. |
| on4_sub | Hospodářství, svět a filozofie. Vyberte lekci na cestě. | The economy, the world and philosophy. Pick a lesson on the path. |
# Přírodní vědy
| Klíč | Česky | Anglicky |
|---|---|---|
| sci_sub | Vyberte chemii nebo biologii. | Pick chemistry or biology. |
| Biologie | Biologie | Biology |
| chem_sub | Osm lekcí od atomu po biochemii. | Eight lessons from the atom to biochemistry. |
| bio_sub | Osm lekcí od buňky po ekologii. | Eight lessons from the cell to ecology. |
# Fyzika
| Klíč | Česky | Anglicky |
|---|---|---|
| fyz_years_sub | Fyzika podle osnov středních škol. Vyberte ročník. | Physics from the secondary-school syllabus. Choose a year. |
| fyz_year1 | 1. ročník | Year 1 |
| fyz_year2 | 2. ročník | Year 2 |
| fyz_year3 | 3. ročník | Year 3 |
| fyz_year4 | 4. ročník | Year 4 |
| fyz_sub | Mechanika: veličiny, pohyb, síly a energie. | Mechanics: quantities, motion, forces and energy. |
| fyz2_sub | Molekulová fyzika, teplo, kmity a vlny. | Molecular physics, heat, oscillations and waves. |
| fyz3_sub | Elektřina a magnetismus. | Electricity and magnetism. |
| fyz4_sub | Optika, relativita, mikročástice a vesmír. | Optics, relativity, the micro-world and the universe. |
# Angličtina a němčina
| Klíč | Česky | Anglicky |
|---|---|---|
| en_years_sub | Čtyři ročníky podle osnov gymnázia, od A2 k maturitě. | Four years of the gymnasium syllabus, from A2 to the exam. |
| en_year1 | 1. ročník | Year 1 |
| en_year2 | 2. ročník | Year 2 |
| en_year3 | 3. ročník | Year 3 |
| en_year4 | 4. ročník | Year 4 |
| en_sec_read | Čtení | Reading |
| en_sec_listen | Poslech | Listening |
| en_sec_write | Psaní | Writing |
| en_sec_gap | Doplňte | Complete |
| en_sec_quiz | Kvíz | Quiz |
| en_sec_test | Jazyková část | Language |
| en_play | Přehrát | Play |
| en_pause | Pauza | Pause |
| en_resume | Pokračovat | Resume |
| en_speed | Rychlost | Speed |
| en_volume | Hlasitost | Volume |
| en_listen_note | Text nahrávky zůstává vidět. Přehrání obstará hlas zařízení. | The recording text stays visible. The device voice plays it. |
| en_listen_first | Nejdřív poslech přehrajte. | Play the recording first. |
| en_listen_fallback | Hlas není dostupný. Text si můžete přečíst. | No voice is available. You can read the text. |
| en_write_short | Text je krátký nebo v něm chybí požadovaná slova. | The text is short or it misses the required words. |
| en_model | Vzor | Model |
| en_write_ph | Pište anglicky… | Write in English… |
| en_y1_sub | Osobní život, škola, domov a minulost. Úroveň A2. | Personal life, school, home and the past. Level A2. |
| en_y2_sub | Cestování, zdraví, práce, média a příroda. Úroveň B1. | Travel, health, work, the media and nature. Level B1. |
| en_y3_sub | Společnost, prostředí, média a složitější gramatika. Úroveň B1 až B2. | Society, the environment, the media and harder grammar. Level B1 to B2. |
| en_y4_sub | Maturitní dovednosti: mluvení, formální psaní, čtení, poslech a gramatika. Úroveň B2. | Exam skills: speaking, formal writing, reading, listening and grammar. Level B2. |
| de_home_sub | Učebnice a čtyři ročníky s poslechem, čtením a psaním. | The textbook plus four years of listening, reading and writing. |
| de_book_title | Učebnice | Textbook |
| de_book_sub | Neue Freunde, Aus aller Welt a Bei uns zu Hause. | Neue Freunde, Aus aller Welt and Bei uns zu Hause. |
| de_years_title | Ročníky | School years |
| de_y1_sub | Pozdravy, rodina, škola, jídlo a den. Úroveň A1. | Greetings, family, school, food and the day. Level A1. |
| de_year1 | 1. ročník | Year 1 |
| de_y2_sub | Město, nákupy, volný čas, bydlení a perfektum. Úroveň A2. | Town, shopping, free time, home and the perfect. Level A2. |
| de_year2 | 2. ročník | Year 2 |
| de_y3_sub | Zdraví, cestování, věty s weil, práce a srovnání. Úroveň A2+. | Health, travel, weil-clauses, work and comparison. Level A2+. |
| de_year3 | 3. ročník | Year 3 |
| de_y4_sub | Média, formální dopis, čtení, konjunktiv a maturitní témata. Úroveň B1. | Media, a formal letter, reading, the subjunctive and exam topics. Level B1. |
| de_year4 | 4. ročník | Year 4 |
# Matematika
| Klíč | Česky | Anglicky |
|---|---|---|
| mat_years_sub | Opakování základní školy a čtyři ročníky gymnázia. | Elementary review and four years of gymnasium mathematics. |
| math_quiz_intro | Napište výsledek. Zlomky jako 3/4, desetinnou čárku i tečku, π jako pi, odmocninu jako sqrt(2) nebo √2. Více čísel oddělte středníkem. | Write the result. Fractions as 3/4, a decimal comma or point, π as pi, a root as sqrt(2) or √2. Separate several numbers with a semicolon. |
| math_answer_ph | výsledek | result |
| math_undo | Zpět | Undo |
| math_clear | Smazat body | Clear points |
| math_draw_hint | Klepněte na mřížku. Osa y směřuje nahoru. | Tap the grid. The y-axis points up. |
| mat0_year | Opakování základní školy | Elementary-school review |
| mat0_sub | Pořadí operací, zlomky, procenta, rovnice a rýsování. | Order of operations, fractions, percentages, equations and a construction. |
| mat_year1 | 1. ročník | Year 1 |
| mat_sub | Dělitelnost, výrazy, rovnice, planimetrie a konstrukce. | Divisibility, expressions, equations, plane figures and a construction. |
| mat_year2 | 2. ročník | Year 2 |
| mat2_sub | Funkce, soustavy, kvadratické rovnice, goniometrie a kružnice. | Functions, systems, quadratic equations, trigonometry and circles. |
| mat_year3 | 3. ročník | Year 3 |
| mat3_sub | Logaritmus, posloupnosti, pravděpodobnost a stereometrie. | Logarithms, sequences, probability and solid geometry. |
| mat_year4 | 4. ročník | Year 4 |
| mat4_sub | Analytická geometrie, derivace, integrál a maturitní opakování. | Coordinate geometry, derivatives, integrals and a maturita review. |
# Ostatní
| Klíč | Česky | Anglicky |
|---|---|---|
| ARP dotaz jde broadcastem | | An ARP request goes out as a broadcast |
# Německá učebnice
| Klíč | Česky | Anglicky |
|---|---|---|
| tvoje | | yours (f.) |
| moje | | mine (f.) |
| u3g_dein | (tvůj) | (your) |
| u3g_sein | (jeho) | (his) |
| u3g_ihr_sie | (její) | (her) |
| u3g_sein_es | (jeho) | (its) |
| u3g_unser | (náš) | (our) |
| u3g_euer | (váš) | (your) |
| u3g_ihr_sie_pl | (jejich) | (their) |
# Další překlady
| Klíč | Česky | Anglicky |
|---|---|---|
| Maturitní četba | Maturitní četba | Maturita reading list |
| 1 B = 8 b | | 1 B = 8 b |
| 2⁸ = 256 hodnot (0–255) | | 2⁸ = 256 values (0–255) |
| Kabely – síť zůstává připojená i po přenosu | | Cables – the network stays connected after transfer |
| WAN = Wide Area Network, např. internet | | WAN = Wide Area Network, e.g. the internet |
| Např. hvězda, kruh, sběrnice | | E.g. star, ring, bus |
| Bez centrálního serveru – stanice si sdílejí data a tiskárny | | No central server – stations share data and printers |
| Server řídí a poskytuje služby, má vyšší výkon | | A server controls and provides services; higher performance |
| Ověřují uživatele a povolují přístup ke službám | | They authenticate users and grant access to services |
| Fileserver = souborový server | | Fileserver = file server |
| Jeden server – při výpadku je služba nedostupná | | One server – if it fails, the service is unavailable |
| Více serverů, každý obsluhuje část – vyšší spolehlivost | | Multiple servers, each handles a part – higher reliability |
| Slouží zároveň i jako pracovní stanice | | It also works as a workstation |
| Jen síťové služby – vysoký výkon a bezpečnost, často v racku | | Network services only – high performance and security, often in a rack |
| Obří sálové počítače (IBM, Siemens) pro banky a státní správu | | Huge mainframe computers (IBM, Siemens) for banks and government |
| SaaS = software jako služba, přístup online z libovolného zařízení | | SaaS = software as a service, online access from any device |
| Více bitů najednou po více vodičích; problém jsou přeslechy | | Multiple bits at once over multiple wires; crosstalk is the problem |
| Přeslechy vznikají při delších kabelech a vysokých frekvencích | | Crosstalk occurs on longer cables and at high frequencies |
| Bit po bitu po jednom vodiči – vyšší frekvence i délky | | Bit by bit over one wire – higher frequencies and longer distances |
| Start-bit (0) synchronizuje, Stop-bit (1) ukončuje znak | | Start bit (0) synchronizes, stop bit (1) ends the character |
| Řízeno společným hodinovým signálem a přesným časováním | | Controlled by a shared clock signal and precise timing |
| Nejslabší – přidá bit pro sudý/lichý počet jedniček | | Weakest – adds a bit for even/odd number of ones |
| Součet znaků jako dvojkových čísel | | Sum of characters treated as binary numbers |
| Nejbezpečnější – počítá se z jednotlivých bitů v bloku | | Most secure – computed from individual bits in the block |
| Analogový je spojitý, digitální má diskrétní hodnoty 0 a 1 | | Analog is continuous; digital has discrete 0 and 1 values |
| Frekvence f = 1/T, kde T je perioda | | Frequency f = 1/T, where T is the period |
| AM mění amplitudu, FM frekvenci, PM fázi | | AM changes amplitude, FM frequency, PM phase |
| QPSK nese 2 bity v prvku, 256-QAM nese 8 bitů | | QPSK carries 2 bits per symbol, 256-QAM carries 8 bits |
| Modulační rychlost = počet změn signálu za sekundu [Bd/s] | | Modulation rate = number of signal changes per second [Bd/s] |
| Přenosová rychlost = množství informace za sekundu [bit/s] | | Bit rate = amount of information per second [bit/s] |
| Šířka pásma určuje maximální možnou přenosovou rychlost | | Bandwidth determines the maximum possible bit rate |
| Datový spoj umožňuje výměnu informací mezi uzly | | A data link enables information exchange between nodes |
| Fyzické médium: kabely, mikrovlny nebo družice | | Physical medium: cables, microwaves or satellites |
| Kanál je jednosměrný souhrn prostředků pro spojení | | A channel is a one-way set of means for a connection |
| Okruh tvoří dva kanály pro obousměrný přenos | | A circuit is made of two channels for two-way transfer |
| Trunking spojuje více cest do jednoho okruhu | | Trunking joins multiple paths into one circuit |
| Trunking zvyšuje šířku pásma spojením cest | | Trunking increases bandwidth by combining paths |
| Multiplex dělí jeden kanál na více logických podkanálů | | Multiplexing splits one channel into multiple logical subchannels |
| FDM: každému kanálu část frekvenčního pásma | | FDM: each channel gets part of the frequency band |
| TDM: každému kanálu vyhrazený časový slot | | TDM: each channel gets a reserved time slot |
| STDM přiděluje kapacitu dle potřeby, negarantuje 100 % | | STDM allocates capacity on demand and does not guarantee 100% |
| Simplex = jen jeden směr přenosu | | Simplex = only one transmission direction |
| Half-duplex = oběma směry, ale ne současně | | Half-duplex = both directions, but not at the same time |
| Full-duplex = oběma směry současně | | Full-duplex = both directions at the same time |
| Agregace = sdílení kapacity kanálu více uživateli | | Aggregation = sharing channel capacity among multiple users |
| Cesta se vytýčí předem, kapacita je garantována | | The path is set up in advance and capacity is guaranteed |
| Data se dělí na pakety, každý může jít jinou cestou | | Data are split into packets; each may take a different path |
| Výhody: reakce na zátěž a lepší využití kapacity | | Advantages: reaction to load and better capacity use |
| Negarantuje pořadí ani plynulost – nevhodné pro živé video | | Does not guarantee order or smoothness – unsuitable for live video |
| Cesta je předem, prostředky se použijí jen při průchodu paketu | | Path is preset; resources are used only when a packet passes |
| Virtuální spoje zrychlují přenos datagramů | | Virtual circuits speed up datagram transfer |
| Standardizace zajišťuje kompatibilitu mezi výrobci | | Standardization ensures compatibility between vendors |
| Dekompozice rozkládá přenos na jednotlivé vrstvy | | Decomposition splits transmission into individual layers |
| Vrstvy komunikují jen se sousedy přes rozhraní SAP | | Layers communicate only with neighbors through SAP interfaces |
| Protokol = pravidla mezi stejnými vrstvami na různých uzlech | | A protocol is rules between the same layers on different nodes |
| Každá vrstva přidává k datům hlavičku (header) | | Each layer adds a header to the data |
| Rámec = linková, paket = síťová, segment = transportní | | Frame = link, packet = network, segment = transport |
| ISO/OSI má 7 vrstev a je univerzální model | | ISO/OSI has 7 layers and is a universal model |
| V praxi je ISO/OSI pomalejší – spolehlivost řeší každá vrstva | | In practice ISO/OSI is slower – each layer handles reliability |
| TCP/IP zvítězil, protože vycházel ze skutečných potřeb | | TCP/IP won because it came from real-world needs |
| V TCP/IP řeší spolehlivost až vyšší vrstvy | | In TCP/IP, reliability is handled by higher layers |
| Fyzická vrstva přenáší bity a řeší médium | | The physical layer transmits bits and handles the medium |
| Spojová vrstva má podvrstvy MAC a LLC | | The data-link layer has MAC and LLC sublayers |
| Síťová vrstva směruje pakety i bez přímého spojení | | The network layer routes packets even without a direct link |
| Transportní vrstva je jen v koncových uzlech, ne v routerech | | The transport layer exists only in end nodes, not in routers |
| Relační vrstva řídí relace, dialog a synchronizaci | | The session layer manages sessions, dialogue and synchronization |
| Prezentační vrstva formátuje data, komprimuje a šifruje | | The presentation layer formats, compresses and encrypts data |
| Aplikační vrstva je rozhraní pro programy a služby | | The application layer is the interface for programs and services |
| Aplikační TCP/IP = aplikační + prezentační + relační ISO/OSI | | TCP/IP application = ISO/OSI application + presentation + session |
| Síťové rozhraní TCP/IP = linková + fyzická ISO/OSI | | TCP/IP network interface = ISO/OSI data-link + physical |
| U TCP/IP vznikly nejdřív protokoly, až pak model | | With TCP/IP, protocols came first and the model later |
| TCP je spolehlivý a spojovaný | | TCP is reliable and connection-oriented |
| UDP je nespolehlivý a rychlý | | UDP is unreliable and fast |
| HTTP, DNS a SMTP jsou aplikační protokoly | | HTTP, DNS and SMTP are application protocols |
| IP, ICMP a OSPF patří na síťovou (internetovou) vrstvu | | IP, ICMP and OSPF belong to the network (internet) layer |
| Ethernet a PPP patří na vrstvu síťového rozhraní | | Ethernet and PPP belong to the network interface layer |
| Sběrnice: médium sdílí všichni | | Bus: the medium is shared by everyone |
| Sběrnice: nízká bezpečnost a porucha kabelu vyřadí síť | | Bus: low security and a cable fault takes down the network |
| Hvězda: uzly jdou přes hub nebo switch | | Star: nodes connect through a hub or switch |
| Hvězda: porucha kabelu k uzlu nevyřadí celou síť | | Star: a cable fault to one node does not take down the whole network |
| Hvězda používá kroucenou dvoulinku nebo optiku | | Star uses twisted pair or fiber |
| V kruhu zprávy obíhají, dokud nenajdou adresáta | | In a ring, messages circulate until they find the recipient |
| Optika: el. impulsy → světlo (LED/laser) → fotodioda | | Optics: electrical pulses → light (LED/laser) → photodiode |
| Jedno vlákno = jednosměrný přenos; obousměrně dvě vlákna | | One fiber = one-way; two fibers for bidirectional use |
| Nevýhody: náročná montáž a cena prvků/konektorů | | Drawbacks: difficult installation and costly parts/connectors |
| MM: jádro 50/62,5 µm a více vidů | | MM: 50/62.5 µm core and multiple modes |
| SM: jádro 7–10 µm, jeden paprsek, laser | | SM: 7–10 µm core, one beam, laser |
| MM typicky používá levnější LED zdroje | | MM typically uses cheaper LED sources |
| Primární ochrana: 250 µm | | Primary protection: 250 µm |
| Sekundární ochrana: 900 µm | | Secondary protection: 900 µm |
| Těsná sekundární ochrana: svislé instalace | | Tight secondary protection: vertical installs |
| Volná sekundární ochrana: vodorovné instalace | | Loose secondary protection: horizontal installs |
| Montáž: lámačka a svářečka optických vláken | | Installation: cleaver and optical fiber splicer |
| Lámání vyžaduje kolmost asi 0,5° | | Cleaving requires roughly 0.5° squareness |
| Kolize = více uzlů vysílá na sdíleném médiu najednou | | A collision is multiple nodes transmitting on a shared medium at once |
| Aloha: vysílej kdykoliv, při chybě znovu | | Aloha: transmit anytime; retry if there is no acknowledgment |
| CSMA/CD patří k Ethernetu | | CSMA/CD belongs to Ethernet |
| CSMA/CD nejdřív poslouchá nosnou (Carrier Sense) | | CSMA/CD first listens for the carrier (Carrier Sense) |
| Po kolizi CSMA/CD čeká náhodný čas a zkusí znovu | | After a collision, CSMA/CD waits a random time and retries |
| CSMA/CA rezervuje médium přes RTS/CTS | | CSMA/CA reserves the medium with RTS/CTS |
| Repeater zesiluje signál na fyzické vrstvě | | A repeater amplifies the signal at the physical layer |
| Transceiver převádí mezi médii (např. metalika ↔ optika) | | A transceiver converts between media (e.g. copper ↔ fiber) |
| Hub rozešle přijatá data do všech ostatních portů | | A hub floods received data to all other ports |
| Bridge/switch = linková vrstva, rámce a MAC | | Bridge/switch = data-link layer, frames and MAC |
| Switch dělí síť na kolizní domény | | A switch splits the network into collision domains |
| Router = síťová vrstva, pakety a IP, propojuje sítě | | A router = network layer, packets and IP, connects networks |
| Gateway převádí mezi odlišnými architekturami/protokoly | | A gateway converts between different architectures/protocols |
| MAC adresy switch ukládá do CAM tabulky | | A switch stores MAC addresses in the CAM table |
| Podle CAM tabulky posílá rámec jen na cílový port | | Using the CAM table, it forwards a frame only to the target port |
| Cut-through čte adresu příjemce a hned přeposílá | | Cut-through reads the destination address and forwards immediately |
| Cut-through je rychlý, ale propustí i chyby | | Cut-through is fast but also forwards errors |
| Store-and-forward kontroluje celý rámec včetně CRC | | Store-and-forward checks the whole frame including CRC |
| Fragment-free čte prvních 64 bytů kvůli kolizím | | Fragment-free reads the first 64 bytes for collision detection |
| Směrování běží na síťové (3.) vrstvě ISO/OSI | | Routing runs on the network (3rd) ISO/OSI layer |
| Adaptivní algoritmy reagují na změny a sdílí stav sítě | | Adaptive algorithms react to changes and share network state |
| Statické cesty nastaví správce a samy se nemění | | Static routes are set by an admin and do not change themselves |
| Centralizované směrování padá s výpadkem centra | | Centralized routing fails if the center fails |
| Distribuované: výměna informací a vlastní tabulky | | Distributed: information exchange and local tables |
| Nižší metrika = lepší / rychlejší trasa | | Lower metric = better / faster route |
| Distance Vector: výměna tabulek se sousedy (např. každých 30 s) | | Distance Vector: exchange tables with neighbors (e.g. every 30 s) |
| Router pracuje na síťové (3.) vrstvě | | A router works at the network (3rd) layer |
| Router segmentuje podsítě a zajišťuje WAN přístup | | A router segments subnets and provides WAN access |
| Firewall filtruje pakety a služby | | A firewall filters packets and services |
| Multiprotokolový router spojuje různé protokoly/standardy | | A multiprotocol router joins different protocols/standards |
| Router dělí síť na broadcastové domény | | A router splits the network into broadcast domains |
| Gateway připojuje LAN na odlišné prostředí | | A gateway connects a LAN to a different environment |
| Pojem brána někdy označuje router nebo LAN–WAN připojení | | Gateway sometimes means a router or LAN–WAN connection |
| MAC adresa Ethernetu má 48 bitů a je unikátní | | An Ethernet MAC address is 48 bits and unique |
| IEEE přidělí výrobci první 3 byty adresy | | IEEE assigns the manufacturer the first 3 address bytes |
| Ethernet II: EtherType > 1500 (IP = 0800h) | | Ethernet II: EtherType > 1500 (IP = 0800h) |
| IEEE 802.3: délka ≤ 1500 a rámec 802.2 | | IEEE 802.3: length ≤ 1500 and an 802.2 frame |
| Raw 802.3: Novell/IPX, začíná FFFFh | | Raw 802.3: Novell/IPX, starts with FFFFh |
| Max. Ethernet II = 1518 B | | Max Ethernet II frame = 1518 B |
| Min. rámec 64 B kvůli koliznímu okénku | | Min frame is 64 B due to the collision window |
| IP je nespojovaný a nespolehlivý | | IP is connectionless and unreliable |
| Routing = rozhodnutí o směru datagramu | | Routing = deciding the next direction of a datagram |
| Forwarding = vložení do rámce a odeslání | | Forwarding = placing into a frame and sending |
| HLEN 5 = 5 × 4 B = 20 B hlavičky | | HLEN 5 = 5 × 4 B = 20 B header |
| TTL snižují směrovače proti zacyklení | | Routers decrease TTL to prevent loops |
| PROTOCOL 6 = TCP | | PROTOCOL 6 = TCP |
| PROTOCOL 17 = UDP | | PROTOCOL 17 = UDP |
| Ethernet MTU = 1500 B | | Ethernet MTU = 1500 B |
| ICMP posílá informace o chybách a stavu přenosu | | ICMP sends information about errors and transfer status |
| ICMP jede uvnitř IP datagramu | | ICMP is carried inside an IP datagram |
| Source Quench = zpomal odesílání kvůli zahlcení | | Source Quench = slow down sending due to congestion |
| Time Exceeded = TTL kleslo na 0 | | Time Exceeded = TTL dropped to 0 |
| Echo Request/Reply = ping | | Echo Request/Reply = ping |
| Traceroute zvyšuje TTL a čte Time Exceeded | | Traceroute raises TTL and reads Time Exceeded |
| ARP: IP → MAC | | ARP: IP → MAC |
| RARP: MAC → IP | | RARP: MAC → IP |
| Nejčastější je dynamický dotaz a odpověď | | The most common way is a dynamic query and reply |
| ARP odpověď je unicast s MAC | | An ARP reply is a unicast with the MAC |
| ARP cache ukládá výsledky dočasně (arp -a) | | ARP cache stores results temporarily (arp -a) |
| TCP = spolehlivý a spojovaný | | TCP = reliable and connection-oriented |
| UDP = nespolehlivý, nespojovaný, malá režie | | UDP = unreliable, connectionless, low overhead |
| Porty rozlišují aplikace na uzlu | | Ports distinguish applications on a node |
| TCP se navazuje three-way handshake | | TCP is established with a three-way handshake |
| TCP hlavička 20 B, UDP 8 B | | TCP header is 20 B, UDP is 8 B |
| TCP čeká na znovudoručení – špatné pro real-time | | TCP waits for retransmission – bad for real-time |
| SCTP: multihoming a multistreaming | | SCTP: multihoming and multistreaming |
| Aplikace posílají data přes TCP/UDP | | Applications send data over TCP/UDP |
| Dva procesy nesmí sdílet stejný port | | Two processes must not share the same port |
| Well-known: 0–1023 (IANA) | | Well-known: 0–1023 (IANA) |
| Dynamické porty: 49152–65535 | | Dynamic ports: 49152–65535 |
| Spojení = pětice hodnot | | A connection is a 5-tuple |
| HTTP 80, HTTPS 443 | | HTTP 80, HTTPS 443 |
| SSH 22, DNS 53 | | SSH 22, DNS 53 |
| MySQL 3306, RDP 3389 | | MySQL 3306, RDP 3389 |
| Geostacionární družice ~36 000 km | | Geostationary satellites ~36,000 km |
| Družice: zpoždění stovky ms | | Satellites: hundreds of ms delay |
| VSAT: jen terminál ↔ hub | | VSAT: only terminal ↔ hub |
| Mikrovlny: přímá viditelnost ~50 km | | Microwaves: line of sight ~50 km |
| STA komunikují přes AP | | STAs communicate via an AP |
| Wi-Fi 6 (ax): OFDMA | | Wi-Fi 6 (ax): OFDMA |
| WEP prolomen – nepoužívat | | WEP is broken – do not use |
| Používej WPA2/WPA3 + AES | | Use WPA2/WPA3 + AES |
| tvůj | | yours (m.) |
| můj | | mine (m.) |
| Rodina ráda cestuje. Často je u nás na návštěvě v Česku. | | The family likes travelling. They often visit us in Czechia. |
| John von Neumann – americký matematik | | John von Neumann – American mathematician |
| Americký matematik s objevy v digitálních počítačích | | American mathematician with discoveries in digital computers |
| Řadič řídí činnost ostatních částí | | The control unit directs the other parts |
| Operační paměť uchovává program i data | | Main memory stores both the program and data |
| Byl tvůrcem teorie her | | He created game theory |
| Nultá generace: 1938–1944 | | Zeroth generation: 1938–1944 |
| Jen několik počítačů, většinou vojenský účel | | Only a few computers, mostly for military use |
| 1. generace – objev elektronky | | 1st generation – invention of the vacuum tube |
| 1. generace – děrné štítky | | 1st generation – punch cards |
| 2. generace – objev tranzistoru | | 2nd generation – invention of the transistor |
| 3. generace – integrovaný obvod | | 3rd generation – integrated circuit |
| 4. generace – programovatelné mikroprocesory | | 4th generation – programmable microprocessors |
| 1972 – první počítačová hra | | 1972 – first computer game |
| Bit = dvojková číslice, nejmenší jednotka informace | | A bit is a binary digit, the smallest unit of information |
| Bit nabývá hodnoty 0 nebo 1 | | A bit is either 0 or 1 |
| Byte = nejmenší adresovatelná jednotka paměti | | A byte is the smallest addressable unit of memory |

View file

@ -0,0 +1,3 @@
---
kviz_nadpis: Quiz: George Orwell – 1984
---

View file

@ -0,0 +1,286 @@
---
id: 1984
autor: George Orwell
nazev: 1984
zanr: Próza
preklad: Šimečková
stranka: cetba1984
plna: ano
kviz_nadpis: Kvíz: George Orwell – 1984
---
# O knize
- Autor: George Orwell (vl. jm. Eric Arthur Blair)
- Žánr: antiutopický (dystopický) román
- Poprvé vydáno: 1949
- Děj: Londýn, provincie Letopočet 1, stát Oceánie
- Vyprávění: v er-formě, po deníkových zápiscích Winstona Smithe
# Svět a strana
- Svět je rozdělen na tři supervelmoci: Oceánii, Eurasii a Východasii, které spolu vedou trvalou válku.
- Vládne jediná strana (Angsoc) v čele s kultem Velkého bratra.
- Ministerstvo pravdy – přepisuje historii a vyrábí „pravdu“.
- Ministerstvo lásky – mučírny a likvidace nepohodlných.
- Ministerstvo hojnosti – plánuje hospodářství a drží lidi v nedostatku.
- Ministerstvo míru – vede válku.
# Postavy
- Winston Smith – hlavní hrdina, přepisuje historii, touží po svobodě.
- Julie – mladá členka strany, Winstonova tajná láska.
- O'Brien – člen vnitřní strany, předstírá odboj a Winstona zničí.
- Emmanuel Goldstein – nepřítel státu, údajný vůdce Bratrstva.
- Velký bratr – symbol všemocné moci strany.
- Pan Charrington – zdánlivý obchodník, ve skutečnosti agent.
# Klíčové pojmy
- Newspeak – uměle zjednodušovaný jazyk, který omezuje myšlení.
- Dvojité myšlení – ochota věřit dvěma protikladným pravdám.
- Zločin myšlení – už samotná zakázaná myšlenka.
- Policie myšlení – tajná policie, která zatýká a mučí.
- Bratrstvo – možná neexistující odbojová organizace.
- Místnost 101 – nejhlubší mučírna, pro každého jeho největší strach.
# Kvíz
## Kdo je hlavní hrdina románu a co dělá?
- [x] Winston Smith – přepisuje historii na Ministerstvu pravdy
- O'Brien – řídí Ministerstvo lásky
- Emmanuel Goldstein – vede Bratrstvo
- Winston Smith – velí Policii myšlení
> Winston Smith je vnější člen strany, který na Ministerstvu pravdy upravuje staré záznamy a novinové články.
## Ve kterém fiktivním státě se děj odehrává?
- V Eurasii
- [x] V Oceánii
- Ve Východasii
- V Angsocu
> Příběh se odehrává v Oceánii, v provincii Letopočet 1 (bývalá Anglie), jejímž hlavním městem je Londýn.
## Co znamená slogan „VÁLKA JE MÍR, SVOBODA JE OTROCTVÍ, NEVĚDOMOST JE SÍLA“?
- Jsou to tři zásady Bratrstva
- Jsou to názvy ministerstev
- Jde o hesla prolety
- [x] Jsou to paradoxní hesla, kterými strana ospravedlňuje útlak
> Strana používá dvojité myšlení: hesla znějí vznešeně, ale ve skutečnosti obhajují válku, otroctví a nevědomost.
## Kdo je Velký bratr a je reálnou osobou?
- Skutečný vůdce Bratrstva
- Winstonův nadřízený na ministerstvu
- [x] Symbol všemocné moci a kultu osobnosti; možná vůbec neexistuje
- O'Brienův pseudonym
> Velký bratr je tvář režimu. Není jasné, zda jde o živou osobu, nebo jen o symbol, který drží strach a poslušnost.
## Co je Newspeak (novořeč) a proč ho strana zavádí?
- Tajná řeč Bratrstva
- [x] Zjednodušená angličtina omezující slovní zásobu, aby nešlo vyslovit zakázané myšlenky
- Úřední jazyk prolety
- Šifra Policie myšlení
> Newspeak neustále ubírá slova. Když pro svobodu nebo odpor neexistuje slovo, nelze o nich ani myslet.
## Co je teleobrazovka (telescreen) a jak funguje?
- Televize určená jen pro zábavu
- [x] Zařízení, které vysílá i sleduje; nelze ji úplně vypnout
- Rozhlasová stanice
- Počítač Ministerstva pravdy
> Telescreen funguje obousměrně – neustále sleduje občany. Vypnout lze jen zvuk, obraz běží dál.
## Jak se jmenují tři supervelmoci světa 1984?
- [x] Oceánie, Eurasie a Východasie
- Oceánie, Evropa a Asie
- Anglie, Eurasie a Ostasie
- Oceánie, Východasie a Afrika
> Svět je rozdělen na tři státy ve věčném, ale nerozhodném konfliktu: Oceánii, Eurasii a Východasii.
## Čím se zabývá Ministerstvo pravdy?
- Vede válku
- Dohlíží na morálku a sňatky
- [x] Upravuje a přepisuje historii, noviny a archivy
- Rozděluje potraviny
> Ministerstvo pravdy vyrábí „pravdu“ – Winston v něm přepisuje staré záznamy tak, aby vždy odpovídaly současné linii strany.
## K čemu slouží Ministerstvo lásky?
- Zajišťuje sňatky a lásku
- [x] Je to budova bez oken, kde se mučí a likvidují nepřátelé strany
- Vyrábí zbraně
- Školí děti
> Ministerstvo lásky je opakem svého názvu – sídlí tam mučírny a nachází se v něm i obávaná místnost 101.
## V čem je paradox Ministerstva hojnosti?
- [x] Má na starosti hospodářství, ale ve skutečnosti drží lidi v nedostatku
- Rozdává jídlo a zboží zdarma
- Zajišťuje dostatek pro všechny
- Starají se o něj prolety
> Ministerstvo hojnosti plánuje výrobu, ale ekonomika je záměrně nastavena tak, aby lidé žili v trvalém nedostatku.
## Co je Ministerstvo míru a čím se zabývá?
- Uzavírá mírové smlouvy
- Dojednává příměří
- [x] Ve skutečnosti vede válku
- Velí Policii myšlení
> Název je přesně opačný: Ministerstvo míru řídí válku, protože trvalý válečný stav pomáhá udržet moc strany.
## Kdo je Julie a jaký vztah s ní Winston naváže?
- Winstonova žena
- [x] Mladá členka strany, s níž Winston prožije tajnou milostnou aféru
- O'Brienova asistentka
- Proletka a obchodnice
> Julie pracuje v oddělení beletrie a naoko je vzornou členkou strany. S Winstonem vytvoří tajný milostný vztah.
## Proč je vztah Winstona a Julie zakázaný?
- [x] Strana zakazuje lásku a sexualitu jako zločin; vztah je navíc tajný
- Julie je vdaná za člena vnitřní strany
- Winston je jejím nadřízeným
- Julie patří k proletům
> Strana potlačuje citové a sexuální vazby, protože oddanost má patřit jen straně. Tajný vztah je proto dvojnásob nebezpečný.
## Kdo je O'Brien a jakou roli hraje v příběhu?
- Skutečný vůdce Bratrstva
- [x] Vysoce postavený člen vnitřní strany, který Winstona nakonec mučí a „vyléčí“
- Winstonův přítel z dětství
- Proletářský básník
> Winston věří, že O'Brien je odbojář. Ve skutečnosti je to věrný člen vnitřní strany, který ho dostane do místnosti 101.
## Co je Bratrstvo a existuje vůbec?
- Skutečná armáda Oceánie
- Náboženská organizace
- [x] Údajná tajná organizace odporu; není jisté, zda existuje
- Odbory prolety
> Bratrstvo zmiňuje Goldstein i O'Brien, ale kniha nikdy nepotvrdí, že doopravdy existuje – může jít o past strany.
## Kdo je Emmanuel Goldstein?
- Ministr pravdy
- [x] Oficiální nepřítel státu a údajný vůdce Bratrstva
- Winstonův kolega
- Velký bratrův dvojník
> Goldstein je terčem dvouminutovky nenávisti. Má ztělesňovat nepřítele, proti kterému strana sjednocuje občany.
## Co je dvojité myšlení (doublethink)?
- Schopnost myslet ve dvou jazycích
- Zdvojení osobnosti
- [x] Schopnost věřit dvěma protikladným tvrzením zároveň
- Tajné zapisování myšlenek
> Dvojité myšlení je jádro stranické ideologie – člověk přijme i zjevné rozpory, jako „válka je mír“.
## Proč strana neustále přepisuje minulost?
- Aby opravila chyby v archivech
- [x] Aby měla vždy pravdu a nepřítel jí nemohl dokázat opak
- Aby ušetřila papír
- Aby potěšila prolety
> Kdo ovládá minulost, ovládá i budoucnost. Přepisováním historie strana udržuje absolutní kontrolu nad pravdou.
## Co je zločin myšlení (thoughtcrime)?
- [x] Pouhá myšlenka nebo názor, který se nelíbí straně
- Krádež státního majetku
- Nelegální obchod
- Opomenutí dvouminutovky
> Strana trestá už samotné myšlenky. Winston se proviní tím, že si do deníku napíše „Dolů s Velkým bratrem“.
## Kdo je pan Charrington a jaké je jeho skutečné postavení?
- Winstonův přítel a majitel hospody
- Proletářský básník
- [x] Zdánlivě starý majitel obchodu, ve skutečnosti agent Policie myšlení
- Člen Bratrstva
> Charrington Winstonovi pronajme pokoj bez teleobrazovky, ale právě on nakonec oba milence udá.
## Co je místnost 101?
- Winstonova cela na policii
- Skladiště knih
- [x] Místnost, ve které každého čeká jeho největší strach
- Zasedací místnost vnitřní strany
> Místnost 101 je vrcholný mučicí nástroj. Winstonovi v ní hrozí krysy – jeho největší fobie.
## Jak skončí vztah Winstona a Julie?
- Utečou spolu do Eurasie
- Vezmou se a žijí svobodně
- Winston se stane členem Bratrstva
- [x] Zradí se a nakonec se přestanou milovat
> Oba jsou po mučení zlomení a vzájemně se zradí. Na konci už k sobě nic necítí.
## Co znamená závěrečná věta „Miloval Velkého bratra“?
- [x] Winstonův vzdor se změnil v pokornou lásku k režimu
- Winston se stal novým vůdcem
- Byl to jen Winstonův sen
- Julie mu to řekla
> Winston byl vnitřně zlomen. Přestal být sám sebou a nakonec režim opravdu přijal za svůj.
## Které tvrzení NENÍ jedním z hlavních témat románu?
- Moc a absolutní kontrola
- Pravda a přepisování historie
- Svoboda a identita
- [x] Idylický venkovský život
> 1984 je temná antiutopie. Hlavními tématy jsou moc, kontrola, manipulace s pravdou a ztráta svobody, ne venkovská idyla.
## V jakém roce byl román 1984 poprvé vydán?
- 1936
- [x] 1949
- 1954
- 1984
> Román vyšel v roce 1949; letopočet 1984 označuje blízkou budoucnost, do níž Orwell příběh zasadil.
## Které další Orwellovo dílo se dá s 1984 srovnat?
- [x] Farma zvířat
- Velký Gatsby
- Zločin a trest
- Babička
> Obě Orwellova díla varují před totalitou. Farma zvířat ji líčí alegoricky pomocí zvířat, 1984 očima jedince.
## Co je „dvouminutovka nenávisti“?
- [x] Krátká každodenní akce, při níž občané společně nenávidí Goldsteina a nepřítele
- Pravidelná modlitba ke Velkému bratrovi
- Sportovní přestávka ve škole
- Porada vedení ministerstva
> Slouží k uvolnění a přesměrování agrese občanů na nepřítele státu, čímž strana posiluje svou moc.
## Proč je podle knihy válka pro systém nezbytná?
- Přináší mír a blahobyt
- [x] Udržuje strach, spotřebovává výrobu a zachovává chudobu i hierarchii
- Rozvíjí vědu a techniku
- Sjednocuje vládu s prolety
> Trvalá válka pohlcuje nadbytečnou produkci a udržuje lidi ve strachu i chudobě, takže se nemohou vzbouřit.
## Jaký je rozdíl mezi vnějšími členy strany a prolety?
- Proleti mají více práv než členové strany
- [x] Vnější členové jsou dozíráni, kdežto proleti žijí v chudobě a strana o ně nestojí
- Není mezi nimi žádný rozdíl
- Proleti tvoří vedení strany
> Proleti tvoří asi 85 % obyvatel. Žijí v bídě a jsou považováni za neškodné, dokud si neuvědomí svou sílu.
## Jak strana kontroluje sexualitu a osobní vztahy?
- Podporuje volnou lásku a nezávislost
- [x] Potlačuje lásku a sexualitu, děti vede k oddanosti straně
- Zakazuje veškerá manželství
- Povoluje vztahy jen s prolety
> Strana chce, aby veškerá oddanost a energie patřily jí. Proto podporuje zdrženlivost a vede děti v Antisexuální lize mládeže.
## Co symbolizuje papírové těžítko, které si Winston koupí?
- Jeho nenávist ke Goldsteinovi
- [x] Kousek krásy a minulosti, kterou už nelze vrátit
- Moc Velkého bratra
- Lásku k Julii
> Těžítko je drobný, krásný předmět z minulosti. Pro Winstona ztělesňuje svět, který strana zničila.
## Jaké poselství chtěl Orwell románem nejspíš předat?
- [x] Varování před totalitou a ztrátou svobody i pravdy
- Oslavu technického pokroku
- Návod, jak založit stát
- Kritiku sportu a zábavy
> 1984 je varování: když lidé přestanou bránit pravdu a svobodu, mohou o ně nenávratně přijít.
# Děj
## Přetáhněte části příběhu do správného pořadí:
1. Winston upravuje historii na Ministerstvu pravdy
2. Seznámí se s Julií a začnou se tajně scházet
3. Pronajme si pokoj nad obchodem pana Charringtona
4. Přečte Goldsteinovu knihu u O'Briena
5. Jsou zatčeni Policií myšlení
6. Winston se zlomí v místnosti 101
> Správné pořadí: přepisování historie → seznámení s Julií → pronájem pokoje → četba Goldsteinovy knihy → zatčení → místnost 101.

View file

@ -0,0 +1,283 @@
---
id: fahrenheit451
autor: Ray Bradbury
nazev: 451 stupňů Fahrenheita
zanr: Próza
preklad: Nezval
---
# O knize
- Autor: Ray Bradbury
- Žánr: antiutopický (dystopický) sci-fi román
- Poprvé vydáno: 1953
- Děj/prostředí: anonymní americké město v blízké budoucnosti
- Vyprávění/forma: er-forma, lyricky obrazný styl; český překlad Vítězslava Nezvala
# Doba a svět
- Knihy jsou zakázané; hasiči pálí knihovny a domy, ne hasí požáry.
- Lidé tráví čas u televizních „stěn“ a rychlých, prázdných pořadů.
- Samostatné myšlení je podezřelé; společnost preferuje rychlou zábavu před hloubkou.
- V pozadí hrozí válka; město působí sterilně a agresivně.
- Odboj tvoří skrytí čtenáři a „pamatovači“, kteří uchovávají texty v paměti.
- Román varuje před cenzurou, konformitou a odpoutáním od literatury.
# Postavy
- Guy Montag – hasič, který přestane věřit systému pálení knih.
- Clarisse McClellan – zvědavá dívka, probudí v Montagovi otázky.
- Mildred Montagová – Guyova žena, závislá na televizi a prášcích.
- Kapitán Beatty – šéf hasičů, vzdělaný obhájce ničení knih.
- Profesor Faber – bývalý učitel, Montagův tajný rádce.
- Granger – vůdce komunity pamatovačů literatury.
# Klíčové pojmy a motivy
- 451 °F – symbol teploty hoření papíru a ničení knih.
- Televizní stěny – náhrada skutečného života a dialogu.
- Mechanický pes – technologie sledování a likvidace nepohodlných.
- Oheň – nejdřív ničení, později i obnova (fénix).
- Paměť – literatura přežívá v lidech, když zmizí knihy.
- Svoboda myšlení – hlavní téma odporu proti režimu.
# Kvíz
## Kdo je hlavní hrdina a jaké má povolání?
- [x] Guy Montag – hasič pálící knihy
- Beatty – spisovatel
- Faber – politik
- Granger – televizní režisér
> Montag pracuje v hasičské jednotce, která místo hašení požárů ničí knihy.
## Co znamená titulní „451 stupňů Fahrenheita“ v kontextu románu?
- Teplota varu vody
- [x] Údajná teplota hoření knižního papíru
- Rychlost hasičského vozu
- Kód tajné policie
> Bradbury používá číslo jako symbol ničení literatury ohněm.
## Kdo poprvé naruší Montagovu spokojenost s režimem?
- Mildred
- [x] Clarisse McClellan
- Beatty
- Granger
> Clarisse klade otázky o štěstí, přírodě a smyslu života.
## Jak žije Mildred Montagová?
- Jako odbojářka se skrytými knihami
- [x] Pasivně u televizních stěn a prášků
- Jako učitelka literatury
- Jako velitelka hasičů
> Mildred je otupělá masovou zábavou a nevnímá hlubší vztah k Guyovi.
## Proč podle Beattyho společnost knihy zakázala?
- Kvůli nedostatku papíru
- [x] Aby lidé nemysleli samostatně a byli spokojení
- Protože knihy jsou drahé
- Kvůli náboženským důvodům
> Beatty tvrdí, že knihy kazí „klid“ tím, že nutí přemýšlet a polemizovat.
## Kdo je profesor Faber?
- Velitel hasičů
- [x] Bývalý učitel, který Montagovi pomáhá
- Montagův švagr
- Autor zakázaných románů v tisku
> Faber Montaga vede k čtení a přes sluchátko ho podporuje v krizi.
## Co je mechanický pes v románu?
- Mildredin domácí mazlíček
- [x] Robotický lovec stíhající režimem označené osoby
- Hasičské auto
- Postava dětského pořadu
> Mechanický pes je nástroj strachu; později stíhá i Montaga.
## Co Montag udělá s kapitánem Beattym při zatýkání?
- Přesvědčí ho ke čtení
- [x] Spálí ho proudem plamenometu
- Uteče s ním z města
- Udá ho policii
> Po Beattyho provokacích Montag obrátí oheň proti svému nadřízenému.
## Kdo jsou „pamatovači“ na konci románu?
- Hasiči v důchodu
- [x] Lidé, kteří si pamatují knihy nazpaměť
- Televizní herci
- Vojáci v zákopu
> Skupina kolem Grangera uchovává literární texty v paměti jako most k budoucnosti.
## Jaký symbol nese oheň v průběhu příběhu?
- Pouze ničení
- [x] Nejdřív ničení, později i možnost obnovy (metafora fénixe)
- Jen romantická láska
- Výhradně válka
> Montag přechází od pálení knih k vnímání ohně jako zdroje tepla i nového začátku.
## Jak Mildred reaguje na Montagovo skryté čtení?
- Podporuje ho
- [x] Je vyděšená a raději knihy nechce vidět
- Okamžitě utíká k hasičům
- Začne psát vlastní deník
> Mildred se bojí následků a zůstává věrná konzumnímu životnímu stylu.
## Co se stane s Montagovou domácností?
- Stane se útočištěm odboje
- [x] Beatty nechá Montaga sám spálit jeho dům s knihami
- Mildred ji prodá
- Faber v ní otevře knihovnu
> Beatty nutí Montaga zničit vlastní domov jako veřejnou lekci poslušnosti.
## Proč Montag utíká k řece?
- Chce najít Clarisse
- [x] Uteče před mechanickým psem a pronásledováním
- Jde k Beattymu na večeři
- Hledá televizní studio
> Po zabití Beattyho musí opustit město a skrýt se před technologií i lidmi.
## Jakou roli hraje televize ve společnosti románu?
- Vzdělává občany
- [x] Nahrazuje knihy, dialog a kritické myšlení
- Slouží jen hasičům
- Vysílá výhradně zprávy o válce
> Interaktivní stěny a rychlé pořady udržují lidi pasivní a spokojené.
## Které téma román NEZdůrazňuje?
- Cenzura
- Nebezpečí konzumní zábavy
- [x] Idylický venkovský realism 19. století
- Odpor jednotlivce
> Jde o dystopii o knihách a médiích, ne o historický venkovský román.
## V jakém roce román poprvé vyšel?
- 1949
- [x] 1953
- 1968
- 1984
> 451 stupňů Fahrenheita vyšlo v roce 1953 a patří k klasice antiutopie.
## Jaký vztah měl Bradbury k televizi podle kontextu díla?
- Bezvýhradně ji oslavoval
- [x] Varoval před jejím návykovým vlivem na kulturu
- Ignoroval ji
- Považoval ji za náhradu školy
> Román kritizuje pasivní sledování obrazovek místo čtení a rozhovoru.
## Co Montag ukradne u staré ženy s knihami?
- Televizní ovladač
- [x] Kniha (biblický text)
- Plamenomet
- Mechanický pes
> Montag si nechá knihu, i když vidí, jak žena raději zemře v plamenech než knihy vydá.
## Proč je scéna se starou ženou důležitá?
- Ukazuje lehkost smrti
- [x] Montag chápe, že knihy mají pro lidi hodnotu života
- Beatty se stane hrdinou
- Mildred se obrátí
> Obětí ženy v plamenech Montag poprvé silně pocítí absurditu pálení knih.
## Jak Faber pomáhá Montagovi během pronásledování?
- Letí s ním vrtulníkem
- [x] Komunikuje přes miniaturní sluchátko v uchu
- Zastřelí mechanického psa
- Spálí Beattyho knihovnu
> Faber ho radí na dálku, dokud Montag neunikne z města.
## Co se stane s městem na konci?
- Slaví mír
- [x] Je zničeno leteckým útokem
- Stane se knihovnou
- Zmizí televize
> Válka na pozadí exploduje; město letecky zničí, zatímco Montag je mimo.
## Jak Granger vnímá budoucnost po zkáze?
- Bez naděje
- [x] Jako šanci znovu budovat kulturu z paměti
- Jako konec světa bez smyslu
- Jako vítězství hasičů
> Pamatovači přirovnávají obnovu k fénixovi – z popela může vyrůst nové.
## Která postava představuje konformitu nejvíc?
- Clarisse
- [x] Mildred
- Granger
- Faber
> Mildred je vzor občana spokojeného s prázdnou zábavou.
## Která postava ztělesňuje vzdělaný cynismus moci?
- Faber
- [x] Beatty
- Clarisse
- Granger
> Beatty cituje literaturu, ale obhajuje její ničení.
## Jaký žánr nejlépe vystihuje román?
- [x] Antiutopický sci-fi román
- Historický epos
- Detektivka
- Pastorální idyla
> Děj v budoucnosti kritizuje společnost a technologie – typická antiutopie.
## Co Montag dělá na začátku knihy profesně?
- Učí na gymnáziu
- [x] Pálí knihy jako hasič
- Píše pro televizi
- Vyrábí mechanické psy
> Úvodní scény ukazují rutinní pálení knih.
## Proč je Clarisse „nebezpečná“ pro systém?
- Je bohatá
- [x] Ptá se na zakázané otázky a myslí jinak
- Je hasička
- Píše knihy
> Její zvědavost a pomalost odporují konzumnímu tempu společnosti.
## Jaký význam má řeka v závěru?
- Hranice státu
- [x] Cesta úniku a symbol očištění
- Místo pálení knih
- Beattyho hrob
> Montag se v řece skrývá před psem a přechází k novému životu.
## Kdo v románu představuje most mezi starou kulturou a odbojem?
- Beatty
- [x] Faber
- Mildred
- Mechanický pes
> Faber zná knihy, ale dlouho se bál jednat – Montaga k akci povzbudí.
## Jaké poselství je pro maturitu nejdůležitější?
- Knihy lze nahradit televizí
- [x] Svoboda myšlení a kultura se brání i za cenu rizika
- Hasiči by měli pálit domy
- Válka je lepší než čtení
> Román obhajuje literaturu a kritické myšlení proti cenzuře a pasivitě.
## Jaký český překladatel je u díla tradičně uváděn v maturitních seznamech?
- Gössell
- [x] Nezval
- Skoumal
- Nevrla
> V katalogu maturitní četby je u díla uveden překlad Vítězslava Nezvala.
## Co následuje po Montagově připojení k pamatovačům?
- Návrat k Mildred
- [x] Pohled na zkázu města a naděje v obnovu
- Beattyho rehabilitace
- Zákaz paměti
> Závěr spojuje zkázu s možností nového začátku skrze paměť a knihy.
# Děj
## Přetáhněte části příběhu do správného pořadí:
1. Montag rutinně pálí knihy jako hasič
2. Clarisse ho přiměje pochybovat o systému
3. Montag schovává knihy a hledá radu u Fabera
4. Beatty nechá Montaga spálit vlastní dům
5. Montag zabije Beattyho a utíká z města
6. Připojí se k pamatovačům a vidí zkázu města
> Správné pořadí: rutinní pálení → Clarisse a pochybnosti → knihy a Faber → spálení domu → útěk a Beatty → pamatovači a zkáza města.

View file

@ -0,0 +1,282 @@
---
id: audience
autor: Václav Havel
nazev: Audience
zanr: Drama
---
# O knize
- Autor: Václav Havel
- Žánr: drama (jednoaktovka, absurdní / groteskní)
- Poprvé vydáno: 1975 (součást Vaněkovy trilogie)
- Děj/prostředí: kancelář vedoucího skladu pivovaru
- Vyprávění/forma: divadelní dialog, jedno dějství, jazyk moci vs. jazyk obyčejného člověka
# Doba a svět
- Dílo vzniklo v normalizačním období; metaforicky kritizuje totalitní společnost.
- Hrdina Vaněk je bývalý spisovatel přeřazený na manuální práci.
- Moc se projevuje v byrokracii, strachu a pokrytectví „nadřízených“.
- Rozhovor není o práci, ale o testu loajality a ideologické poslušnosti.
- Groteska: banální situace skrývá vážný tlak na svědomí jedince.
- Havel rozvíjí téma „slov odpovědnosti“ a falešné ideologické fráze.
# Postavy
- Ferdinand Vaněk – bývalý spisovatel, dělník ve skladu pivovaru.
- Vedoucí skladu (Šéf) – malý funkcionář toužící po kariéře, ideologicky nejistý.
- Vaněkova manželka (zmíněna) – představuje tlak rodiny a strach ze ztráty výdělku.
- Kolegové (nepřítomní) – symbol kolektivního dohledu.
- Šéf jako typ – přizpůsobivý člověk, který opakuje fráze moci.
- Vaněk jako typ disidenta – mlčí, ale nepodléhá.
# Klíčové pojmy a motivy
- Audience – „audience u vedoucího“ = výslech, nátlak, pokus o spolupráci.
- Hovory o pravdě – rozdíl mezi oficiální rétorikou a skutečným prožíváním.
- Strach a kariéra – vedoucí se bojí, ale chce postup.
- Práce vs. svědomí – Vaněk odmítá hrát ideologickou hru.
- Groteska – vážné téma v banálním prostředí skladu.
- Jazyk moci – prázdné fráze, které mají nahradit morální rozhodnutí.
# Kvíz
## Kdo je autorem hry Audience?
- Jaroslav Hašek
- [x] Václav Havel
- Karel Čapek
- Ladislav Smoljak
> Audience napsal Václav Havel jako součást Vaněkovy trilogie.
## Jaký je žánr díla?
- Epos
- [x] Jednoaktové drama
- Lyrická báseň
- Detektivní román
> Jde o jednoaktovou divadelní hru s dialogem dvou postav.
## Kde se děj odehrává?
- Na ministerstvu
- [x] V kanceláři vedoucího skladu pivovaru
- V divadle
- V exilu
> Celá hra probíhá v kanceláři vedoucího skladu.
## Kdo je Ferdinand Vaněk?
- Vedoucí skladu
- [x] Bývalý spisovatel přeřazený na manuální práci
- Ministr
- Hasič
> Vaněk je inteligent přeřazený do dělnické profese – alter ego Havla.
## Proč vedoucí Vaněka „předvolá“?
- Kvůli inventuře
- [x] Aby ho ideologicky „ převýchovali“ a získali informace
- Kvůli dovolené
- Kvůli pivu zdarma
> Rozhovor je nátlak na poslušnost a spolupráci s mocí.
## Jak vedoucí vnímá svou pozici?
- Jako mučedníka
- [x] Jako malého funkcionáře toužícího po kariéře
- Jako disidenta
- Jako umělce
> Vedoucí se přizpůsobuje systému a chce postup.
## Jak Vaněk reaguje na nátlak?
- Okamžitě udává
- [x] Mlčí, ironizuje, nepodléhá
- Odejde z práce
- Napadne vedoucího
> Vaněk odmítá hrát hru moci, i když riskuje problémy.
## Co je typické pro Havlovy hry s Vaněkem?
- Historický realismus 19. století
- [x] Groteskní situace a jazyk moci
- Pohádková fantasy
- Přírodní lyrika
> Havel ukazuje absurditu režimu skrze banální rozhovory.
## Kdy hra vznikla / poprvé vyšla?
- 1948
- [x] 1975
- 1989
- 2000
> Audience patří k Vaněkově trilogii z normalizačního období (1975).
## Jaké další hry tvoří Vaněkovu trilogii kromě Audience?
- Hamlet a Macbeth
- [x] Protest a Vyrozumění
- R.U.R. a Bílá nemoc
- Čekání na Godota a Endgame
> Trilogie: Audience, Protest, Vyrozumění.
## Proč je hra „absurdní“?
- Protože se dějí zázraky
- [x] Protože vážný nátlak probíhá v banální situaci skladu
- Protože je bez dialogu
- Protože je v rýmu
> Absurdita je v rozporu mezi tématem a prostředím.
## Co symbolizuje pivovar / sklad?
- Exkluzivní elita
- [x] Běžné pracovní prostředí, kde se testuje loajalita
- Církev
- Škola
> Moc se prosazuje i v obyčejné fabrice.
## Jak vedoucí mluví o „pravdě“?
- Jasně a otevřeně
- [x] Používá ideologické fráze a ospravedlňuje se
- V latině
- Vůbec nemluví
> Vedoucí opakuje fráze, ale neumí nést morální odpovědnost.
## Proč Vaněk nechce „spolupracovat“?
- Neví jak
- [x] Odmítá kompromis se lží a strachem
- Chce být vedoucím
- Je mu jedno
> Hrdina volí svědomí před kariérním nebo materiálním klidem.
## Jaký vztah mají oba muži k alkoholu v hře?
- Nepijí
- [x] Pivo je součástí prostředí a uvolnění napětí
- Pijí jen Vaněk
- Pivo zakazují
> Pivovar jako prostředí ovlivňuje tón rozhovoru.
## Co hrozí Vaněkovi, pokud neuspokojí vedoucího?
- Pouze pokuta
- [x] Další perzekuce, problémy v práci i rodině
- Okamžitá sláva
- Povýšení
> Normalizační režim trestal odmítnutí spolupráce.
## Které téma Havel v Audience NEřeší primárně?
- Morální odpovědnost
- Jazyk moci
- [x] Kosmické cestování
- Strach z autorit
> Hra je společenská groteska, ne sci-fi.
## Jak končí hra typicky v interpretaci?
- Vítězstvím vedoucího
- [x] Bez jasného „happy endu“ – napětí a Vaněkovo trvání na svém
- Svatbou
- Válka
> Zůstává konflikt mezi mocí a svědomím.
## Proč je Audience maturitní četbou?
- Kvůli pohádkovému ději
- [x] Ukazuje českou literaturu 20. století a disidentskou zkušenost
- Je nejkratší kniha
- Je bez témat
> Dílo reprezentuje Havla a období normalizace.
## Kdo je autorem termínu „slov odpovědnosti“ v kontextu Havla?
- Shakespeare
- [x] Václav Havel
- Orwell
- Kafka
> Havel zdůrazňoval, že slova mají morální váhu.
## Jaký je tón dialogu?
- Epický popis přírody
- [x] Napětí mezi frázemi moci a Vaněkovou ironií
- Pouze monolog
- Pouze pění
> Dialog odhaluje charakter obou postav.
## Vedoucí se bojí hlavně…
- Přízraků
- [x] Ztráty postavení a problémů s nadřízenými
- Vaněkova talentu na pivo
- Cizinců
> Jeho kariéra závisí na ideologické spolehlivosti.
## Vaněk v hře představuje…
- Typ spolupracovníka režimu
- [x] Typ člověka, který nezradí svědomí
- Krále
- Detektiva
> Vaněk je Havlovo alter ego disidenta.
## Kolik hlavních mluvících postav je v Audience?
- Pět
- [x] Dvě
- Deset
- Žádná
> Dialog vedoucího a Vaněka tvoří jádro hry.
## Jak souvisí Audience s normalizací?
- [x] Vznikla v období po roce 1968 jako metafora tlaku režimu
- Popisuje středověk
- Je z roku 1848
- Je americký western
> Hra reaguje na politickou situaci v Československu 70. let.
## Proč vedoucí opakuje fráze?
- Nemá slovní zásobu
- [x] Nahrazuje nimi vlastní rozhodnutí a odpovědnost
- Učí se latinu
- Je básník
> Ideologický jazyk má ulevit od morální volby.
## Co Vaněk odmítá udělat?
- Pracovat
- [x] Hrát roli informátora / ideologicky se „ přizpůsobit“
- Pít pivo
- Chodit do skladu
> Jde o odmítnutí spolupráce s mocí.
## Jaký literární směr je nejblíže Havlovu divadlu?
- Romantismus
- [x] Divadlo absurdní / groteskní
- Klasicismus
- Naturalismus venkova
> Havel navazuje na absurdní drama, ale s českým politickým kontextem.
## Co je „audience“ v anglickém významu slova?
- [x] Publikum / jednání s nadřízeným
- Hasičský sbor
- Pivní sud
- Soudní trest
> Název ironicky evokuje setkání, které je ve skutečnosti výslech.
## Která postava je víc „pro systém“?
- Vaněk
- [x] Vedoucí skladu
- Obě stejně
- Ani jedna
> Vedoucí se snaží vyhovět nadřízeným.
## Jaké poselství je pro maturitu klíčové?
- Poslušnost za každou cenu
- [x] Člověk může odmítnout lhát, i když za to platí
- Práce je vždy důležitější než pravda
- Fráze nahrazují etiku
> Hra obhajuje svědomí proti ideologickému nátlaku.
## V jakém žánru je Audience v katalogu maturitní četby?
- Poezie
- [x] Drama
- Próza
- Epos
> V katalogu je uvedeno jako drama.
# Děj
## Přetáhněte části příběhu do správného pořadí:
1. Vaněk pracuje ve skladu pivovaru jako dělník
2. Vedoucí ho předvolá na rozhovor v kanceláři
3. Vedoucí tlakem a frázemi testuje Vaněkovo přizpůsobení
4. Vaněk odmítá ideologickou spolupráci
5. Napětí mezi kariérou vedoucího a Vaněkovým svědomím roste
6. Hra končí bez smíření – moc a disident zůstávají v konfliktu
> Správné pořadí: Vaněk ve skladu → předvolání → nátlak vedoucího → Vaněkovo odmítnutí → eskalace napětí → nevyřešený konflikt.

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