Add physics lessons across four secondary-school years.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-06 10:55:27 +02:00
commit 524269edd7
32 changed files with 4152 additions and 13 deletions

View file

@ -69,6 +69,8 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
- **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov - **Český jazyk a literatura** — literatura (4 ročníky, 32 lekcí podle osnov
středních škol), mluvnice (20 cvičení) a maturitní četba středních škol), mluvnice (20 cvičení) a maturitní četba
(82 knih: zápisky, kvíz, sestavení děje) (82 knih: zápisky, kvíz, sestavení děje)
- **Fyzika** — 4 ročníky, 32 lekcí podle osnov středních škol
(mechanika, teplo a vlny, elektřina a magnetismus, optika až astrofyzika)
- **Základy přírodopisných věd** — chemie a biologie, v každé 8 lekcí - **Základy přírodopisných věd** — chemie a biologie, v každé 8 lekcí
(od atomu a buňky po biochemii a ekologii) s výkladem a kvízem (od atomu a buňky po biochemii a ekologii) s výkladem a kvízem
- **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství); - **Občanská nauka** — 1. ročník, 17 lekcí (člověk v lidském společenství);
@ -80,7 +82,7 @@ Intel Mac, jiná architektura nebo úpravy kódu → [sestavení ze zdroje](#ses
### Použití ### Použití
1. **Pokračuj** otevře mapu předmětů. 1. **Pokračuj** otevře mapu předmětů.
2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, baňka) vede na učební cestu. 2. Otevřený předmět (vlajka, Wi‑Fi, čip, česká vlajka, váhy, atom, baňka) vede na učební cestu.
3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají. 3. Uzly cvičení / lekcí otevřou obsah; zamčené nic nedělají.
4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře. 4. **Hledat** (lupa), **Statistiky** (graf) a **Nastavení** (ozubené kolo) jsou vpravo nahoře.
@ -268,6 +270,8 @@ Intel Mac, another architecture, or hacking on the code →
- **Czech language** — literature (4 years, 32 lessons from the secondary-school - **Czech language** — literature (4 years, 32 lessons from the secondary-school
syllabus), grammar (20 exercises) and required reading syllabus), grammar (20 exercises) and required reading
(82 books: notes, quiz, plot ordering) (82 books: notes, quiz, plot ordering)
- **Physics** — 4 years, 32 lessons from the secondary-school syllabus
(mechanics, heat and waves, electricity and magnetism, optics through astrophysics)
- **Natural sciences** — chemistry and biology, eight lessons each - **Natural sciences** — chemistry and biology, eight lessons each
(from the atom and the cell to biochemistry and ecology), with an overview and a quiz (from the atom and the cell to biochemistry and ecology), with an overview and a quiz
- **Civics** — year 1, 17 lessons (the individual and society); - **Civics** — year 1, 17 lessons (the individual and society);
@ -280,7 +284,7 @@ Intel Mac, another architecture, or hacking on the code →
### Usage ### Usage
1. **Continue** opens the subject map. 1. **Continue** opens the subject map.
2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, flask) opens its path. 2. An open subject (flag, Wi‑Fi, chip, Czech flag, scales, atom, flask) opens its path.
3. Exercise / lesson nodes open content; locked nodes do nothing. 3. Exercise / lesson nodes open content; locked nodes do nothing.
4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right. 4. **Search** (magnifier), **Statistics** (chart) and **Settings** (gear) sit in the top-right.

View file

@ -176,6 +176,26 @@ class AppViewModel(app: Application) : AndroidViewModel(app) {
refresh() refresh()
} }
fun markFyz(id: Int) {
progress.markFyz(id)
refresh()
}
fun markFyz2(id: Int) {
progress.markFyz2(id)
refresh()
}
fun markFyz3(id: Int) {
progress.markFyz3(id)
refresh()
}
fun markFyz4(id: Int) {
progress.markFyz4(id)
refresh()
}
fun markMluv(n: Int) { fun markMluv(n: Int) {
progress.markMluv(n) progress.markMluv(n)
refresh() refresh()

View file

@ -32,6 +32,8 @@ import org.maturita.maturita.ui.HomeScreen
import org.maturita.maturita.ui.HwQuizScreen import org.maturita.maturita.ui.HwQuizScreen
import org.maturita.maturita.ui.HwYearsScreen import org.maturita.maturita.ui.HwYearsScreen
import org.maturita.maturita.ui.LessonListScreen import org.maturita.maturita.ui.LessonListScreen
import org.maturita.maturita.ui.FyzQuizScreen
import org.maturita.maturita.ui.FyzYearsScreen
import org.maturita.maturita.ui.LitYearsScreen import org.maturita.maturita.ui.LitYearsScreen
import org.maturita.maturita.ui.LessonRow import org.maturita.maturita.ui.LessonRow
import org.maturita.maturita.ui.LitQuizScreen import org.maturita.maturita.ui.LitQuizScreen
@ -348,6 +350,63 @@ private fun MaturitaAppBody(vm: AppViewModel) {
} }
} }
is Route.Lit4Ex -> LitQuizScreen(vm, 4, r.id) is Route.Lit4Ex -> LitQuizScreen(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.SciMap -> SciMapScreen(vm)
Route.ChemMap -> LessonListScreen( Route.ChemMap -> LessonListScreen(
vm, vm.tr("Chemie"), vm.tr("chem_sub"), vm, vm.tr("Chemie"), vm.tr("chem_sub"),

View file

@ -50,6 +50,19 @@ sealed class Route(val page: String) {
data object BioMap : Route("biomap") data object BioMap : Route("biomap")
data class BioLesson(val id: Int) : Route("biounit$id") data class BioLesson(val id: Int) : Route("biounit$id")
data class BioEx(val id: Int) : Route("bioex$id") data class BioEx(val id: Int) : Route("bioex$id")
data object FyzYears : Route("fyzyears")
data object FyzMap : Route("fyzmap")
data class FyzLesson(val id: Int) : Route("fyzunit$id")
data class FyzEx(val id: Int) : Route("fyzex$id")
data object Fyz2Map : Route("fyz2map")
data class Fyz2Lesson(val id: Int) : Route("fyz2unit$id")
data class Fyz2Ex(val id: Int) : Route("fyz2ex$id")
data object Fyz3Map : Route("fyz3map")
data class Fyz3Lesson(val id: Int) : Route("fyz3unit$id")
data class Fyz3Ex(val id: Int) : Route("fyz3ex$id")
data object Fyz4Map : Route("fyz4map")
data class Fyz4Lesson(val id: Int) : Route("fyz4unit$id")
data class Fyz4Ex(val id: Int) : Route("fyz4ex$id")
data object CzechMap : Route("czechmap") data object CzechMap : Route("czechmap")
data object Mluvnice : Route("mluvnice") data object Mluvnice : Route("mluvnice")
data class MluvEx(val n: Int) : Route("mluve$n") data class MluvEx(val n: Int) : Route("mluve$n")
@ -68,7 +81,9 @@ sealed class Route(val page: String) {
this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson || this is NetLesson || this is HwLesson || this is OnLesson || this is On2Lesson || this is On3Lesson || this is On4Lesson ||
this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex || this is LitLesson || this is LitEx || this is Lit2Lesson || this is Lit2Ex ||
this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex || this is Lit3Lesson || this is Lit3Ex || this is Lit4Lesson || this is Lit4Ex ||
this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx this is ChemLesson || this is ChemEx || this is BioLesson || this is BioEx ||
this is FyzLesson || this is FyzEx || this is Fyz2Lesson || this is Fyz2Ex ||
this is Fyz3Lesson || this is Fyz3Ex || this is Fyz4Lesson || this is Fyz4Ex
/** Full stack from the practice root to this screen, so back always goes up one level. */ /** Full stack from the practice root to this screen, so back always goes up one level. */
fun stack(): List<Route> = when (this) { fun stack(): List<Route> = when (this) {
@ -120,6 +135,19 @@ sealed class Route(val page: String) {
BioMap -> listOf(Subjects, SciMap, BioMap) BioMap -> listOf(Subjects, SciMap, BioMap)
is BioLesson -> listOf(Subjects, SciMap, BioMap, this) is BioLesson -> listOf(Subjects, SciMap, BioMap, this)
is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this) is BioEx -> listOf(Subjects, SciMap, BioMap, BioLesson(id), this)
FyzYears -> listOf(Subjects, FyzYears)
FyzMap -> listOf(Subjects, FyzYears, FyzMap)
is FyzLesson -> listOf(Subjects, FyzYears, FyzMap, this)
is FyzEx -> listOf(Subjects, FyzYears, FyzMap, FyzLesson(id), this)
Fyz2Map -> listOf(Subjects, FyzYears, Fyz2Map)
is Fyz2Lesson -> listOf(Subjects, FyzYears, Fyz2Map, this)
is Fyz2Ex -> listOf(Subjects, FyzYears, Fyz2Map, Fyz2Lesson(id), this)
Fyz3Map -> listOf(Subjects, FyzYears, Fyz3Map)
is Fyz3Lesson -> listOf(Subjects, FyzYears, Fyz3Map, this)
is Fyz3Ex -> listOf(Subjects, FyzYears, Fyz3Map, Fyz3Lesson(id), this)
Fyz4Map -> listOf(Subjects, FyzYears, Fyz4Map)
is Fyz4Lesson -> listOf(Subjects, FyzYears, Fyz4Map, this)
is Fyz4Ex -> listOf(Subjects, FyzYears, Fyz4Map, Fyz4Lesson(id), this)
CzechMap -> listOf(Subjects, CzechMap) CzechMap -> listOf(Subjects, CzechMap)
Mluvnice -> listOf(Subjects, CzechMap, Mluvnice) Mluvnice -> listOf(Subjects, CzechMap, Mluvnice)
is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this) is MluvEx -> listOf(Subjects, CzechMap, Mluvnice, this)
@ -153,6 +181,11 @@ fun routeFromPage(page: String): Route? = when (page) {
"scimap" -> Route.SciMap "scimap" -> Route.SciMap
"chemmap" -> Route.ChemMap "chemmap" -> Route.ChemMap
"biomap" -> Route.BioMap "biomap" -> Route.BioMap
"fyzyears" -> Route.FyzYears
"fyzmap" -> Route.FyzMap
"fyz2map" -> Route.Fyz2Map
"fyz3map" -> Route.Fyz3Map
"fyz4map" -> Route.Fyz4Map
"czechmap" -> Route.CzechMap "czechmap" -> Route.CzechMap
"mluvnice" -> Route.Mluvnice "mluvnice" -> Route.Mluvnice
"readinglist" -> Route.ReadingList "readinglist" -> Route.ReadingList
@ -196,6 +229,30 @@ fun routeFromPage(page: String): Route? = when (page) {
Regex("""bioex(\d+)""").matchEntire(page)?.let { Regex("""bioex(\d+)""").matchEntire(page)?.let {
return Route.BioEx(it.groupValues[1].toInt()) return Route.BioEx(it.groupValues[1].toInt())
} }
Regex("""fyz4unit(\d+)""").matchEntire(page)?.let {
return Route.Fyz4Lesson(it.groupValues[1].toInt())
}
Regex("""fyz4ex(\d+)""").matchEntire(page)?.let {
return Route.Fyz4Ex(it.groupValues[1].toInt())
}
Regex("""fyz3unit(\d+)""").matchEntire(page)?.let {
return Route.Fyz3Lesson(it.groupValues[1].toInt())
}
Regex("""fyz3ex(\d+)""").matchEntire(page)?.let {
return Route.Fyz3Ex(it.groupValues[1].toInt())
}
Regex("""fyz2unit(\d+)""").matchEntire(page)?.let {
return Route.Fyz2Lesson(it.groupValues[1].toInt())
}
Regex("""fyz2ex(\d+)""").matchEntire(page)?.let {
return Route.Fyz2Ex(it.groupValues[1].toInt())
}
Regex("""fyzunit(\d+)""").matchEntire(page)?.let {
return Route.FyzLesson(it.groupValues[1].toInt())
}
Regex("""fyzex(\d+)""").matchEntire(page)?.let {
return Route.FyzEx(it.groupValues[1].toInt())
}
Regex("""lit4unit(\d+)""").matchEntire(page)?.let { Regex("""lit4unit(\d+)""").matchEntire(page)?.let {
return Route.Lit4Lesson(it.groupValues[1].toInt()) return Route.Lit4Lesson(it.groupValues[1].toInt())
} }

View file

@ -39,6 +39,10 @@ class Content(root: JSONObject) {
val lit4 = raw.arr("lit4") val lit4 = raw.arr("lit4")
val chem = raw.arr("chem") val chem = raw.arr("chem")
val bio = raw.arr("bio") val bio = raw.arr("bio")
val fyz = raw.arr("fyz")
val fyz2 = raw.arr("fyz2")
val fyz3 = raw.arr("fyz3")
val fyz4 = raw.arr("fyz4")
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")
@ -56,6 +60,10 @@ class Content(root: JSONObject) {
fun lit4Lesson(id: Int) = lit4.firstOrNull { it.int("id") == id } fun lit4Lesson(id: Int) = lit4.firstOrNull { it.int("id") == id }
fun chemLesson(id: Int) = chem.firstOrNull { it.int("id") == id } fun chemLesson(id: Int) = chem.firstOrNull { it.int("id") == id }
fun bioLesson(id: Int) = bio.firstOrNull { it.int("id") == id } fun bioLesson(id: Int) = bio.firstOrNull { it.int("id") == id }
fun fyzLesson(id: Int) = fyz.firstOrNull { it.int("id") == id }
fun fyz2Lesson(id: Int) = fyz2.firstOrNull { it.int("id") == id }
fun fyz3Lesson(id: Int) = fyz3.firstOrNull { it.int("id") == id }
fun fyz4Lesson(id: Int) = fyz4.firstOrNull { it.int("id") == id }
fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n } fun mluv(n: Int) = mluvnice.firstOrNull { it.int("id") == n }
fun book(id: String) = books.firstOrNull { it.str("id") == id } fun book(id: String) = books.firstOrNull { it.str("id") == id }

View file

@ -57,6 +57,14 @@ class ProgressStore(context: Context) {
fun markChem(id: Int) { prefs.edit().putBoolean("chem.$id", true).apply() } fun markChem(id: Int) { prefs.edit().putBoolean("chem.$id", true).apply() }
fun bioDone(id: Int) = prefs.getBoolean("bio.$id", false) fun bioDone(id: Int) = prefs.getBoolean("bio.$id", false)
fun markBio(id: Int) { prefs.edit().putBoolean("bio.$id", true).apply() } fun markBio(id: Int) { prefs.edit().putBoolean("bio.$id", true).apply() }
fun fyzDone(id: Int) = prefs.getBoolean("fyz.$id", false)
fun markFyz(id: Int) { prefs.edit().putBoolean("fyz.$id", true).apply() }
fun fyz2Done(id: Int) = prefs.getBoolean("fyz2.$id", false)
fun markFyz2(id: Int) { prefs.edit().putBoolean("fyz2.$id", true).apply() }
fun fyz3Done(id: Int) = prefs.getBoolean("fyz3.$id", false)
fun markFyz3(id: Int) { prefs.edit().putBoolean("fyz3.$id", true).apply() }
fun fyz4Done(id: Int) = prefs.getBoolean("fyz4.$id", false)
fun markFyz4(id: Int) { prefs.edit().putBoolean("fyz4.$id", true).apply() }
fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false) fun mluvDone(n: Int) = prefs.getBoolean("mluv.$n", false)
fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() } fun markMluv(n: Int) { prefs.edit().putBoolean("mluv.$n", true).apply() }
@ -176,6 +184,38 @@ fun summarize(content: Content, p: ProgressStore): ProgressSum {
sum.doneUnits += 1 sum.doneUnits += 1
} }
} }
content.fyz.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.fyzDone(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.fyz2.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.fyz2Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.fyz3.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.fyz3Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
content.fyz4.forEach { l ->
sum.totalEx += 1
sum.openUnits += 1
if (p.fyz4Done(l.int("id"))) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.size sum.totalEx += content.mluvnice.size
sum.openUnits += 1 sum.openUnits += 1
val md = content.mluvnice.count { p.mluvDone(it.int("id")) } val md = content.mluvnice.count { p.mluvDone(it.int("id")) }

View file

@ -863,6 +863,30 @@ fun SciQuizScreen(vm: AppViewModel, area: String, id: Int) {
} }
} }
@Composable
fun FyzQuizScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) {
2 -> vm.content.fyz2Lesson(id)
3 -> vm.content.fyz3Lesson(id)
4 -> vm.content.fyz4Lesson(id)
else -> vm.content.fyzLesson(id)
} ?: return
val spec = J(org.json.JSONObject().apply {
put("type", "choice")
put("title", vm.tr(lesson.str("exTitleKey")))
put("sub", lesson.str("quizHeadKey"))
put("questions", lesson.o.optJSONArray("quiz"))
})
DispatchExercise(vm, spec, vm.tr(lesson.str("exTitleKey"))) {
when (year) {
2 -> vm.markFyz2(id)
3 -> vm.markFyz3(id)
4 -> vm.markFyz4(id)
else -> vm.markFyz(id)
}
}
}
@Composable @Composable
fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) { fun LitQuizScreen(vm: AppViewModel, year: Int, id: Int) {
val lesson = when (year) { val lesson = when (year) {

View file

@ -25,6 +25,7 @@ import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.automirrored.filled.MenuBook import androidx.compose.material.icons.automirrored.filled.MenuBook
import androidx.compose.material.icons.filled.Bolt
import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.FormatListNumbered import androidx.compose.material.icons.filled.FormatListNumbered
@ -222,6 +223,7 @@ private fun SubjectIcon(icon: String, open: Boolean) {
"people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp)) "people" -> Icon(Icons.Filled.Groups, null, tint = tint, modifier = Modifier.size(26.dp))
"cz" -> CzechFlag(28.dp) "cz" -> CzechFlag(28.dp)
"flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp)) "flask" -> Icon(Icons.Filled.Science, null, tint = tint, modifier = Modifier.size(26.dp))
"bolt" -> Icon(Icons.Filled.Bolt, null, tint = tint, modifier = Modifier.size(26.dp))
else -> Icon(Icons.Filled.Lock, null, tint = tint) else -> Icon(Icons.Filled.Lock, null, tint = tint)
} }
} }
@ -314,6 +316,13 @@ private fun subjectSum(vm: AppViewModel, s: J): ProgressSum {
sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } + sum.doneEx = vm.content.chem.count { vm.progress.chemDone(it.int("id")) } +
vm.content.bio.count { vm.progress.bioDone(it.int("id")) } vm.content.bio.count { vm.progress.bioDone(it.int("id")) }
} }
"fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map" -> {
sum.totalEx = vm.content.fyz.size + vm.content.fyz2.size + vm.content.fyz3.size + vm.content.fyz4.size
sum.doneEx = vm.content.fyz.count { vm.progress.fyzDone(it.int("id")) } +
vm.content.fyz2.count { vm.progress.fyz2Done(it.int("id")) } +
vm.content.fyz3.count { vm.progress.fyz3Done(it.int("id")) } +
vm.content.fyz4.count { vm.progress.fyz4Done(it.int("id")) }
}
"czechmap" -> { "czechmap" -> {
val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } + val litDone = vm.content.lit.count { vm.progress.litDone(it.int("id")) } +
vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } + vm.content.lit2.count { vm.progress.lit2Done(it.int("id")) } +
@ -547,6 +556,27 @@ private fun buildSearch(vm: AppViewModel): List<Hit> {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit${l.int("id")}", extra = "literatura") add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit${l.int("id")}", extra = "literatura")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex${l.int("id")}")
} }
add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra = "fyzika")
add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra = "rocnik")
add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra = "rocnik")
add(vm.tr("fyz_year3"), vm.tr("search_lesson"), "fyz3map", extra = "rocnik")
add(vm.tr("fyz_year4"), vm.tr("search_lesson"), "fyz4map", extra = "rocnik")
vm.content.fyz.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyzunit${l.int("id")}", extra = "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyzex${l.int("id")}")
}
vm.content.fyz2.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz2unit${l.int("id")}", extra = "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz2ex${l.int("id")}")
}
vm.content.fyz3.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz3unit${l.int("id")}", extra = "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz3ex${l.int("id")}")
}
vm.content.fyz4.forEach { l ->
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz4unit${l.int("id")}", extra = "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz4ex${l.int("id")}")
}
add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra = "chemie") add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra = "chemie")
add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra = "biologie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra = "biologie")
vm.content.chem.forEach { l -> vm.content.chem.forEach { l ->
@ -683,6 +713,20 @@ fun LitYearsScreen(vm: AppViewModel) {
} }
} }
@Composable
fun FyzYearsScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Fyzika"), vm.tr("fyz_years_sub")) { inner ->
LazyColumn(contentPadding = PaddingValues(top = inner.calculateTopPadding(), bottom = inner.calculateBottomPadding() + 16.dp)) {
item {
NavRow(title = vm.tr("fyz_year1"), subtitle = vm.tr("fyz_sub"), leading = { Text("1.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.FyzMap) }
NavRow(title = vm.tr("fyz_year2"), subtitle = vm.tr("fyz2_sub"), leading = { Text("2.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz2Map) }
NavRow(title = vm.tr("fyz_year3"), subtitle = vm.tr("fyz3_sub"), leading = { Text("3.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz3Map) }
NavRow(title = vm.tr("fyz_year4"), subtitle = vm.tr("fyz4_sub"), leading = { Text("4.", style = MaterialTheme.typography.titleMedium) }) { vm.go(Route.Fyz4Map) }
}
}
}
}
@Composable @Composable
fun SciMapScreen(vm: AppViewModel) { fun SciMapScreen(vm: AppViewModel) {
DetailScaffold(vm, vm.tr("Základy Přírodopisných věd"), vm.tr("sci_sub")) { inner -> DetailScaffold(vm, vm.tr("Základy Přírodopisných věd"), vm.tr("sci_sub")) { inner ->

View file

@ -6,6 +6,12 @@
# cs: bullet # cs: bullet
# en: bullet # en: bullet
--- 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 --- 2026-10-06
cs: Základy přírodopisných věd: chemie a biologie, v každé 8 lekcí podle osnov 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 cs: Každá lekce má výklad a kvíz se čtyřmi otázkami

View file

@ -178,6 +178,26 @@ final class AppModel: ObservableObject {
refresh() refresh()
} }
func markFyz(_ id: Int) {
progress.markFyz(id)
refresh()
}
func markFyz2(_ id: Int) {
progress.markFyz2(id)
refresh()
}
func markFyz3(_ id: Int) {
progress.markFyz3(id)
refresh()
}
func markFyz4(_ id: Int) {
progress.markFyz4(id)
refresh()
}
func markMluv(_ n: Int) { func markMluv(_ n: Int) {
progress.markMluv(n) progress.markMluv(n)
refresh() refresh()

View file

@ -41,6 +41,10 @@ final class Content {
let lit4: [J] let lit4: [J]
let chem: [J] let chem: [J]
let bio: [J] let bio: [J]
let fyz: [J]
let fyz2: [J]
let fyz3: [J]
let fyz4: [J]
let mluvnice: [J] let mluvnice: [J]
let books: [J] let books: [J]
let changelog: [J] let changelog: [J]
@ -82,6 +86,10 @@ final class Content {
lit4 = raw.arr("lit4") lit4 = raw.arr("lit4")
chem = raw.arr("chem") chem = raw.arr("chem")
bio = raw.arr("bio") bio = raw.arr("bio")
fyz = raw.arr("fyz")
fyz2 = raw.arr("fyz2")
fyz3 = raw.arr("fyz3")
fyz4 = raw.arr("fyz4")
mluvnice = raw.arr("mluvnice") mluvnice = raw.arr("mluvnice")
books = raw.arr("books") books = raw.arr("books")
changelog = raw.arr("changelog") changelog = raw.arr("changelog")
@ -100,6 +108,10 @@ final class Content {
func lit4Lesson(_ id: Int) -> J? { lit4.first { $0.int("id") == id } } func lit4Lesson(_ id: Int) -> J? { lit4.first { $0.int("id") == id } }
func chemLesson(_ id: Int) -> J? { chem.first { $0.int("id") == id } } func chemLesson(_ id: Int) -> J? { chem.first { $0.int("id") == id } }
func bioLesson(_ id: Int) -> J? { bio.first { $0.int("id") == id } } func bioLesson(_ id: Int) -> J? { bio.first { $0.int("id") == id } }
func fyzLesson(_ id: Int) -> J? { fyz.first { $0.int("id") == id } }
func fyz2Lesson(_ id: Int) -> J? { fyz2.first { $0.int("id") == id } }
func fyz3Lesson(_ id: Int) -> J? { fyz3.first { $0.int("id") == id } }
func fyz4Lesson(_ id: Int) -> J? { fyz4.first { $0.int("id") == id } }
func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } } func mluv(_ n: Int) -> J? { mluvnice.first { $0.int("id") == n } }
func book(_ id: String) -> J? { books.first { $0.str("id") == id } } func book(_ id: String) -> J? { books.first { $0.str("id") == id } }

View file

@ -59,6 +59,14 @@ final class ProgressStore {
func markChem(_ id: Int) { defaults.set(true, forKey: "chem.\(id)") } func markChem(_ id: Int) { defaults.set(true, forKey: "chem.\(id)") }
func bioDone(_ id: Int) -> Bool { defaults.bool(forKey: "bio.\(id)") } func bioDone(_ id: Int) -> Bool { defaults.bool(forKey: "bio.\(id)") }
func markBio(_ id: Int) { defaults.set(true, forKey: "bio.\(id)") } func markBio(_ id: Int) { defaults.set(true, forKey: "bio.\(id)") }
func fyzDone(_ id: Int) -> Bool { defaults.bool(forKey: "fyz.\(id)") }
func markFyz(_ id: Int) { defaults.set(true, forKey: "fyz.\(id)") }
func fyz2Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz2.\(id)") }
func markFyz2(_ id: Int) { defaults.set(true, forKey: "fyz2.\(id)") }
func fyz3Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz3.\(id)") }
func markFyz3(_ id: Int) { defaults.set(true, forKey: "fyz3.\(id)") }
func fyz4Done(_ id: Int) -> Bool { defaults.bool(forKey: "fyz4.\(id)") }
func markFyz4(_ id: Int) { defaults.set(true, forKey: "fyz4.\(id)") }
func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") } func mluvDone(_ n: Int) -> Bool { defaults.bool(forKey: "mluv.\(n)") }
func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") } func markMluv(_ n: Int) { defaults.set(true, forKey: "mluv.\(n)") }
@ -183,6 +191,38 @@ func summarize(_ content: Content, _ p: ProgressStore) -> ProgressSum {
sum.doneUnits += 1 sum.doneUnits += 1
} }
} }
for l in content.fyz {
sum.totalEx += 1
sum.openUnits += 1
if p.fyzDone(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.fyz2 {
sum.totalEx += 1
sum.openUnits += 1
if p.fyz2Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.fyz3 {
sum.totalEx += 1
sum.openUnits += 1
if p.fyz3Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
for l in content.fyz4 {
sum.totalEx += 1
sum.openUnits += 1
if p.fyz4Done(l.int("id")) {
sum.doneEx += 1
sum.doneUnits += 1
}
}
sum.totalEx += content.mluvnice.count sum.totalEx += content.mluvnice.count
sum.openUnits += 1 sum.openUnits += 1
let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count let md = content.mluvnice.filter { p.mluvDone($0.int("id")) }.count

View file

@ -56,6 +56,19 @@ enum Route: Hashable {
case bioMap case bioMap
case bioLesson(Int) case bioLesson(Int)
case bioEx(Int) case bioEx(Int)
case fyzYears
case fyzMap
case fyzLesson(Int)
case fyzEx(Int)
case fyz2Map
case fyz2Lesson(Int)
case fyz2Ex(Int)
case fyz3Map
case fyz3Lesson(Int)
case fyz3Ex(Int)
case fyz4Map
case fyz4Lesson(Int)
case fyz4Ex(Int)
case czechMap case czechMap
case mluvnice case mluvnice
case mluvEx(Int) case mluvEx(Int)
@ -114,6 +127,19 @@ enum Route: Hashable {
case .bioMap: return "biomap" case .bioMap: return "biomap"
case .bioLesson(let id): return "biounit\(id)" case .bioLesson(let id): return "biounit\(id)"
case .bioEx(let id): return "bioex\(id)" case .bioEx(let id): return "bioex\(id)"
case .fyzYears: return "fyzyears"
case .fyzMap: return "fyzmap"
case .fyzLesson(let id): return "fyzunit\(id)"
case .fyzEx(let id): return "fyzex\(id)"
case .fyz2Map: return "fyz2map"
case .fyz2Lesson(let id): return "fyz2unit\(id)"
case .fyz2Ex(let id): return "fyz2ex\(id)"
case .fyz3Map: return "fyz3map"
case .fyz3Lesson(let id): return "fyz3unit\(id)"
case .fyz3Ex(let id): return "fyz3ex\(id)"
case .fyz4Map: return "fyz4map"
case .fyz4Lesson(let id): return "fyz4unit\(id)"
case .fyz4Ex(let id): return "fyz4ex\(id)"
case .czechMap: return "czechmap" case .czechMap: return "czechmap"
case .mluvnice: return "mluvnice" case .mluvnice: return "mluvnice"
case .mluvEx(let n): return "mluve\(n)" case .mluvEx(let n): return "mluve\(n)"
@ -228,6 +254,32 @@ enum Route: Hashable {
return [.sciMap, .bioMap, .bioLesson(id)] return [.sciMap, .bioMap, .bioLesson(id)]
case .bioEx(let id): case .bioEx(let id):
return [.sciMap, .bioMap, .bioLesson(id), .bioEx(id)] return [.sciMap, .bioMap, .bioLesson(id), .bioEx(id)]
case .fyzYears:
return [.fyzYears]
case .fyzMap:
return [.fyzYears, .fyzMap]
case .fyzLesson(let id):
return [.fyzYears, .fyzMap, .fyzLesson(id)]
case .fyzEx(let id):
return [.fyzYears, .fyzMap, .fyzLesson(id), .fyzEx(id)]
case .fyz2Map:
return [.fyzYears, .fyz2Map]
case .fyz2Lesson(let id):
return [.fyzYears, .fyz2Map, .fyz2Lesson(id)]
case .fyz2Ex(let id):
return [.fyzYears, .fyz2Map, .fyz2Lesson(id), .fyz2Ex(id)]
case .fyz3Map:
return [.fyzYears, .fyz3Map]
case .fyz3Lesson(let id):
return [.fyzYears, .fyz3Map, .fyz3Lesson(id)]
case .fyz3Ex(let id):
return [.fyzYears, .fyz3Map, .fyz3Lesson(id), .fyz3Ex(id)]
case .fyz4Map:
return [.fyzYears, .fyz4Map]
case .fyz4Lesson(let id):
return [.fyzYears, .fyz4Map, .fyz4Lesson(id)]
case .fyz4Ex(let id):
return [.fyzYears, .fyz4Map, .fyz4Lesson(id), .fyz4Ex(id)]
case .czechMap: case .czechMap:
return [.czechMap] return [.czechMap]
case .mluvnice: case .mluvnice:
@ -269,6 +321,11 @@ func routeFromPage(_ page: String) -> Route? {
case "scimap": return .sciMap case "scimap": return .sciMap
case "chemmap": return .chemMap case "chemmap": return .chemMap
case "biomap": return .bioMap case "biomap": return .bioMap
case "fyzyears": return .fyzYears
case "fyzmap": return .fyzMap
case "fyz2map": return .fyz2Map
case "fyz3map": return .fyz3Map
case "fyz4map": return .fyz4Map
case "czechmap": return .czechMap case "czechmap": return .czechMap
case "mluvnice": return .mluvnice case "mluvnice": return .mluvnice
case "readinglist": return .readingList case "readinglist": return .readingList
@ -324,6 +381,30 @@ func routeFromPage(_ page: String) -> Route? {
if let m = page.wholeMatch(of: /bioex(\d+)/), let n = Int(m.1) { if let m = page.wholeMatch(of: /bioex(\d+)/), let n = Int(m.1) {
return .bioEx(n) return .bioEx(n)
} }
if let m = page.wholeMatch(of: /fyz4unit(\d+)/), let n = Int(m.1) {
return .fyz4Lesson(n)
}
if let m = page.wholeMatch(of: /fyz4ex(\d+)/), let n = Int(m.1) {
return .fyz4Ex(n)
}
if let m = page.wholeMatch(of: /fyz3unit(\d+)/), let n = Int(m.1) {
return .fyz3Lesson(n)
}
if let m = page.wholeMatch(of: /fyz3ex(\d+)/), let n = Int(m.1) {
return .fyz3Ex(n)
}
if let m = page.wholeMatch(of: /fyz2unit(\d+)/), let n = Int(m.1) {
return .fyz2Lesson(n)
}
if let m = page.wholeMatch(of: /fyz2ex(\d+)/), let n = Int(m.1) {
return .fyz2Ex(n)
}
if let m = page.wholeMatch(of: /fyzunit(\d+)/), let n = Int(m.1) {
return .fyzLesson(n)
}
if let m = page.wholeMatch(of: /fyzex(\d+)/), let n = Int(m.1) {
return .fyzEx(n)
}
if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) { if let m = page.wholeMatch(of: /lit4unit(\d+)/), let n = Int(m.1) {
return .lit4Lesson(n) return .lit4Lesson(n)
} }

View file

@ -608,6 +608,7 @@ func subjectSymbol(_ icon: String) -> String {
case "cz": return "text.book.closed.fill" case "cz": return "text.book.closed.fill"
case "people": return "person.2.fill" case "people": return "person.2.fill"
case "flask": return "flask.fill" case "flask": return "flask.fill"
case "bolt": return "bolt.fill"
default: return "lock.fill" default: return "lock.fill"
} }
} }

View file

@ -646,6 +646,39 @@ struct SciQuizScreen: View {
} }
} }
struct FyzQuizScreen: View {
@ObservedObject var vm: AppModel
let year: Int
let id: Int
var body: some View {
let lesson: J? = {
switch year {
case 2: return vm.content.fyz2Lesson(id)
case 3: return vm.content.fyz3Lesson(id)
case 4: return vm.content.fyz4Lesson(id)
default: return vm.content.fyzLesson(id)
}
}()
if let lesson {
let spec = J([
"type": "choice",
"title": vm.tr(lesson.str("exTitleKey")),
"sub": lesson.str("quizHeadKey"),
"questions": lesson.rawArray("quiz"),
])
DispatchExercise(vm: vm, spec: spec, titleFallback: vm.tr(lesson.str("exTitleKey"))) {
switch year {
case 2: vm.markFyz2(id)
case 3: vm.markFyz3(id)
case 4: vm.markFyz4(id)
default: vm.markFyz(id)
}
}
}
}
}
struct LitYearQuizScreen: View { struct LitYearQuizScreen: View {
@ObservedObject var vm: AppModel @ObservedObject var vm: AppModel
let year: Int let year: Int

View file

@ -699,6 +699,12 @@ private func subjectSum(_ vm: AppModel, _ s: J) -> ProgressSum {
sum.totalEx = vm.content.chem.count + vm.content.bio.count sum.totalEx = vm.content.chem.count + vm.content.bio.count
sum.doneEx = vm.content.chem.filter { vm.progress.chemDone($0.int("id")) }.count sum.doneEx = vm.content.chem.filter { vm.progress.chemDone($0.int("id")) }.count
+ vm.content.bio.filter { vm.progress.bioDone($0.int("id")) }.count + vm.content.bio.filter { vm.progress.bioDone($0.int("id")) }.count
case "fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map":
sum.totalEx = vm.content.fyz.count + vm.content.fyz2.count + vm.content.fyz3.count + vm.content.fyz4.count
sum.doneEx = vm.content.fyz.filter { vm.progress.fyzDone($0.int("id")) }.count
+ vm.content.fyz2.filter { vm.progress.fyz2Done($0.int("id")) }.count
+ vm.content.fyz3.filter { vm.progress.fyz3Done($0.int("id")) }.count
+ vm.content.fyz4.filter { vm.progress.fyz4Done($0.int("id")) }.count
case "czechmap": case "czechmap":
let litCount = vm.content.lit.count + vm.content.lit2.count + vm.content.lit3.count + vm.content.lit4.count let litCount = vm.content.lit.count + vm.content.lit2.count + vm.content.lit3.count + vm.content.lit4.count
let litDone = vm.content.lit.filter { vm.progress.litDone($0.int("id")) }.count let litDone = vm.content.lit.filter { vm.progress.litDone($0.int("id")) }.count
@ -929,6 +935,27 @@ private func buildSearch(_ vm: AppModel) -> [Hit] {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit\(l.int("id"))", extra: "literatura") add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "lit4unit\(l.int("id"))", extra: "literatura")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(l.int("id"))") add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "lit4ex\(l.int("id"))")
} }
add(vm.tr("Fyzika"), vm.tr("search_lesson"), "fyzyears", extra: "fyzika")
add(vm.tr("fyz_year1"), vm.tr("search_lesson"), "fyzmap", extra: "rocnik")
add(vm.tr("fyz_year2"), vm.tr("search_lesson"), "fyz2map", extra: "rocnik")
add(vm.tr("fyz_year3"), vm.tr("search_lesson"), "fyz3map", extra: "rocnik")
add(vm.tr("fyz_year4"), vm.tr("search_lesson"), "fyz4map", extra: "rocnik")
for l in vm.content.fyz {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyzunit\(l.int("id"))", extra: "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyzex\(l.int("id"))")
}
for l in vm.content.fyz2 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz2unit\(l.int("id"))", extra: "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz2ex\(l.int("id"))")
}
for l in vm.content.fyz3 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz3unit\(l.int("id"))", extra: "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz3ex\(l.int("id"))")
}
for l in vm.content.fyz4 {
add(vm.tr(l.str("titleKey")), vm.tr("search_lesson"), "fyz4unit\(l.int("id"))", extra: "fyzika")
add(vm.tr(l.str("titleKey")), vm.tr("search_exercise"), "fyz4ex\(l.int("id"))")
}
add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra: "chemie") add(vm.tr("Chemie"), vm.tr("search_lesson"), "chemmap", extra: "chemie")
add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra: "biologie") add(vm.tr("Biologie"), vm.tr("search_lesson"), "biomap", extra: "biologie")
for l in vm.content.chem { for l in vm.content.chem {
@ -1485,6 +1512,47 @@ struct LitYearsScreen: View {
} }
} }
struct FyzYearsScreen: View {
@ObservedObject var vm: AppModel
var body: some View {
#if os(macOS)
let years: [(String, String, Route)] = [
("fyz_year1", "fyz_sub", .fyzMap),
("fyz_year2", "fyz2_sub", .fyz2Map),
("fyz_year3", "fyz3_sub", .fyz3Map),
("fyz_year4", "fyz4_sub", .fyz4Map),
]
MacFillGrid(count: years.count, minWidth: 260, minHeight: 180) { i in
NavigationLink(value: years[i].2) {
MacLinkCard(title: vm.tr(years[i].0), subtitle: vm.tr(years[i].1), badge: "\(i + 1).")
}
.buttonStyle(.plain)
}
.navigationTitle(vm.tr("Fyzika"))
.navigationSubtitle(vm.tr("fyz_years_sub"))
#else
List {
NavigationLink(value: Route.fyzMap) {
ListRowLabel(title: vm.tr("fyz_year1"), subtitle: vm.tr("fyz_sub"))
}
NavigationLink(value: Route.fyz2Map) {
ListRowLabel(title: vm.tr("fyz_year2"), subtitle: vm.tr("fyz2_sub"))
}
NavigationLink(value: Route.fyz3Map) {
ListRowLabel(title: vm.tr("fyz_year3"), subtitle: vm.tr("fyz3_sub"))
}
NavigationLink(value: Route.fyz4Map) {
ListRowLabel(title: vm.tr("fyz_year4"), subtitle: vm.tr("fyz4_sub"))
}
}
.navigationTitle(vm.tr("Fyzika"))
.navigationSubtitle(vm.tr("fyz_years_sub"))
.appListStyle()
#endif
}
}
struct SciMapScreen: View { struct SciMapScreen: View {
@ObservedObject var vm: AppModel @ObservedObject var vm: AppModel
@ -2253,6 +2321,92 @@ struct RouteDestination: View {
} }
case .bioEx(let id): case .bioEx(let id):
SciQuizScreen(vm: vm, area: "bio", id: id) SciQuizScreen(vm: vm, area: "bio", id: id)
case .fyzYears:
FyzYearsScreen(vm: vm)
case .fyzMap:
LessonListScreen(
vm: vm,
title: vm.tr("fyz_year1"),
subtitle: vm.tr("fyz_sub"),
rows: vm.content.fyz.map { l in
let id = l.int("id")
return LessonRow(
id: "f\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.fyzDone(id),
destination: .fyzLesson(id)
)
}
)
case .fyzLesson(let id):
if let l = vm.content.fyzLesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyzEx(id))
}
case .fyzEx(let id):
FyzQuizScreen(vm: vm, year: 1, id: id)
case .fyz2Map:
LessonListScreen(
vm: vm,
title: vm.tr("fyz_year2"),
subtitle: vm.tr("fyz2_sub"),
rows: vm.content.fyz2.map { l in
let id = l.int("id")
return LessonRow(
id: "f2\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.fyz2Done(id),
destination: .fyz2Lesson(id)
)
}
)
case .fyz2Lesson(let id):
if let l = vm.content.fyz2Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz2Ex(id))
}
case .fyz2Ex(let id):
FyzQuizScreen(vm: vm, year: 2, id: id)
case .fyz3Map:
LessonListScreen(
vm: vm,
title: vm.tr("fyz_year3"),
subtitle: vm.tr("fyz3_sub"),
rows: vm.content.fyz3.map { l in
let id = l.int("id")
return LessonRow(
id: "f3\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.fyz3Done(id),
destination: .fyz3Lesson(id)
)
}
)
case .fyz3Lesson(let id):
if let l = vm.content.fyz3Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz3Ex(id))
}
case .fyz3Ex(let id):
FyzQuizScreen(vm: vm, year: 3, id: id)
case .fyz4Map:
LessonListScreen(
vm: vm,
title: vm.tr("fyz_year4"),
subtitle: vm.tr("fyz4_sub"),
rows: vm.content.fyz4.map { l in
let id = l.int("id")
return LessonRow(
id: "f4\(id)",
title: vm.tr(l.str("titleKey")),
done: vm.progress.fyz4Done(id),
destination: .fyz4Lesson(id)
)
}
)
case .fyz4Lesson(let id):
if let l = vm.content.fyz4Lesson(id) {
SlidesScreen(vm: vm, title: vm.tr(l.str("titleKey")), subtitle: vm.tr(l.str("subKey")), slides: l.arr("slides"), next: .fyz4Ex(id))
}
case .fyz4Ex(let id):
FyzQuizScreen(vm: vm, year: 4, id: id)
case .mluvnice: case .mluvnice:
LessonListScreen( LessonListScreen(
vm: vm, vm: vm,

View file

@ -1111,6 +1111,10 @@ def main() -> int:
lit4_lessons = lit_pack("lit4") lit4_lessons = lit_pack("lit4")
chem_lessons = lit_pack("chem") chem_lessons = lit_pack("chem")
bio_lessons = lit_pack("bio") bio_lessons = lit_pack("bio")
fyz_lessons = lit_pack("fyz")
fyz2_lessons = lit_pack("fyz2")
fyz3_lessons = lit_pack("fyz3")
fyz4_lessons = lit_pack("fyz4")
on_lessons = [] on_lessons = []
for i in range(1, 18): for i in range(1, 18):
@ -1560,7 +1564,7 @@ def main() -> int:
{"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"}, {"key": "Občanská nauka", "open": True, "target": "onyears", "icon": "people"},
{"key": "English", "open": False, "target": None, "icon": "lock"}, {"key": "English", "open": False, "target": None, "icon": "lock"},
{"key": "Matematika", "open": False, "target": None, "icon": "lock"}, {"key": "Matematika", "open": False, "target": None, "icon": "lock"},
{"key": "Fyzika", "open": False, "target": None, "icon": "lock"}, {"key": "Fyzika", "open": True, "target": "fyzyears", "icon": "bolt"},
{"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"}, {"key": "Základy Přírodopisných věd", "open": True, "target": "scimap", "icon": "flask"},
{"key": "Technická grafika", "open": False, "target": None, "icon": "lock"}, {"key": "Technická grafika", "open": False, "target": None, "icon": "lock"},
{"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"}, {"key": "Prezentační grafika", "open": False, "target": None, "icon": "lock"},
@ -1585,6 +1589,10 @@ def main() -> int:
"lit4": lit4_lessons, "lit4": lit4_lessons,
"chem": chem_lessons, "chem": chem_lessons,
"bio": bio_lessons, "bio": bio_lessons,
"fyz": fyz_lessons,
"fyz2": fyz2_lessons,
"fyz3": fyz3_lessons,
"fyz4": fyz4_lessons,
"on2": on2_lessons, "on2": on2_lessons,
"on3": on3_lessons, "on3": on3_lessons,
"on4": on4_lessons, "on4": on4_lessons,
@ -1626,6 +1634,7 @@ def main() -> int:
print(f" lit lessons: {sum(1 for L in lit_lessons + lit2_lessons + lit3_lessons + lit4_lessons if L['slides'])}") print(f" lit lessons: {sum(1 for L in lit_lessons + lit2_lessons + lit3_lessons + lit4_lessons if L['slides'])}")
print(f" chem lessons: {sum(1 for L in chem_lessons if L['slides'])}") print(f" chem lessons: {sum(1 for L in chem_lessons if L['slides'])}")
print(f" bio lessons: {sum(1 for L in bio_lessons if L['slides'])}") print(f" bio lessons: {sum(1 for L in bio_lessons if L['slides'])}")
print(f" fyz lessons: {sum(1 for L in fyz_lessons + fyz2_lessons + fyz3_lessons + fyz4_lessons if L['slides'])}")
print(f" on2 lessons: {sum(1 for L in on2_lessons if L['slides'])}") print(f" on2 lessons: {sum(1 for L in on2_lessons if L['slides'])}")
print(f" on3 lessons: {sum(1 for L in on3_lessons if L['slides'])}") print(f" on3 lessons: {sum(1 for L in on3_lessons if L['slides'])}")
print(f" on4 lessons: {sum(1 for L in on4_lessons if L['slides'])}") print(f" on4 lessons: {sum(1 for L in on4_lessons if L['slides'])}")
@ -1686,6 +1695,8 @@ def main() -> int:
("lit", lit_lessons), ("lit2", lit2_lessons), ("lit", lit_lessons), ("lit2", lit2_lessons),
("lit3", lit3_lessons), ("lit4", lit4_lessons), ("lit3", lit3_lessons), ("lit4", lit4_lessons),
("chem", chem_lessons), ("bio", bio_lessons), ("chem", chem_lessons), ("bio", bio_lessons),
("fyz", fyz_lessons), ("fyz2", fyz2_lessons),
("fyz3", fyz3_lessons), ("fyz4", fyz4_lessons),
): ):
for L in block: for L in block:
if not L["slides"]: if not L["slides"]:

615
src/fyz_year1.c Normal file
View file

@ -0,0 +1,615 @@
#include "graduately.h"
/* Year 1: mechanics. Quantities, kinematics, dynamics, energy,
* gravitation, rigid bodies, fluids. */
NetLesson fyz_lessons[FYZ_N] = {
{ .n_slides = 3, .unit_page = "fyzunit1", .ex_page = "fyzex1" },
{ .n_slides = 3, .unit_page = "fyzunit2", .ex_page = "fyzex2" },
{ .n_slides = 3, .unit_page = "fyzunit3", .ex_page = "fyzex3" },
{ .n_slides = 3, .unit_page = "fyzunit4", .ex_page = "fyzex4" },
{ .n_slides = 3, .unit_page = "fyzunit5", .ex_page = "fyzex5" },
{ .n_slides = 3, .unit_page = "fyzunit6", .ex_page = "fyzex6" },
{ .n_slides = 3, .unit_page = "fyzunit7", .ex_page = "fyzex7" },
{ .n_slides = 3, .unit_page = "fyzunit8", .ex_page = "fyzex8" },
};
static GtkWidget *build_fyz_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Měření", "Veličina a jednotka",
"Tip: základní jednotka hmotnosti je kilogram, ne gram.",
{
"Fyzikální veličina má číselnou hodnotu a jednotku.",
"Soustava SI má sedm základních jednotek: m, kg, s, A, K, mol a cd.",
"Odvozené jednotky vznikají ze základních, třeba newton nebo joule.",
"Předpona kilo znamená tisíc, mili znamená tisícinu.",
NULL,
},
},
{
"2 / 3 • Druhy", "Skalár a vektor",
"Tip: rychlost jako vektor má velikost i směr.",
{
"Skalár má jen velikost. Patří sem hmotnost, čas nebo teplota.",
"Vektor má velikost i směr. Patří sem síla, rychlost a zrychlení.",
"Vektory se skládají podle rovnoběžníku.",
"Opačný vektor má stejnou velikost a opačný směr.",
NULL,
},
},
{
"3 / 3 • Přesnost", "Chyba měření",
"Tip: relativní chyba se často udává v procentech.",
{
"Každé měření má chybu. Opakování měření ji zmenšuje.",
"Absolutní chyba je rozdíl naměřené a správné hodnoty.",
"Relativní chyba je podíl absolutní chyby a správné hodnoty.",
"Výsledek se zaokrouhluje podle přesnosti měřidla.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[0], "fyz_unit1", "fyz_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Která jednotka je základní jednotka SI pro hmotnost?",
{"Gram", "Kilogram", "Newton", "Tuna"},
4, 1},
{"Co má vektor navíc proti skaláru?",
{"Jen jednotku", "Směr", "Jen číselnou hodnotu", "Předponu kilo"},
4, 1},
{"Co znamená předpona mili?",
{"Tisíc", "Milion", "Tisícinu", "Setinu"},
4, 2},
{"Co je relativní chyba?",
{"Rozdíl naměřené a správné hodnoty",
"Podíl absolutní chyby a správné hodnoty",
"Součet všech měření",
"Nejmenší dílek měřidla"},
4, 1},
};
static const char *hints[] = {
"Gram je tisícina kilogramu. Newton je jednotka síly.",
"Hmotnost je skalár. Rychlost a síla jsou vektory.",
"Kilo je 1000, centi je 0,01, mili je 0,001.",
"Absolutní chyba je rozdíl. Relativní je tento rozdíl dělený správnou hodnotou.",
};
return fyz_mcq_page(0, 0, "fyzunit1", "fyz_ex1_title", "fyz_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Pohyb", "Dráha a rychlost",
"Tip: při rovnoměrném pohybu je rychlost stálá.",
{
"Trajektorie je čára, po které se bod pohybuje. Dráha je její délka.",
"Průměrná rychlost je dráha dělená časem.",
"Rovnoměrný přímočarý pohyb má stálou rychlost a přímou trajektorii.",
"Dráha rovnoměrného pohybu je s = v·t.",
NULL,
},
},
{
"2 / 3 • Změna rychlosti", "Zrychlení",
"Tip: jednotka zrychlení je metr za sekundu na druhou.",
{
"Zrychlení popisuje, jak rychle se mění rychlost.",
"Rovnoměrně zrychlený pohyb má stálé zrychlení.",
"Rychlost je v = v₀ + a·t.",
"Dráha z klidu je s = ½·a·t².",
NULL,
},
},
{
"3 / 3 • Pád", "Volný pád",
"Tip: tíhové zrychlení u Země je přibližně 9,81 m/s².",
{
"Volný pád je pohyb jen pod vlivem tíhového zrychlení.",
"Tíhové zrychlení g je u povrchu Země asi 9,81 m/s².",
"Odpor vzduchu volný pád zpomaluje. Ve vakuu padají tělesa stejně.",
"Graf rychlosti na čase je u rovnoměrně zrychleného pohybu přímka.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[1], "fyz_unit2", "fyz_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá dráha rovnoměrného pohybu?",
{"s = v / t", "s = v·t", "s = a·t", "s = v·a"},
4, 1},
{"Jaká je jednotka zrychlení?",
{"m/s", "m/s²", "N", "J"},
4, 1},
{"Čemu se rovná rychlost rovnoměrně zrychleného pohybu?",
{"v = v₀ + a·t", "v = s·t", "v = a / t", "v = g·m"},
4, 0},
{"Jak velké je tíhové zrychlení u povrchu Země?",
{"Asi 1 m/s²", "Asi 9,81 m/s²", "Asi 100 m/s²", "Přesně rychlost světla"},
4, 1},
};
static const char *hints[] = {
"Rychlost je dráha za čas, proto dráha je rychlost krát čas.",
"Zrychlení je změna rychlosti za čas, tedy metr za sekundu za sekundu.",
"Počáteční rychlost se k přírůstku a·t přičítá.",
"Škola používá g ≈ 9,81 m/s², často i přibližně 10 m/s².",
};
return fyz_mcq_page(0, 1, "fyzunit2", "fyz_ex2_title", "fyz_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • První zákon", "Setrvačnost",
"Tip: zákon platí v inerciální soustavě.",
{
"Těleso zůstává v klidu nebo v rovnoměrném přímočarém pohybu, dokud na ně nepůsobí výsledná síla.",
"Setrvačnost je snaha tělesa udržet svůj pohybový stav.",
"Hmotnost je míra setrvačnosti.",
"Inerciální soustava se vůči jiné inerciální nepohybuje se zrychlením.",
NULL,
},
},
{
"2 / 3 • Druhý zákon", "Síla a zrychlení",
"Tip: newton je síla, která hmotnosti 1 kg udělí zrychlení 1 m/s².",
{
"Výsledná síla je F = m·a.",
"Větší síla při stejné hmotnosti znamená větší zrychlení.",
"Větší hmotnost při stejné síle znamená menší zrychlení.",
"Jednotka síly je newton, N.",
NULL,
},
},
{
"3 / 3 • Třetí zákon", "Akce a reakce",
"Tip: obě síly působí na dvě různá tělesa.",
{
"Síly, kterými na sebe dvě tělesa působí, jsou stejně velké a opačně orientované.",
"Akce a reakce vznikají i zanikají současně.",
"Navzájem se neruší, protože každá působí na jiné těleso.",
"Hybnost je p = m·v. V izolované soustavě se hybnost zachovává.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[2], "fyz_unit3", "fyz_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co říká první Newtonův zákon?",
{"F = m·a",
"Těleso nemění pohybový stav, dokud na ně nepůsobí výsledná síla",
"Akce a reakce se sčítají na jednom tělese",
"Hybnost se vždy ztrácí"},
4, 1},
{"Jak zní druhý Newtonův zákon?",
{"F = m / a", "F = m·a", "F = m·v", "F = a / m"},
4, 1},
{"Na co působí akce a reakce?",
{"Na jedno a totéž těleso a ruší se",
"Na dvě různá tělesa",
"Jen na těleso v klidu",
"Jen ve směru tíhy"},
4, 1},
{"Co je hybnost?",
{"m·a", "m·v", "F·t jen jako jednotka", "m·g·h"},
4, 1},
};
static const char *hints[] = {
"První zákon je zákon setrvačnosti. Druhý je F = m·a.",
"Newton je kg·m/s². Síla a zrychlení mají stejný směr.",
"Reakce není síla, která by na stejném tělese rušila akci.",
"Hybnost je vektor. Izolovaná soustava ji zachovává.",
};
return fyz_mcq_page(0, 2, "fyzunit3", "fyz_ex3_title", "fyz_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Tíha", "Tíhová a třecí síla",
"Tip: tíhová síla je G = m·g.",
{
"Tíhová síla působí svisle dolů a je G = m·g.",
"Třecí síla působí proti pohybu nebo proti snaze o pohyb.",
"Smykové tření závisí na normálové síle a na součiniteli tření.",
"Valivé tření je obvykle menší než smykové.",
NULL,
},
},
{
"2 / 3 • Tlak", "Síla na plochu",
"Tip: pascal je newton na metr čtvereční.",
{
"Tlak je p = F / S, kde S je obsah plochy.",
"Stejná síla na menší plochu znamená větší tlak.",
"Jednotka tlaku je pascal, Pa.",
"Ostrý hrot má malou plochu, proto velký tlak.",
NULL,
},
},
{
"3 / 3 • Skládání", "Výslednice sil",
"Tip: dvě stejně velké opačné síly dají nulu.",
{
"Síly stejného směru se sčítají.",
"Síly opačného směru se odečítají.",
"Kolmé síly se skládají podle Pythagorovy věty.",
"Rovnováha nastane, když je výslednice nulová.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[3], "fyz_unit4", "fyz_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá tíhová síla?",
{"G = m / g", "G = m·g", "G = m·v", "G = F / S"},
4, 1},
{"Na čem závisí smykové tření?",
{"Jen na barvě tělesa",
"Na normálové síle a součiniteli tření",
"Jen na objemu",
"Na rychlosti světla"},
4, 1},
{"Jak se počítá tlak?",
{"p = F·S", "p = F / S", "p = m·a", "p = S / F"},
4, 1},
{"Jaká je jednotka tlaku?",
{"Newton", "Pascal", "Joule", "Watt"},
4, 1},
};
static const char *hints[] = {
"g je tíhové zrychlení. Tíha má směr svisle dolů.",
"Ft = f·Fn. Drsnější styk má větší součinitel tření.",
"Menší plocha při stejné síle zvětší tlak.",
"1 Pa = 1 N/m². Atmosférický tlak je asi 101 kPa.",
};
return fyz_mcq_page(0, 3, "fyzunit4", "fyz_ex4_title", "fyz_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Práce", "Síla po dráze",
"Tip: síla kolmá k posunutí práci nekoná.",
{
"Mechanická práce je W = F·s·cos α.",
"α je úhel mezi silou a posunutím.",
"Když je síla kolmá k posunutí, práce je nulová.",
"Jednotka práce je joule, J.",
NULL,
},
},
{
"2 / 3 • Energie", "Pohybová a polohová",
"Tip: polohová energie roste s výškou.",
{
"Kinetická energie je Ek = ½·m·v².",
"Polohová energie v tíhovém poli je Ep = m·g·h.",
"Výšku h počítáme od zvolené hladiny.",
"Bez tření se součet kinetické a polohové energie zachovává.",
NULL,
},
},
{
"3 / 3 • Výkon", "Práce za čas",
"Tip: watt je joule za sekundu.",
{
"Výkon je P = W / t.",
"Jednotka výkonu je watt, W.",
"Účinnost je podíl užitečné a dodané energie.",
"Účinnost je vždy menší než 1, protože část energie se ztrácí.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[4], "fyz_unit5", "fyz_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdy je mechanická práce nulová?",
{"Když je síla rovnoběžná s posunutím",
"Když je síla kolmá k posunutí",
"Když je hmotnost velká",
"Když je výkon 1 W"},
4, 1},
{"Jak se počítá kinetická energie?",
{"Ek = m·g·h", "Ek = ½·m·v²", "Ek = F / s", "Ek = m·v"},
4, 1},
{"Jak se počítá polohová energie v tíhovém poli?",
{"Ep = ½·m·v²", "Ep = m·g·h", "Ep = F·a", "Ep = p·V"},
4, 1},
{"Co je výkon?",
{"Práce dělená časem", "Síla dělená plochou",
"Hmotnost krát zrychlení", "Hybnost za dráhu"},
4, 0},
};
static const char *hints[] = {
"cos 90° je 0, proto kolmá síla práci nekoná.",
"Dvojnásobná rychlost znamená čtyřnásobnou kinetickou energii.",
"Hladinu nulové polohové energie si volíme.",
"Watt je J/s. Účinnost je podíl energií, ne jejich součet.",
};
return fyz_mcq_page(0, 4, "fyzunit5", "fyz_ex5_title", "fyz_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Zákon", "Newtonova gravitace",
"Tip: gravitační síla klesá s druhou mocninou vzdálenosti.",
{
"Dvě tělesa se přitahují silou Fg = κ·m₁·m₂ / r².",
"κ je gravitační konstanta, asi 6,67·10⁻¹¹ N·m²/kg².",
"r je vzdálenost středů těles.",
"Gravitace působí na dálku a je vždy přitažlivá.",
NULL,
},
},
{
"2 / 3 • U Země", "Tíhové zrychlení",
"Tip: první kosmická rychlost je asi 7,9 km/s.",
{
"U povrchu Země je tíhové zrychlení přibližně 9,81 m/s².",
"Tíhová síla je výslednice gravitační síly a setrvačné síly od otáčení Země.",
"První kosmická rychlost je rychlost kruhové dráhy těsně u povrchu, asi 7,9 km/s.",
"Druhá kosmická rychlost, asi 11,2 km/s, stačí k opuštění Země.",
NULL,
},
},
{
"3 / 3 • Pohyby", "Keplerovy zákony",
"Tip: planety obíhají po elipsách, Slunce je v ohnisku.",
{
"Planety se pohybují po elipsách a Slunce je v jednom ohnisku.",
"Průvodič planety opíše za stejný čas stejnou plochu.",
"Poměr krychle velké poloosy a čtverce oběžné doby je pro planety stejný.",
"Družice padá kolem Země, proto zůstává na oběžné dráze.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[5], "fyz_unit6", "fyz_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak závisí gravitační síla na vzdálenosti?",
{"Je přímo úměrná vzdálenosti",
"Je nepřímo úměrná druhé mocnině vzdálenosti",
"Na vzdálenosti nezávisí",
"Je úměrná třetí mocnině vzdálenosti"},
4, 1},
{"Jaká je přibližně gravitační konstanta κ?",
{"9,81", "6,67·10⁻¹¹ N·m²/kg²", "3·10⁸ m/s", "101 kPa"},
4, 1},
{"Co je první kosmická rychlost?",
{"Rychlost světla",
"Rychlost kruhové dráhy těsně u Země, asi 7,9 km/s",
"Rychlost zvuku",
"Rychlost volného pádu z 1 m"},
4, 1},
{"Jaký tvar mají dráhy planet podle Keplera?",
{"Kružnice se Sluncem ve středu vždy",
"Elipsy a Slunce je v ohnisku",
"Přímky",
"Paraboly se Zemí v ohnisku"},
4, 1},
};
static const char *hints[] = {
"Dvojnásobná vzdálenost znamená čtvrtinovou sílu.",
"κ je velmi malé číslo. g = 9,81 m/s² je zrychlení, ne konstanta κ.",
"Druhá kosmická rychlost je asi 11,2 km/s.",
"První Keplerův zákon mluví o elipse a ohnisku.",
};
return fyz_mcq_page(0, 5, "fyzunit6", "fyz_ex6_title", "fyz_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Otáčení", "Moment síly",
"Tip: moment je větší, když síla zabírá dál od osy.",
{
"Tuhé těleso nemění působením sil svůj tvar.",
"Moment síly je M = F·d, kde d je rameno síly.",
"Rameno je kolmá vzdálenost osy od přímky síly.",
"Jednotka momentu je newton metr, N·m.",
NULL,
},
},
{
"2 / 3 • Páka", "Rovnováha",
"Tip: na páce v rovnováze jsou momenty na obou stranách stejné.",
{
"Těleso je v rovnováze, když je výslednice sil i výsledný moment nulový.",
"Na dvojzvratné páce platí F₁·d₁ = F₂·d₂.",
"Páka, kladka a nakloněná rovina jsou jednoduché stroje.",
"Jednoduchý stroj práci neušetří, jen změní velikost síly nebo dráhu.",
NULL,
},
},
{
"3 / 3 • Těžiště", "Stabilita",
"Tip: těleso je stabilní, když se po malém vychýlení vrací.",
{
"Těžiště je působiště tíhové síly.",
"U stejnorodého pravidelného tělesa je těžiště ve středu.",
"Stabilní poloha se po vychýlení vrací, labilní se převrací.",
"Nízké těžiště a široká základna zvětšují stabilitu.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[6], "fyz_unit7", "fyz_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá moment síly?",
{"M = F / d", "M = F·d", "M = m·g·h", "M = p·V"},
4, 1},
{"Kdy je páka v rovnováze?",
{"Když jsou síly vždy stejně velké bez ohledu na rameno",
"Když se momenty na obou stranách rovnají",
"Když je jedno rameno nulové",
"Když páka koná práci zdarma"},
4, 1},
{"Co je těžiště?",
{"Místo největšího tlaku kapaliny",
"Působiště tíhové síly",
"Ohnisko elipsy",
"Střed elektrického náboje"},
4, 1},
{"Která poloha je stabilní?",
{"Po malém vychýlení se těleso vrací",
"Po malém vychýlení se těleso převrátí",
"Těleso zůstane v nové poloze vždy",
"Těžiště je nad bodem dotyku co nejvýš a základna je bod"},
4, 0},
};
static const char *hints[] = {
"Delší rameno znamená větší moment při stejné síle.",
"F₁·d₁ = F₂·d₂. Menší silou na delším rameni zvedneme větší náklad.",
"Těleso se chová, jako by v těžišti působila celá tíha.",
"Labilní poloha se po vychýlení vzdaluje. Volná zůstane, kde je.",
};
return fyz_mcq_page(0, 6, "fyzunit7", "fyz_ex7_title", "fyz_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_fyz_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Tlak", "Hydrostatický tlak",
"Tip: tlak v kapalině roste s hloubkou.",
{
"Tlak v kapalině způsobený tíhou je p = h·ρ·g.",
"h je hloubka, ρ je hustota kapaliny.",
"V téže hloubce je tlak ve všech směrech stejný.",
"Pascalův zákon: tlak v uzavřené kapalině se šíří všemi směry stejně.",
NULL,
},
},
{
"2 / 3 • Vztlak", "Archimédův zákon",
"Tip: těleso plave, když se jeho průměrná hustota rovná hustotě kapaliny nebo je menší.",
{
"Na těleso v tekutině působí vztlaková síla svisle vzhůru.",
"Vztlak se rovná tíze tekutiny tělesem vytlačené.",
"Těleso se potápí, když je jeho hustota větší než hustota tekutiny.",
"Těleso se vznáší, když jsou hustoty stejné.",
NULL,
},
},
{
"3 / 3 • Plyny", "Atmosféra a proudění",
"Tip: normální atmosférický tlak je asi 101,3 kPa.",
{
"Atmosférický tlak u hladiny moře je přibližně 101,3 kPa.",
"S výškou atmosférický tlak klesá.",
"V užší části trubice proudí kapalina rychleji a tlak tam klesá.",
"Na tom stojí vztlak křídla i rozprašovač.",
NULL,
},
},
};
return fyz_unit_page(0, &fyz_lessons[7], "fyz_unit8", "fyz_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá hydrostatický tlak?",
{"p = h / ρ", "p = h·ρ·g", "p = F·S", "p = m·v"},
4, 1},
{"Co říká Archimédův zákon?",
{"Vztlak se rovná tíze vytlačené tekutiny",
"Tlak nezávisí na hloubce",
"Kapaliny se nedají stlačit vůbec a nemají vztlak",
"Vztlak působí dolů"},
4, 0},
{"Kdy se těleso v kapalině potápí?",
{"Když má menší hustotu než kapalina",
"Když má větší hustotu než kapalina",
"Když má stejnou hustotu",
"Když je hladina v klidu"},
4, 1},
{"Jak velký je přibližně normální atmosférický tlak?",
{"1 Pa", "101 kPa", "9,81 Pa", "6,67·10⁻¹¹ Pa"},
4, 1},
};
static const char *hints[] = {
"Hustší kapalina a větší hloubka znamenají větší tlak.",
"Vytlačená tekutina má objem ponořené části tělesa.",
"Vznášení je při stejné hustotě. Plování je při menší průměrné hustotě.",
"1013 hPa je totéž co asi 101,3 kPa.",
};
return fyz_mcq_page(0, 7, "fyzunit8", "fyz_ex8_title", "fyz_quiz8_head",
qs, hints, 4);
}
void add_fyz_pages(GtkStack *stack) {
typedef GtkWidget *(*FyzBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
FyzBuilder build_unit;
FyzBuilder build_ex;
} pages[] = {
{"fyzunit1", "fyzex1", build_fyz_unit1_page, build_fyz_unit1_exercise_page},
{"fyzunit2", "fyzex2", build_fyz_unit2_page, build_fyz_unit2_exercise_page},
{"fyzunit3", "fyzex3", build_fyz_unit3_page, build_fyz_unit3_exercise_page},
{"fyzunit4", "fyzex4", build_fyz_unit4_page, build_fyz_unit4_exercise_page},
{"fyzunit5", "fyzex5", build_fyz_unit5_page, build_fyz_unit5_exercise_page},
{"fyzunit6", "fyzex6", build_fyz_unit6_page, build_fyz_unit6_exercise_page},
{"fyzunit7", "fyzex7", build_fyz_unit7_page, build_fyz_unit7_exercise_page},
{"fyzunit8", "fyzex8", build_fyz_unit8_page, build_fyz_unit8_exercise_page},
};
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
}
}

621
src/fyz_year2.c Normal file
View file

@ -0,0 +1,621 @@
#include "graduately.h"
/* Year 2: kinetic theory, heat, the structure of matter, changes of
* state, mechanical oscillation and waves, sound. */
NetLesson fyz2_lessons[FYZ_N] = {
{ .n_slides = 3, .unit_page = "fyz2unit1", .ex_page = "fyz2ex1" },
{ .n_slides = 3, .unit_page = "fyz2unit2", .ex_page = "fyz2ex2" },
{ .n_slides = 3, .unit_page = "fyz2unit3", .ex_page = "fyz2ex3" },
{ .n_slides = 3, .unit_page = "fyz2unit4", .ex_page = "fyz2ex4" },
{ .n_slides = 3, .unit_page = "fyz2unit5", .ex_page = "fyz2ex5" },
{ .n_slides = 3, .unit_page = "fyz2unit6", .ex_page = "fyz2ex6" },
{ .n_slides = 3, .unit_page = "fyz2unit7", .ex_page = "fyz2ex7" },
{ .n_slides = 3, .unit_page = "fyz2unit8", .ex_page = "fyz2ex8" },
};
static GtkWidget *build_fyz2_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Částice", "Z čeho je látka",
"Tip: částice se neustále neuspořádaně pohybují.",
{
"Látky se skládají z atomů, molekul nebo iontů.",
"Částice se neuspořádaně pohybují. Tomu říkáme tepelný pohyb.",
"Mezi částicemi působí přitažlivé i odpudivé síly.",
"Brownův pohyb a difuze jsou důkazy pohybu částic.",
NULL,
},
},
{
"2 / 3 • Skupenství", "Pevné, kapalné, plynné",
"Tip: v plynu jsou částice daleko od sebe a nevyplňují stálý tvar.",
{
"V pevné látce částice kmitají kolem rovnovážných poloh.",
"V kapalině jsou částice blízko, ale mohou se přesouvat.",
"V plynu se částice pohybují volně a narážejí do stěn.",
"Plazma je ionizovaný plyn a bývá čtvrté skupenství.",
NULL,
},
},
{
"3 / 3 • Energie", "Vnitřní energie",
"Tip: vnitřní energie roste s teplotou.",
{
"Vnitřní energie je součet kinetické a potenciální energie částic.",
"Teplo je energie předaná díky rozdílu teplot.",
"Práce i teplo mohou vnitřní energii změnit.",
"Teplota popisuje stav, teplo popisuje děj.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[0], "fyz2_unit1", "fyz2_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co dokazuje Brownův pohyb?",
{"Že částice stojí", "Že se částice neustále pohybují",
"Že světlo je vlna", "Že tlak nezávisí na teplotě"},
4, 1},
{"Jak se částice chovají v pevné látce?",
{"Volně létají nádobou",
"Kmitají kolem rovnovážných poloh",
"Nemají žádnou energii",
"Jsou od sebe jako v řídkém plynu"},
4, 1},
{"Co je teplo?",
{"Stavová veličina stejná jako teplota",
"Energie předaná díky rozdílu teplot",
"Hmotnost částic",
"Tlak nasycené páry"},
4, 1},
{"Co je vnitřní energie?",
{"Jen polohová energie celého tělesa m·g·h",
"Součet kinetické a potenciální energie částic",
"Práce tíhové síly",
"Výkon ohřívače"},
4, 1},
};
static const char *hints[] = {
"Pylová zrnka ve vodě poskakují, protože do nich narážejí molekuly.",
"Kapalina teče, plyn vyplní nádobu. Pevná látka drží tvar.",
"Teplota je stav. Teplo je přenos energie.",
"Zahřátí zvýší kinetickou energii částic, a tím vnitřní energii.",
};
return fyz_mcq_page(1, 0, "fyz2unit1", "fyz2_ex1_title", "fyz2_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Stupnice", "Celsiova a termodynamická",
"Tip: 0 °C je 273,15 K. Velikost jednoho stupně je stejná.",
{
"Celsiova teplota má nulu při tání ledu a 100 °C při varu vody za normálního tlaku.",
"Termodynamická teplota T se měří v kelvinech.",
"T = t + 273,15. Absolutní nula je 0 K, tedy −273,15 °C.",
"Částice se při absolutní nule nezastaví úplně, ale klasická teorie tam končí.",
NULL,
},
},
{
"2 / 3 • Teplo", "Kalorimetrická rovnice",
"Tip: měrná tepelná kapacita vody je asi 4180 J/(kg·K).",
{
"Teplo přijaté tělesem je Q = m·c·Δt.",
"c je měrná tepelná kapacita, u vody přibližně 4180 J/(kg·K).",
"Tepelná kapacita C = m·c říká, kolik tepla ohřeje těleso o 1 K.",
"V izolované soustavě je teplo odevzdané rovno teplu přijatému.",
NULL,
},
},
{
"3 / 3 • Přenos", "Vedení, proudění, záření",
"Tip: vakuum nevede teplo částicemi, záření jím projde.",
{
"Vedení přenáší energii srážkami částic, hlavně v pevných látkách.",
"Proudění přenáší teplo pohybem kapaliny nebo plynu.",
"Záření přenáší energii elektromagnetickými vlnami i ve vakuu.",
"Kovy vedou teplo dobře, vzduch a pěnové izolace špatně.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[1], "fyz2_unit2", "fyz2_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kolik kelvinů je 0 °C?",
{"0 K", "100 K", "273,15 K", "373,15 K"},
4, 2},
{"Jak se počítá přijaté teplo?",
{"Q = m·c·Δt", "Q = m·g·h", "Q = ½·m·v²", "Q = p·V"},
4, 0},
{"Jaká je přibližně měrná tepelná kapacita vody?",
{"4180 J/(kg·K)", "9,81 J/(kg·K)", "101 J/(kg·K)", "3·10⁸ J/(kg·K)"},
4, 0},
{"Který přenos tepla funguje i ve vakuu?",
{"Vedení", "Proudění", "Záření", "Difuzi částic"},
4, 2},
};
static const char *hints[] = {
"100 °C je 373,15 K. Rozdíl 1 °C je stejný jako rozdíl 1 K.",
"Δt je změna teploty. Větší hmotnost i větší c znamenají více tepla.",
"Voda má velkou měrnou tepelnou kapacitu, proto se ohřívá pomalu.",
"Vedení a proudění potřebují látku. Záření ne.",
};
return fyz_mcq_page(1, 1, "fyz2unit2", "fyz2_ex2_title", "fyz2_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Kalorimetr", "Směšovací rovnice",
"Tip: teplejší těleso teplo odevzdá, chladnější přijme.",
{
"V izolované soustavě platí Q odevzdané = Q přijaté.",
"Při smíchání dvou dávek téže kapaliny je m₁·c·(t₁ − t) = m₂·c·(t − t₂).",
"c se u stejné látky zkrátí.",
"Kalorimetr sám také přijme část tepla. Přesný výpočet s ním počítá.",
NULL,
},
},
{
"2 / 3 • Skupenství", "Teplo bez změny teploty",
"Tip: při tání a varu se teplota čisté látky nemění.",
{
"Skupenské teplo tání je Lt = m·lt.",
"Skupenské teplo varu je Lv = m·lv.",
"Měrné skupenské teplo tání ledu je asi 334 kJ/kg.",
"Měrné skupenské teplo varu vody je asi 2,26 MJ/kg.",
NULL,
},
},
{
"3 / 3 • 1. věta", "Zákon zachování",
"Tip: perpetuum mobile prvního druhu odporuje první větě.",
{
"První termodynamická věta: ΔU = W + Q.",
"Znaménka závisí na dohodě, škola často bere teplo přijaté a práci vykonanou na soustavě.",
"Energie nevzniká ani nezaniká, jen se přeměňuje.",
"Stroj nemůže trvale konat práci bez přísunu energie.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[2], "fyz2_unit3", "fyz2_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co platí v izolované soustavě při výměně tepla?",
{"Teplo odevzdané je větší než přijaté",
"Teplo odevzdané se rovná teplu přijatému",
"Teplota obou těles zůstane původní",
"Hmotnost se zdvojnásobí"},
4, 1},
{"Co se děje s teplotou čisté látky při tání?",
{"Stoupá rovnoměrně", "Nemění se, dokud látka taje",
"Klesá k absolutní nule", "Skáče o 100 K"},
4, 1},
{"Jak se počítá skupenské teplo tání?",
{"Lt = m·c·Δt", "Lt = m·lt", "Lt = p·V", "Lt = ½·m·v²"},
4, 1},
{"Co říká první termodynamická věta?",
{"Teplo se nedá nikam předat",
"Změna vnitřní energie souvisí s teplem a prací",
"Teplota je vždy 273 K",
"Účinnost tepelného stroje je 100 %"},
4, 1},
};
static const char *hints[] = {
"Kalorimetrická rovnice z toho přímo vychází.",
"Dodávané teplo při tání rozrušuje vazby, nezvedá teplotu.",
"lt je měrné skupenské teplo tání. U ledu asi 334 kJ/kg.",
"Druhá věta omezuje, kolik tepla lze proměnit v práci.",
};
return fyz_mcq_page(1, 2, "fyz2unit3", "fyz2_ex3_title", "fyz2_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Model", "Ideální plyn",
"Tip: stavová rovnice je p·V = n·R·T.",
{
"Ideální plyn má zanedbatelný vlastní objem částic a srážky bez ztráty kinetické energie.",
"Stavová rovnice je p·V = n·R·T.",
"R je molární plynová konstanta, asi 8,31 J/(mol·K).",
"T v této rovnici musí být v kelvinech.",
NULL,
},
},
{
"2 / 3 • Děje", "Izochorický, izobarický, izotermický",
"Tip: izotermický děj má stálou teplotu, proto p·V je konstantní.",
{
"Izochorický děj má stálý objem. Platí p / T = konst.",
"Izobarický děj má stálý tlak. Platí V / T = konst.",
"Izotermický děj má stálou teplotu. Platí p·V = konst.",
"Adiabatický děj nevyměňuje teplo s okolím.",
NULL,
},
},
{
"3 / 3 • Tlak plynu", "Odkud se bere",
"Tip: vyšší teplota znamená rychlejší částice a větší tlak.",
{
"Tlak plynu vzniká nárazy částic na stěnu.",
"Střední kinetická energie částice je úměrná termodynamické teplotě.",
"Normální podmínky jsou 0 °C a normální atmosférický tlak.",
"Reálný plyn se ideálnímu blíží při nízkém tlaku a vyšší teplotě.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[3], "fyz2_unit4", "fyz2_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak zní stavová rovnice ideálního plynu?",
{"p·V = n·R·T", "p / V = T", "Q = m·c·Δt", "F = m·a"},
4, 0},
{"Co je stálé při izotermickém ději?",
{"Objem", "Tlak", "Teplota", "Hmotnost částice"},
4, 2},
{"Co platí při izochorickém ději?",
{"V / T = konst.", "p / T = konst.", "p·V = konst.", "p·T = V"},
4, 1},
{"V jakých jednotkách musí být T ve stavové rovnici?",
{"Ve stupních Celsia", "V kelvinech", "Ve wattech", "V pascalech"},
4, 1},
};
static const char *hints[] = {
"n je látkové množství, R ≈ 8,31 J/(mol·K).",
"Boylův-Mariottův zákon: p·V = konst. při stálé teplotě.",
"Izochorický znamená stálý objem. Zahřátí zvedne tlak.",
"Dosazení Celsiovy teploty do p·V = n·R·T je chyba.",
};
return fyz_mcq_page(1, 3, "fyz2unit4", "fyz2_ex4_title", "fyz2_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Deformace", "Hookův zákon",
"Tip: v pružné oblasti je prodloužení úměrné síle.",
{
"Deformace je změna tvaru nebo objemu.",
"Pružná deformace zmizí, až síla přestane působit.",
"Tvárná deformace zůstane.",
"Hookův zákon: F = k·Δl. k je tuhost.",
NULL,
},
},
{
"2 / 3 • Krystal", "Pevné látky",
"Tip: amorfní látka nemá pravidelnou mřížku v celém objemu.",
{
"Krystal má částice v pravidelné mřížce.",
"Amorfní látka, třeba sklo, pravidelnou mřížku na velkou vzdálenost nemá.",
"Teplotní roztažnost zvětšuje rozměry při zahřátí.",
"Dilatace mostů a kolejí počítá s mezerami.",
NULL,
},
},
{
"3 / 3 • Povrch", "Kapaliny",
"Tip: kapka je kulatá, protože povrchová vrstva se stahuje.",
{
"Povrchová vrstva kapaliny se chová jako tenká blána.",
"Povrchové napětí stahuje povrch na co nejmenší obsah.",
"Kapilární elevace zvedá smáčivou kapalinu v úzké trubici.",
"Kapilární deprese stahuje nesmáčivou kapalinu, třeba rtuť ve skle.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[4], "fyz2_unit5", "fyz2_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co říká Hookův zákon v pružné oblasti?",
{"Síla je úměrná prodloužení",
"Síla je úměrná druhé mocnině prodloužení vždy",
"Deformace je vždy trvalá",
"Tlak nezávisí na síle"},
4, 0},
{"Čím se krystal liší od amorfní látky?",
{"Nemá částice",
"Má pravidelnou mřížku",
"Nemá teplotní roztažnost",
"Je vždy plyn"},
4, 1},
{"Proč má kapka kulový tvar?",
{"Protože povrchová vrstva stahuje povrch",
"Protože v kapalině není tlak",
"Protože kapka je krystal",
"Protože Hookův zákon platí jen pro koule"},
4, 0},
{"Co je kapilární elevace?",
{"Pokles nesmáčivé kapaliny",
"Vzestup smáčivé kapaliny v úzké trubici",
"Var při nízkém tlaku",
"Trvalá deformace"},
4, 1},
};
static const char *hints[] = {
"Za mezí pružnosti už závislost není přímá a těleso se nevrátí.",
"Sklo je typická amorfní látka. Sůl je krystal.",
"Koule má při daném objemu nejmenší povrch.",
"Deprese je pokles, třeba rtuti. Elevace je vzestup, třeba vody.",
};
return fyz_mcq_page(1, 4, "fyz2unit5", "fyz2_ex5_title", "fyz2_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Tání a tuhnutí", "Pevná látka a kapalina",
"Tip: teplota tání a teplota tuhnutí téže látky jsou stejné.",
{
"Tání mění pevnou látku na kapalinu při teplotě tání.",
"Tuhnutí je opačný děj a u čisté látky probíhá při stejné teplotě.",
"Příměsi teplotu tání obvykle snižují.",
"Led potřebuje skupenské teplo tání, proto směs ledu a vody drží 0 °C.",
NULL,
},
},
{
"2 / 3 • Vypařování", "Var a pára",
"Tip: var nastane, když se tlak syté páry rovná vnějšímu tlaku.",
{
"Vypařování probíhá z povrchu při každé teplotě.",
"Var probíhá v celém objemu při teplotě varu.",
"Sytá pára je v rovnováze se svou kapalinou.",
"Nižší vnější tlak znamená nižší teplotu varu.",
NULL,
},
},
{
"3 / 3 • Vlhkost", "Pára ve vzduchu",
"Tip: relativní vlhkost 100 % znamená, že pára je sytá.",
{
"Absolutní vlhkost udává hmotnost vodní páry v objemu vzduchu.",
"Relativní vlhkost je podíl tlaku páry a tlaku syté páry při dané teplotě.",
"Rosný bod je teplota, při které se pára ve vzduchu stane sytou.",
"Kondenzace je vznik kapaliny z páry.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[5], "fyz2_unit6", "fyz2_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Při jaké teplotě tuhne čistá voda za normálního tlaku?",
{"Při 100 °C", "Při 0 °C", "Při −273,15 °C", "Při teplotě varu rtuti"},
4, 1},
{"Čím se var liší od vypařování?",
{"Var probíhá jen na povrchu a při každé teplotě",
"Var probíhá v celém objemu při teplotě varu",
"Var nepotřebuje skupenské teplo",
"Vypařování nastane jen při 100 °C"},
4, 1},
{"Kdy nastane var?",
{"Když je tlak syté páry roven vnějšímu tlaku",
"Když je relativní vlhkost nulová",
"Když klesne teplota k rosnému bodu",
"Když je látka amorfní"},
4, 0},
{"Co je relativní vlhkost 100 %?",
{"Ve vzduchu není žádná pára",
"Pára ve vzduchu je sytá",
"Teplota je 100 °C",
"Tlak je 100 Pa"},
4, 1},
};
static const char *hints[] = {
"Tání a tuhnutí čisté vody jsou při 0 °C. Var je při 100 °C.",
"Vypařování je povrchové a děje se i pod teplotou varu.",
"Na horách je nižší tlak, proto voda vře při nižší teplotě.",
"Rosný bod je teplota, při níž relativní vlhkost dosáhne 100 %.",
};
return fyz_mcq_page(1, 5, "fyz2unit6", "fyz2_ex6_title", "fyz2_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Oscilátor", "Perioda a frekvence",
"Tip: frekvence je převrácená hodnota periody.",
{
"Kmitání je pohyb, který se po periodě opakuje.",
"Perioda T je doba jednoho kmitu. Frekvence f = 1 / T.",
"Jednotka frekvence je hertz, Hz.",
"Harmonické kmitání popisuje sinus nebo kosinus.",
NULL,
},
},
{
"2 / 3 • Dva oscilátory", "Pružina a kyvadlo",
"Tip: perioda kyvadla nezávisí na hmotnosti závaží.",
{
"Pružinový oscilátor má T = 2π·√(m / k).",
"Matematické kyvadlo má pro malé výchylky T = 2π·√(l / g).",
"Delší kyvadlo kmitá pomaleji.",
"Větší g znamená kratší periodu kyvadla.",
NULL,
},
},
{
"3 / 3 • Rezonance", "Vlastní a nucené kmitání",
"Tip: rezonance nastane, když budicí frekvence souhlasí s vlastní.",
{
"Vlastní frekvence patří volnému oscilátoru.",
"Nucené kmitání udržuje vnější síla.",
"Při rezonanci amplituda silně vzroste.",
"Tlumení energii kmitů odebírá a amplitudu zmenšuje.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[6], "fyz2_unit7", "fyz2_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak spolu souvisí frekvence a perioda?",
{"f = T", "f = 1 / T", "f = T²", "f = 2π·T"},
4, 1},
{"Na čem závisí perioda matematického kyvadla?",
{"Na hmotnosti závaží a na barvě",
"Na délce a na tíhovém zrychlení",
"Jen na amplitudě při velkých úhlech jako jediném údaji",
"Na tlaku vzduchu jako na hlavní veličině"},
4, 1},
{"Jak se změní perioda, když kyvadlo prodloužíme?",
{"Zkrátí se", "Prodlouží se", "Nezmění se", "Stane se nulovou"},
4, 1},
{"Kdy nastane rezonance?",
{"Když je budicí frekvence rovna vlastní frekvenci",
"Když je tlumení nekonečné",
"Když oscilátor stojí",
"Když je perioda rovna vlnové délce"},
4, 0},
};
static const char *hints[] = {
"10 Hz znamená 10 kmitů za sekundu, perioda je 0,1 s.",
"Vzorec T = 2π·√(l / g) hmotnost neobsahuje.",
"Pod odmocninou je délka. Delší l znamená větší T.",
"Rezonance může stavbu nebo most rozkmitat nebezpečně.",
};
return fyz_mcq_page(1, 6, "fyz2unit7", "fyz2_ex7_title", "fyz2_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_fyz2_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Vlna", "Vlnová délka",
"Tip: v = λ·f platí pro každou vlnu.",
{
"Vlnění přenáší energii, ne látku jako celek.",
"Vlnová délka λ je vzdálenost dvou sousedních bodů ve stejné fázi.",
"Rychlost vlny je v = λ·f.",
"Postupné vlnění se šíří prostředím. Stojaté vzniká skládáním.",
NULL,
},
},
{
"2 / 3 • Druhy", "Příčné a podélné",
"Tip: zvuk ve vzduchu je podélné vlnění.",
{
"U příčného vlnění kmitají částice kolmo ke směru šíření.",
"U podélného vlnění kmitají ve směru šíření.",
"Na hladině vody je vlnění příčné, zvuk v plynu podélný.",
"Odraz, lom a ohyb patří k vlnění obecně.",
NULL,
},
},
{
"3 / 3 • Zvuk", "Výška, hlasitost, barva",
"Tip: zdravé ucho slyší zhruba od 20 Hz do 20 kHz.",
{
"Zvuk je mechanické vlnění, které vnímáme sluchem.",
"Výška tónu odpovídá frekvenci. Hlasitost souvisí s amplitudou.",
"Infrazvuk je pod 20 Hz, ultrazvuk nad 20 kHz.",
"Ve vzduchu při pokojové teplotě je rychlost zvuku asi 340 m/s.",
NULL,
},
},
};
return fyz_unit_page(1, &fyz2_lessons[7], "fyz2_unit8", "fyz2_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz2_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá rychlost vlny?",
{"v = λ / f", "v = λ·f", "v = f / λ", "v = T·λ²"},
4, 1},
{"Jaké vlnění je zvuk ve vzduchu?",
{"Příčné elektromagnetické", "Podélné mechanické",
"Stojaté jen na hladině", "Příčné jako světlo"},
4, 1},
{"Které frekvence člověk obvykle slyší?",
{"Od 20 Hz do 20 kHz", "Jen nad 20 kHz",
"Jen pod 20 Hz", "Od 340 Hz do 340 kHz"},
4, 0},
{"Jak rychle se šíří zvuk ve vzduchu při pokojové teplotě?",
{"Asi 340 m/s", "Asi 3·10⁸ m/s", "Asi 9,81 m/s", "Asi 1500 km/s"},
4, 0},
};
static const char *hints[] = {
"Vyšší frekvence při stejné rychlosti znamená kratší vlnovou délku.",
"Světlo je příčné elektromagnetické. Zvuk potřebuje látkové prostředí.",
"Pod 20 Hz je infrazvuk, nad 20 kHz ultrazvuk.",
"Ve vodě je zvuk rychlejší, zhruba 1500 m/s. Světlo má 3·10⁸ m/s.",
};
return fyz_mcq_page(1, 7, "fyz2unit8", "fyz2_ex8_title", "fyz2_quiz8_head",
qs, hints, 4);
}
void add_fyz2_pages(GtkStack *stack) {
typedef GtkWidget *(*FyzBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
FyzBuilder build_unit;
FyzBuilder build_ex;
} pages[] = {
{"fyz2unit1", "fyz2ex1", build_fyz2_unit1_page, build_fyz2_unit1_exercise_page},
{"fyz2unit2", "fyz2ex2", build_fyz2_unit2_page, build_fyz2_unit2_exercise_page},
{"fyz2unit3", "fyz2ex3", build_fyz2_unit3_page, build_fyz2_unit3_exercise_page},
{"fyz2unit4", "fyz2ex4", build_fyz2_unit4_page, build_fyz2_unit4_exercise_page},
{"fyz2unit5", "fyz2ex5", build_fyz2_unit5_page, build_fyz2_unit5_exercise_page},
{"fyz2unit6", "fyz2ex6", build_fyz2_unit6_page, build_fyz2_unit6_exercise_page},
{"fyz2unit7", "fyz2ex7", build_fyz2_unit7_page, build_fyz2_unit7_exercise_page},
{"fyz2unit8", "fyz2ex8", build_fyz2_unit8_page, build_fyz2_unit8_exercise_page},
};
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
}
}

618
src/fyz_year3.c Normal file
View file

@ -0,0 +1,618 @@
#include "graduately.h"
/* Year 3: electricity and magnetism. Charge, current, circuits,
* matter in electric field, magnetic field, induction, alternating
* current and electromagnetic waves. */
NetLesson fyz3_lessons[FYZ_N] = {
{ .n_slides = 3, .unit_page = "fyz3unit1", .ex_page = "fyz3ex1" },
{ .n_slides = 3, .unit_page = "fyz3unit2", .ex_page = "fyz3ex2" },
{ .n_slides = 3, .unit_page = "fyz3unit3", .ex_page = "fyz3ex3" },
{ .n_slides = 3, .unit_page = "fyz3unit4", .ex_page = "fyz3ex4" },
{ .n_slides = 3, .unit_page = "fyz3unit5", .ex_page = "fyz3ex5" },
{ .n_slides = 3, .unit_page = "fyz3unit6", .ex_page = "fyz3ex6" },
{ .n_slides = 3, .unit_page = "fyz3unit7", .ex_page = "fyz3ex7" },
{ .n_slides = 3, .unit_page = "fyz3unit8", .ex_page = "fyz3ex8" },
};
static GtkWidget *build_fyz3_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Náboj", "Dva druhy",
"Tip: souhlasné náboje se odpuzují, nesouhlasné přitahují.",
{
"Elektrický náboj je kladný nebo záporný.",
"Elementární náboj je e ≈ 1,6·10⁻¹⁹ C.",
"Náboj se zachovává a je celistvým násobkem e.",
"Třením se náboje rozdělují, nevznikají z ničeho.",
NULL,
},
},
{
"2 / 3 • Coulomb", "Síla mezi náboji",
"Tip: síla klesá s druhou mocninou vzdálenosti.",
{
"Coulombův zákon: Fe = k·|Q₁·Q₂| / r².",
"k je asi 9·10⁹ N·m²/C².",
"Vodič má volné náboje, izolant je nemá.",
"Elektrické pole je kolem každého náboje.",
NULL,
},
},
{
"3 / 3 • Pole", "Intenzita a napětí",
"Tip: napětí je práce na jednotkový náboj.",
{
"Intenzita E = F / q ukazuje sílu na kladný jednotkový náboj.",
"Homogenní pole má všude stejnou intenzitu, třeba mezi deskami.",
"Napětí U = W / Q. Jednotka je volt.",
"Ekvipotenciální hladina je množina bodů se stejným potenciálem.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[0], "fyz3_unit1", "fyz3_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak na sebe působí souhlasné náboje?",
{"Přitahují se", "Odpuzují se", "Nepůsobí", "Jen při stejné hmotnosti"},
4, 1},
{"Jak velký je elementární náboj?",
{"Asi 1,6·10⁻¹⁹ C", "Asi 9·10⁹ C", "1 C přesně jako proton", "Asi 6,67·10⁻¹¹ C"},
4, 0},
{"Jak závisí Coulombova síla na vzdálenosti?",
{"Přímo úměrně", "Nepřímo úměrně druhé mocnině vzdálenosti",
"Nezávisí na ní", "Přímo úměrně druhé mocnině"},
4, 1},
{"Co je elektrické napětí?",
{"Síla na jednotkovou hmotnost",
"Práce na jednotkový náboj",
"Náboj za čas",
"Odpor krát plocha"},
4, 1},
};
static const char *hints[] = {
"Nesouhlasné náboje se přitahují. Neutrální těleso má vyrovnané náboje.",
"Proton má +e, elektron −e. Coulomb je velmi velký náboj.",
"Stejný tvar má i gravitační zákon, jen konstanta je jiná.",
"1 V = 1 J/C. Intenzita se měří v N/C nebo V/m.",
};
return fyz_mcq_page(2, 0, "fyz3unit1", "fyz3_ex1_title", "fyz3_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Proud", "Náboj v pohybu",
"Tip: dohodnutý směr proudu je směr pohybu kladného náboje.",
{
"Elektrický proud je usměrněný pohyb nabitých částic.",
"Proud I = Q / t. Jednotka je ampér.",
"V kovech proud nesou volné elektrony.",
"Dohodnutý směr proudu je opačný než směr pohybu elektronů.",
NULL,
},
},
{
"2 / 3 • Ohm", "Odpor",
"Tip: větší délka vodiče znamená větší odpor, větší průřez menší.",
{
"Ohmův zákon pro část obvodu: U = R·I.",
"Odpor R závisí na materiálu, délce a průřezu.",
"R = ρ·l / S. ρ je měrný elektrický odpor.",
"S rostoucí teplotou odpor kovů obvykle roste.",
NULL,
},
},
{
"3 / 3 • Zdroj", "Svorkové napětí",
"Tip: vnitřní odpor zdroje snižuje napětí při odběru proudu.",
{
"Zdroj má elektromotorické napětí a vnitřní odpor.",
"Svorkové napětí je menší než elektromotorické, když obvodem teče proud.",
"Zkrat je spojení s velmi malým odporem. Proud je nebezpečně velký.",
"Ampérmetr se řadí sériově, voltmetr paralelně.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[1], "fyz3_unit2", "fyz3_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá proud?",
{"I = Q·t", "I = Q / t", "I = U·R", "I = R / U"},
4, 1},
{"Jak zní Ohmův zákon pro část obvodu?",
{"U = R / I", "U = R·I", "U = I / R", "R = U·I"},
4, 1},
{"Kdy má kovový vodič větší odpor?",
{"Když je kratší a tlustší",
"Když je delší a tenčí",
"Když má větší průřez při stejné délce",
"Když je z látky s nulovým měrným odporem"},
4, 1},
{"Jak se zapojuje ampérmetr?",
{"Paralelně ke spotřebiči", "Sériově do obvodu",
"Jen místo voltmetru paralelně", "Mimo obvod"},
4, 1},
};
static const char *hints[] = {
"Ampér je coulomb za sekundu.",
"Odpor je U/I. Větší odpor při stejném napětí znamená menší proud.",
"R = ρ·l / S. Měrný odpor ρ je vlastnost látky.",
"Voltmetr má mít velký odpor a měří napětí vedle spotřebiče.",
};
return fyz_mcq_page(2, 1, "fyz3unit2", "fyz3_ex2_title", "fyz3_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Spojení", "Sériově a paralelně",
"Tip: sériově se odpory sčítají, proud je všude stejný.",
{
"Při sériovém spojení je proud stejný a napětí se sčítají.",
"Výsledný odpor sériově je R = R₁ + R₂ + …",
"Při paralelním spojení je napětí stejné a proudy se sčítají.",
"Pro dva paralelní odpory je 1/R = 1/R₁ + 1/R₂.",
NULL,
},
},
{
"2 / 3 • Kirchhoff", "Uzly a smyčky",
"Tip: v uzlu je součet přitékajících proudů roven součtu odtékajících.",
{
"První Kirchhoffův zákon je o proudu v uzlu.",
"Druhý Kirchhoffův zákon: součet napětí ve smyčce je nulový.",
"Zdroj ve smyčce dodává napětí, spotřebiče ho ubírají.",
"Zákony stačí na jednoduché obvody se dvěma smyčkami.",
NULL,
},
},
{
"3 / 3 • Výkon", "Práce proudu",
"Tip: příkon je P = U·I.",
{
"Práce elektrického proudu je W = U·I·t.",
"Příkon je P = U·I = R·I² = U² / R.",
"Kilowatthodina je jednotka energie, ne výkonu.",
"Jouleovo teplo ve vodiči je Q = R·I²·t.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[2], "fyz3_unit3", "fyz3_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaký je výsledný odpor dvou sériově spojených rezistorů?",
{"R = R₁ + R₂", "1/R = 1/R₁ + 1/R₂", "R = R₁·R₂", "R = R₁ / R₂"},
4, 0},
{"Co je stejné na paralelně spojených spotřebičích?",
{"Proud každým z nich", "Napětí",
"Odpor každého z nich", "Jouleovo teplo bez ohledu na odpor"},
4, 1},
{"Co říká první Kirchhoffův zákon?",
{"Součet napětí ve smyčce je nula",
"Součet proudů v uzlu je nula",
"Odpor roste s teplotou",
"Výkon je U děleno I"},
4, 1},
{"Jak se počítá elektrická práce?",
{"W = F·s", "W = U·I·t", "W = R / I", "W = Q·c·Δt"},
4, 1},
};
static const char *hints[] = {
"Paralelní spojení výsledný odpor zmenší. Dva stejné paralelně dají polovinu.",
"Větší odpor při stejném napětí bere menší proud.",
"Druhý zákon se týká napětí ve smyčce.",
"1 kWh = 3,6·10⁶ J. Příkon ve wattech je U·I.",
};
return fyz_mcq_page(2, 2, "fyz3unit3", "fyz3_ex3_title", "fyz3_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Polovodič", "Vlastní a příměsová vodivost",
"Tip: dioda vede proud hlavně v propustném směru.",
{
"Polovodič má vodivost mezi vodičem a izolantem.",
"S teplotou jeho odpor obvykle klesá, na rozdíl od kovu.",
"Typ N má příměs s volnými elektrony, typ P s dírami.",
"Přechod PN je základ diody. V závěrném směru téměř nevede.",
NULL,
},
},
{
"2 / 3 • Kapaliny", "Elektrolyt",
"Tip: v elektrolytu nesou proud ionty, ne volné elektrony jako v kovu.",
{
"Elektrolyt je roztok nebo tavenina, která vede proud ionty.",
"Kationty putují ke katodě, anionty k anodě.",
"Elektrolýza vylučuje látku na elektrodách.",
"Hmotnost vyloučené látky je úměrná náboji, který prošel.",
NULL,
},
},
{
"3 / 3 • Plyny", "Výboj",
"Tip: plyn za běžných podmínek nevede, dokud se neionizuje.",
{
"Samostatný výboj udržuje ionizaci sám, nesamostatný potřebuje vnější ionizátor.",
"Jiskra, oblouk a doutnavý výboj jsou druhy výboje.",
"Blesk je jiskrový výboj v atmosféře.",
"Výbojka a zářivka využívají záření výboje v plynu.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[3], "fyz3_unit4", "fyz3_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se s teplotou obvykle mění odpor polovodiče?",
{"Roste jako u kovu", "Klesá", "Nemění se", "Skokem se stane nulovým vždy"},
4, 1},
{"Co je polovodič typu N?",
{"Má převahu děr", "Má převahu volných elektronů z příměsi",
"Je izolant bez náboje", "Vede jen ionty"},
4, 1},
{"Čím je nesen proud v elektrolytu?",
{"Jen volnými elektrony jako v mědi", "Ionty",
"Fotony", "Neutrony"},
4, 1},
{"Kdy plyn začne výrazně vést proud?",
{"Až když je ionizovaný", "Vždy stejně jako kov",
"Jen pod absolutní nulou", "Jen když je to vakuum bez částic"},
4, 0},
};
static const char *hints[] = {
"Termistor s klesajícím odporem při zahřátí je typické užití.",
"Typ P má díry. Dioda je jeden přechod PN.",
"Katoda přitahuje kationty. Faradayův zákon váže hmotnost a náboj.",
"Nesamostatný výboj zanikne, když zmizí vnější ionizátor.",
};
return fyz_mcq_page(2, 3, "fyz3unit4", "fyz3_ex4_title", "fyz3_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Magnet", "Pole magnetu",
"Tip: souhlasné póly se odpuzují.",
{
"Souhlasné magnetické póly se odpuzují, nesouhlasné přitahují.",
"Severní pól střelky ukazuje k jižnímu magnetickému pólu Země.",
"Magnetické indukční čáry jsou uzavřené.",
"Zemské pole odklání část nabitých částic z vesmíru.",
NULL,
},
},
{
"2 / 3 • Proud", "Magnetické pole vodiče",
"Tip: cívka s proudem se chová jako magnet.",
{
"Kolem přímého vodiče s proudem jsou indukční čáry kružnice.",
"Cívka, solenoid, má uvnitř téměř homogenní pole.",
"Na vodič s proudem v magnetickém poli působí síla F = B·I·l·sin α.",
"B je magnetická indukce. Jednotka je tesla, T.",
NULL,
},
},
{
"3 / 3 • Užití", "Motor a měřidlo",
"Tip: elektromotor mění elektrickou energii na pohybovou.",
{
"Stejnosměrný motor využívá sílu na závit s proudem v magnetickém poli.",
"Komutátor mění směr proudu v závitu, aby se točil dál.",
"Částice s nábojem v magnetickém poli zatáčí, pokud má složku rychlosti kolmou k poli.",
"Magnetický záznam a elektromagnet jsou běžné užití pole.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[4], "fyz3_unit5", "fyz3_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaký tvar mají magnetické indukční čáry?",
{"Začínají na severním pólu a končí v nekonečnu jako přímky",
"Jsou uzavřené",
"Jsou vždy rovnoběžné s vodičem",
"Existují jen ve vakuu"},
4, 1},
{"Jak se počítá síla na přímý vodič s proudem v magnetickém poli?",
{"F = B·I·l·sin α", "F = k·Q₁·Q₂ / r²", "F = m·g", "F = U / R"},
4, 0},
{"Jaká je jednotka magnetické indukce?",
{"Volt", "Tesla", "Henry jen jako indukce B", "Ampér"},
4, 1},
{"K čemu je v stejnosměrném motoru komutátor?",
{"Aby zvýšil odpor na nekonečno",
"Aby měnil směr proudu v závitu a motor se točil dál",
"Aby zrušil magnetické pole",
"Aby měřil napětí"},
4, 1},
};
static const char *hints[] = {
"Elektrické siločáry začínají a končí na nábojích. Magnetické jsou uzavřené.",
"Když je vodič rovnoběžný s polem, sin α je 0 a síla je nulová.",
"Tesla je N/(A·m). Henry je jednotka indukčnosti.",
"Bez komutátoru by se závit po půl otáčce zastavil.",
};
return fyz_mcq_page(2, 4, "fyz3unit5", "fyz3_ex5_title", "fyz3_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Faraday", "Indukované napětí",
"Tip: napětí vzniká, jen když se magnetický tok mění.",
{
"Elektromagnetická indukce vzniká při změně magnetického toku plochou smyčky.",
"Magnetický tok je Φ = B·S·cos α.",
"Indukované napětí je tím větší, čím rychleji se tok mění.",
"Stálé pole a klidná smyčka napětí neindukují.",
NULL,
},
},
{
"2 / 3 • Lenz", "Směr proudu",
"Tip: indukovaný proud působí proti změně, která ho vyvolala.",
{
"Lenzův zákon určuje směr indukovaného proudu.",
"Proud vytváří pole, které brání změně toku.",
"Proto se magnet padající měděnou trubkou brzdí.",
"Vlastní indukce brání změně proudu v cívce.",
NULL,
},
},
{
"3 / 3 • Transformátor", "Změna napětí",
"Tip: poměr napětí je poměr počtů závitů.",
{
"Transformátor má dvě cívky na společném jádře.",
"U₁ / U₂ = N₁ / N₂.",
"Zvyšovací transformátor má na sekundáru víc závitů.",
"U ideálního transformátoru je příkon roven výkonu, proud se mění opačně než napětí.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[5], "fyz3_unit6", "fyz3_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Kdy se ve smyčce indukuje napětí?",
{"Když je magnetický tok stálý",
"Když se magnetický tok plochou smyčky mění",
"Když smyčkou neteče žádný náboj a tok je nulový trvale",
"Jen při stejnosměrném proudu v klidném poli"},
4, 1},
{"Co říká Lenzův zákon?",
{"Indukovaný proud podporuje změnu toku",
"Indukovaný proud působí proti změně, která ho vyvolala",
"Napětí je vždy rovno proudu",
"Tok je B děleno S"},
4, 1},
{"Jaký je poměr napětí na ideálním transformátoru?",
{"U₁ / U₂ = N₂ / N₁", "U₁ / U₂ = N₁ / N₂",
"U₁ / U₂ = I₁ / I₂ vždy při stejném počtu závitů jen",
"Napětí nezávisí na závitech"},
4, 1},
{"Co platí pro ideální transformátor?",
{"Příkon je roven výkonu",
"Sekundární proud je vždy nulový",
"Funguje i se stejnosměrným stálým proudem",
"Napětí na obou cívkách je stejné bez ohledu na závity"},
4, 0},
};
static const char *hints[] = {
"Pohyb magnetu u cívky, nebo změna proudu v primáru, tok mění.",
"Zákon zachování energie: indukce nemůže změnu sama zesilovat donekonečna.",
"Více závitů na sekundáru znamená vyšší sekundární napětí.",
"Stejnosměrný stálý proud tok nemění, proto se netransformuje.",
};
return fyz_mcq_page(2, 5, "fyz3unit6", "fyz3_ex6_title", "fyz3_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Střídání", "Perioda a frekvence",
"Tip: síť v Česku má frekvenci 50 Hz.",
{
"Střídavý proud periodicky mění velikost i směr.",
"Harmonické napětí popisuje sinus.",
"Frekvence sítě je 50 Hz, perioda je 0,02 s.",
"Okamžitá hodnota se stále mění. Měří se efektivní hodnota.",
NULL,
},
},
{
"2 / 3 • Efektivní", "Co ukáže voltmetr",
"Tip: efektivní hodnota je maximální dělená odmocninou ze dvou.",
{
"Efektivní proud vydá na rezistoru stejný výkon jako stejný stejnosměrný proud.",
"Pro sinusový průběh je Uef = Umax / √2.",
"Síťové napětí 230 V je efektivní hodnota.",
"Amplituda je tedy asi 325 V.",
NULL,
},
},
{
"3 / 3 • Obvod", "Odpor, cívka, kondenzátor",
"Tip: na rezistoru jsou napětí a proud ve fázi.",
{
"Rezistor proud a napětí neposouvá.",
"Cívka zpožďuje proud za napětím, kondenzátor ho předbíhá.",
"Indukčnost a kapacita závisejí na frekvenci.",
"Výkon na rezistoru je P = Uef·Ief.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[6], "fyz3_unit7", "fyz3_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaká je frekvence elektrické sítě v Česku?",
{"50 Hz", "230 Hz", "1 Hz", "60 kHz"},
4, 0},
{"Co znamená síťové napětí 230 V?",
{"Maximální hodnotu", "Efektivní hodnotu",
"Napětí za jednu periodu jako součet", "Vnitřní odpor"},
4, 1},
{"Jak spolu souvisí efektivní a maximální hodnota u sinusového napětí?",
{"Uef = Umax·√2", "Uef = Umax / √2", "Uef = Umax", "Uef = Umax / 2"},
4, 1},
{"Jak je to s fází na rezistoru?",
{"Proud se zpožďuje o čtvrt periodu",
"Napětí a proud jsou ve fázi",
"Proud předbíhá o půl periodu",
"Fáze závisí jen na barvě rezistoru"},
4, 1},
};
static const char *hints[] = {
"Perioda 50 Hz je 20 ms. V USA je síť 60 Hz.",
"Maximální napětí sítě je 230·√2, zhruba 325 V.",
"√2 je asi 1,414. Efektivní hodnota je menší než amplituda.",
"Fázový posun dělá cívka a kondenzátor, ne samotný rezistor.",
};
return fyz_mcq_page(2, 6, "fyz3unit7", "fyz3_ex7_title", "fyz3_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_fyz3_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Vznik", "Elektromagnetická vlna",
"Tip: ve vakuu se vlna šíří rychlostí světla.",
{
"Kmitající náboj vyzařuje elektromagnetickou vlnu.",
"Elektrická a magnetická složka jsou navzájem kolmé a kolmé ke směru šíření.",
"Ve vakuu je rychlost c = 3·10⁸ m/s.",
"Vlna přenáší energii a nepotřebuje látkové prostředí.",
NULL,
},
},
{
"2 / 3 • Spektrum", "Od rádia po záření gama",
"Tip: viditelné světlo je jen úzká část spektra.",
{
"Spektrum tvoří rádiové vlny, mikrovlny, infračervené záření, světlo, ultrafialové, rentgenové a gama.",
"Větší frekvence znamená kratší vlnovou délku, protože c = λ·f.",
"Viditelné světlo je zhruba 400 až 750 nm.",
"Za fialovou je ultrafialové záření, před červenou infračervené.",
NULL,
},
},
{
"3 / 3 • Užití", "Spoje a ohřev",
"Tip: mikrovlny rozkmitávají molekuly vody.",
{
"Rádiové vlny přenášejí rozhlas, televizi i mobilní signál.",
"Mikrovlny se používají v radarové technice a v troubě.",
"Rentgenové záření prochází měkkými tkáněmi víc než kostmi.",
"Všechny tyto vlny jsou totéž elektromagnetické vlnění, liší se frekvencí.",
NULL,
},
},
};
return fyz_unit_page(2, &fyz3_lessons[7], "fyz3_unit8", "fyz3_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz3_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak rychle se šíří elektromagnetická vlna ve vakuu?",
{"Asi 340 m/s", "Asi 3·10⁸ m/s", "Asi 9,81 m/s", "Závisí na hlasitosti"},
4, 1},
{"Co platí pro vlnovou délku a frekvenci ve vakuu?",
{"c = λ / f", "c = λ·f", "c = f / λ", "λ nezávisí na f"},
4, 1},
{"Kde ve spektru leží viditelné světlo?",
{"Mezi rádiovými vlnami a mikrovlnami",
"Mezi infračerveným a ultrafialovým zářením",
"Za zářením gama",
"Jen jako zvuk"},
4, 1},
{"Čím se od sebe liší rádiová vlna a rentgenové záření?",
{"Rentgen není elektromagnetická vlna",
"Mají jinou frekvenci",
"Rentgen potřebuje vzduch, rádio vakuum",
"Rádiová vlna má vždy vyšší frekvenci"},
4, 1},
};
static const char *hints[] = {
"340 m/s je zvuk. Světlo je skoro milionkrát rychlejší.",
"Stejný vztah jako u mechanické vlny, rychlost je c.",
"Fialová má kratší vlnovou délku než červená.",
"Gama a rentgen mají vysokou frekvenci a krátkou vlnovou délku.",
};
return fyz_mcq_page(2, 7, "fyz3unit8", "fyz3_ex8_title", "fyz3_quiz8_head",
qs, hints, 4);
}
void add_fyz3_pages(GtkStack *stack) {
typedef GtkWidget *(*FyzBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
FyzBuilder build_unit;
FyzBuilder build_ex;
} pages[] = {
{"fyz3unit1", "fyz3ex1", build_fyz3_unit1_page, build_fyz3_unit1_exercise_page},
{"fyz3unit2", "fyz3ex2", build_fyz3_unit2_page, build_fyz3_unit2_exercise_page},
{"fyz3unit3", "fyz3ex3", build_fyz3_unit3_page, build_fyz3_unit3_exercise_page},
{"fyz3unit4", "fyz3ex4", build_fyz3_unit4_page, build_fyz3_unit4_exercise_page},
{"fyz3unit5", "fyz3ex5", build_fyz3_unit5_page, build_fyz3_unit5_exercise_page},
{"fyz3unit6", "fyz3ex6", build_fyz3_unit6_page, build_fyz3_unit6_exercise_page},
{"fyz3unit7", "fyz3ex7", build_fyz3_unit7_page, build_fyz3_unit7_exercise_page},
{"fyz3unit8", "fyz3ex8", build_fyz3_unit8_page, build_fyz3_unit8_exercise_page},
};
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
}
}

635
src/fyz_year4.c Normal file
View file

@ -0,0 +1,635 @@
#include "graduately.h"
/* Year 4: wave and geometric optics, special relativity, quantum
* physics, the atom, the nucleus, nuclear reactions and astrophysics. */
NetLesson fyz4_lessons[FYZ_N] = {
{ .n_slides = 3, .unit_page = "fyz4unit1", .ex_page = "fyz4ex1" },
{ .n_slides = 3, .unit_page = "fyz4unit2", .ex_page = "fyz4ex2" },
{ .n_slides = 3, .unit_page = "fyz4unit3", .ex_page = "fyz4ex3" },
{ .n_slides = 3, .unit_page = "fyz4unit4", .ex_page = "fyz4ex4" },
{ .n_slides = 3, .unit_page = "fyz4unit5", .ex_page = "fyz4ex5" },
{ .n_slides = 3, .unit_page = "fyz4unit6", .ex_page = "fyz4ex6" },
{ .n_slides = 3, .unit_page = "fyz4unit7", .ex_page = "fyz4ex7" },
{ .n_slides = 3, .unit_page = "fyz4unit8", .ex_page = "fyz4ex8" },
};
static GtkWidget *build_fyz4_unit1_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Paprsek", "Odraz a lom",
"Tip: úhel odrazu se rovná úhlu dopadu.",
{
"Světlo se v homogenním prostředí šíří přímočaře.",
"Úhel odrazu se rovná úhlu dopadu. Oba se měří od kolmice.",
"Index lomu je n = c / v. Ve skle je větší než 1.",
"Snellův zákon: n₁·sin α = n₂·sin β.",
NULL,
},
},
{
"2 / 3 • Mez", "Úplný odraz",
"Tip: úplný odraz nastane při přechodu do opticky řidšího prostředí.",
{
"Opticky hustší prostředí má větší index lomu.",
"Při přechodu do řidšího prostředí se paprsek láme od kolmice.",
"Od mezního úhlu se už neláme a nastane úplný odraz.",
"Optická vlákna vedou světlo opakovaným úplným odrazem.",
NULL,
},
},
{
"3 / 3 • Vlna", "Interference a ohyb",
"Tip: světlo je příčné elektromagnetické vlnění.",
{
"Interference je skládání vln. Světlý pruh vzniká při dráhovém rozdílu k·λ.",
"Tmavý pruh vzniká při rozdílu liché poloviny vlnové délky.",
"Ohyb je patrný, když je překážka srovnatelná s vlnovou délkou.",
"Polarizace dokazuje, že světlo je příčné vlnění.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[0], "fyz4_unit1", "fyz4_unit1_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit1_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak zní zákon odrazu?",
{"Úhel odrazu je dvojnásobek úhlu dopadu",
"Úhel odrazu se rovná úhlu dopadu",
"Úhel odrazu je vždy 90°",
"Odraz nezávisí na kolmici"},
4, 1},
{"Co je index lomu?",
{"n = v / c", "n = c / v", "n = λ·f v látce dělené c²", "n = sin α"},
4, 1},
{"Kdy nastane úplný odraz?",
{"Při přechodu do opticky řidšího prostředí nad mezním úhlem",
"Při každém dopadu na zrcadlo",
"Jen ve vakuu",
"Při přechodu do hustšího prostředí"},
4, 0},
{"Kdy vznikne interferencí světlý pruh?",
{"Při dráhovém rozdílu k·λ",
"Při dráhovém rozdílu vždy λ/2",
"Když se vlny nepotkají",
"Jen u podélného zvuku"},
4, 0},
};
static const char *hints[] = {
"Úhly se měří od kolmice dopadu, ne od plochy.",
"V látce je světlo pomalejší než ve vakuu, proto n > 1.",
"Mezní úhel splňuje sin αm = n₂ / n₁ pro n₁ > n₂.",
"Podmínka pro minimum je (2k+1)·λ/2.",
};
return fyz_mcq_page(3, 0, "fyz4unit1", "fyz4_ex1_title", "fyz4_quiz1_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit2_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Zrcadla", "Dutá a vypuklá",
"Tip: zobrazovací rovnice je 1/a + 1/a' = 1/f.",
{
"Rovinné zrcadlo dává vzpřímený obraz stejné velikosti, zdánlivě za zrcadlem.",
"Duté zrcadlo může dát skutečný převrácený obraz.",
"Vypuklé zrcadlo dává zmenšený vzpřímený zdánlivý obraz.",
"Ohnisková vzdálenost kulového zrcadla je f = r / 2.",
NULL,
},
},
{
"2 / 3 • Čočky", "Spojka a rozptylka",
"Tip: spojka soustředí rovnoběžné paprsky do ohniska.",
{
"Spojka je uprostřed tlustší a má kladnou ohniskovou vzdálenost.",
"Rozptylka je uprostřed tenčí a paprsky rozbíhá.",
"Zobrazovací rovnice je 1/a + 1/a' = 1/f.",
"a je vzdálenost předmětu, a' vzdálenost obrazu.",
NULL,
},
},
{
"3 / 3 • Oko", "Vady a přístroje",
"Tip: krátkozrakost se koriguje rozptylkou.",
{
"Oko zobrazuje spojnou soustavou na sítnici.",
"Krátkozraké oko zobrazuje před sítnici. Pomůže rozptylka.",
"Dalekozraké oko zobrazuje za sítnici. Pomůže spojka.",
"Lupa je spojka a předmět je mezi čočkou a ohniskem.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[1], "fyz4_unit2", "fyz4_unit2_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit2_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jaká je ohnisková vzdálenost kulového zrcadla?",
{"f = r", "f = r / 2", "f = 2·r", "f = 1 / r²"},
4, 1},
{"Jak zní zobrazovací rovnice čočky?",
{"a + a' = f", "1/a + 1/a' = 1/f", "a·a' = f", "1/a − 1/f = a'"},
4, 1},
{"Čím se koriguje krátkozrakost?",
{"Spojkou", "Rozptylkou", "Dutým zrcadlem v oku", "Clonou bez čočky"},
4, 1},
{"Jaký obraz dává lupa?",
{"Skutečný převrácený, předmět je za ohniskem",
"Zvětšený vzpřímený zdánlivý, předmět je mezi čočkou a ohniskem",
"Zmenšený obraz v nekonečnu vždy",
"Obraz stejný jako rovinné zrcadlo"},
4, 1},
};
static const char *hints[] = {
"Poloměr křivosti je dvojnásobek ohniskové vzdálenosti.",
"Když je a = 2f, je i a' = 2f a obraz je stejně velký.",
"Dalekozrakost koriguje spojka. Obraz pak padne na sítnici.",
"Zdánlivý obraz spojky vzniká, když je předmět uvnitř ohniska.",
};
return fyz_mcq_page(3, 1, "fyz4unit2", "fyz4_ex2_title", "fyz4_quiz2_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit3_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Postuláty", "Dva principy",
"Tip: rychlost světla ve vakuu je ve všech inerciálních soustavách stejná.",
{
"Fyzikální zákony mají ve všech inerciálních soustavách stejný tvar.",
"Rychlost světla ve vakuu je ve všech inerciálních soustavách c.",
"c je mezní rychlost pro přenos informace a pro částice s hmotností.",
"Klasické skládání rychlostí u rychlostí blízkých c selhává.",
NULL,
},
},
{
"2 / 3 • Čas a délka", "Dilatace a kontrakce",
"Tip: pohybující se hodiny jdou z hlediska klidné soustavy pomaleji.",
{
"Dilatace času: pohyblivé hodiny jdou pomaleji.",
"Kontrakce délky zkracuje rozměr ve směru pohybu.",
"Rozměry kolmé ke směru pohybu se nemění.",
"Efekty jsou patrné až při rychlostech srovnatelných s c.",
NULL,
},
},
{
"3 / 3 • Energie", "E = m·c²",
"Tip: klidová energie je energie tělesa v soustavě, kde je v klidu.",
{
"Klidová energie je E₀ = m·c².",
"Celková energie roste s rychlostí.",
"Hmotnost a energie jsou dvě stránky téže veličiny.",
"Částice s nenulovou klidovou hmotností nikdy nedosáhne rychlosti c.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[2], "fyz4_unit3", "fyz4_unit3_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit3_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co platí o rychlosti světla ve vakuu?",
{"Závisí na rychlosti zdroje klasickým součtem",
"Je ve všech inerciálních soustavách stejná",
"Je 340 m/s",
"Je různá podle barvy jako hlavní postulát"},
4, 1},
{"Co je dilatace času?",
{"Pohyblivé hodiny jdou z hlediska klidného pozorovatele pomaleji",
"Pohyblivé hodiny jdou rychleji",
"Čas se zastaví při každé rychlosti",
"Délka se prodlužuje"},
4, 0},
{"Který rozměr se při kontrakci zkracuje?",
{"Rozměr kolmý ke směru pohybu",
"Rozměr ve směru pohybu",
"Všechny rozměry stejně",
"Žádný, zkracuje se jen hmotnost"},
4, 1},
{"Jak se značí klidová energie?",
{"E₀ = m·v² / 2", "E₀ = m·c²", "E₀ = m·g·h", "E₀ = h·f"},
4, 1},
};
static const char *hints[] = {
"Druhý postulát nahrazuje klasické skládání rychlostí světla.",
"Vzorec obsahuje √(1 − v²/c²) ve jmenovateli vlastního času.",
"Tyč kolmá k pohybu má stejnou délku v obou soustavách.",
"c² je obrovské číslo, proto i malá hmotnost znamená velkou energii.",
};
return fyz_mcq_page(3, 2, "fyz4unit3", "fyz4_ex3_title", "fyz4_quiz3_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit4_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Foton", "Energie kvanta",
"Tip: E = h·f. h je Planckova konstanta.",
{
"Záření si vyměňuje energii po kvantech, fotonech.",
"Energie fotonu je E = h·f.",
"h je asi 6,63·10⁻³⁴ J·s.",
"Větší frekvence znamená energičtější foton.",
NULL,
},
},
{
"2 / 3 • Fotoefekt", "Světlo vyráží elektrony",
"Tip: pod mezní frekvencí elektrony nevyletí, i když je světlo silné.",
{
"Fotoelektrický jev: foton předá energii elektronu v kovu.",
"Výstupní práce Wv je energie potřebná k opuštění kovu.",
"Einsteinova rovnice: h·f = Wv + Ek.",
"Počet elektronů roste s intenzitou, jejich maximální energie s frekvencí.",
NULL,
},
},
{
"3 / 3 • Dualita", "Vlna i částice",
"Tip: de Broglieho vlnová délka je λ = h / p.",
{
"Světlo se v interferenci chová jako vlna a ve fotoefektu jako částice.",
"I elektrony mají vlnové vlastnosti.",
"De Broglie: λ = h / p, kde p je hybnost.",
"Čím větší hybnost, tím kratší vlnová délka.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[3], "fyz4_unit4", "fyz4_unit4_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit4_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Jak se počítá energie fotonu?",
{"E = m·c² jen pro foton v klidu", "E = h·f", "E = h / f", "E = f / h"},
4, 1},
{"Co rozhoduje o tom, zda fotoefekt nastane?",
{"Jen intenzita světla",
"Frekvence musí být aspoň mezní",
"Jen barva kovu bez frekvence",
"Teplota laboratoře jako jediná podmínka"},
4, 1},
{"Jak zní Einsteinova rovnice fotoefektu?",
{"h·f = Wv + Ek", "h·f = Wv − Ek vždy záporně",
"E = m·g·h", "h·f = R·I"},
4, 0},
{"Co říká de Broglieho vztah?",
{"λ = h·p", "λ = h / p", "λ = p / h", "λ = c·p"},
4, 1},
};
static const char *hints[] = {
"Foton nemá klidovou hmotnost. Jeho energie je h·f, hybnost h/λ.",
"Silnější světlo stejné nízké frekvence elektrony neuvolní.",
"Zbytek energie fotonu po výstupní práci je kinetická energie elektronu.",
"Makroskopická tělesa mají obrovskou hybnost, proto nepatrnou vlnovou délku.",
};
return fyz_mcq_page(3, 3, "fyz4unit4", "fyz4_ex4_title", "fyz4_quiz4_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit5_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Modely", "Od Rutherforda k Bohrovi",
"Tip: Rutherfordův pokus ukázal malé kladné jádro.",
{
"Thomsonův model měl náboj rozptýlený v celém atomu.",
"Rutherford: atom má malé kladné jádro a obal z elektronů.",
"Většina objemu atomu je prázdná.",
"Bohr doplnil, že elektron je jen na určitých drahách bez vyzařování.",
NULL,
},
},
{
"2 / 3 • Hladiny", "Emise a absorpce",
"Tip: atom vyzáří foton, když elektron klesne na nižší hladinu.",
{
"Energie elektronu v atomu je kvantovaná.",
"Při přeskoku dolů atom vyzáří foton, při přeskoku nahoru ho pohltí.",
"Rozdíl hladin je E = h·f.",
"Spektrum plynu je čárové, spektrum pevné látky je spojité.",
NULL,
},
},
{
"3 / 3 • Obal", "Slupky",
"Tip: slupky se značí K, L, M a odpovídají hlavnímu kvantovému číslu.",
{
"Hlavní kvantové číslo n určuje slupku a energii ve vodíku.",
"Slupka K je n = 1, L je n = 2, M je n = 3.",
"Pauliho princip: v atomu nejsou dva elektrony se všemi kvantovými čísly stejnými.",
"Chemická vazba vzniká hlavně elektrony ve vnější slupce.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[4], "fyz4_unit5", "fyz4_unit5_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit5_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co ukázal Rutherfordův pokus?",
{"Že atom nemá jádro",
"Že kladný náboj je soustředěn v malém jádře",
"Že elektron má vlnovou délku nula",
"Že spektrum pevné látky je čárové"},
4, 1},
{"Kdy atom vyzáří foton?",
{"Když elektron přejde na nižší hladinu",
"Když elektron přejde na vyšší hladinu",
"Když se jádro nepohne a elektron stojí",
"Při každém oběhu podle klasické fyziky trvale"},
4, 0},
{"Jaká je energie fotonu při přeskoku?",
{"Součet obou hladin", "Rozdíl hladin",
"Vždy h·c bez rozdílu", "Klidová energie elektronu"},
4, 1},
{"Která slupka je nejblíž jádru?",
{"M", "L", "K", "Libovolná se stejným n"},
4, 2},
};
static const char *hints[] = {
"Odraz mála částic alfa o velký úhel nešel vysvětlit rozptýleným nábojem.",
"Přeskok nahoru je absorpce. Elektron energii fotonu přijme.",
"E₂ − E₁ = h·f. Větší skok znamená větší frekvenci.",
"n = 1 je K. Vyšší n je dál od jádra a má vyšší energii.",
};
return fyz_mcq_page(3, 4, "fyz4unit5", "fyz4_ex5_title", "fyz4_quiz5_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit6_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Jádro", "Protonové a nukleonové číslo",
"Tip: nukleonové číslo A je počet protonů a neutronů.",
{
"Jádro tvoří protony a neutrony, souhrnně nukleony.",
"Protonové číslo Z je počet protonů a určuje prvek.",
"Nukleonové číslo A je počet nukleonů. Neutronů je A − Z.",
"Izotopy mají stejné Z a různé A.",
NULL,
},
},
{
"2 / 3 • Vazba", "Vazebná energie",
"Tip: jádro je lehčí než součet volných nukleonů.",
{
"Jaderné síly drží nukleony pohromadě a působí na krátkou vzdálenost.",
"Vazebná energie je energie, kterou je třeba dodat na rozbití jádra na nukleony.",
"Hmotnostní úbytek odpovídá vazebné energii podle E = m·c².",
"Nejstabilnější jsou jádra kolem železa.",
NULL,
},
},
{
"3 / 3 • Rozpad", "Alfa, beta, gama",
"Tip: poločas je doba, za kterou se rozpadne polovina jader.",
{
"Záření alfa je jádro helia, má malý dolet a silné ionizační účinky.",
"Záření beta jsou elektrony nebo pozitrony z jádra.",
"Záření gama je elektromagnetické a proniká nejvíc.",
"Poločas rozpadu je pro daný nuklid stálý a nezávisí na okolí.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[5], "fyz4_unit6", "fyz4_unit6_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit6_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je nukleonové číslo A?",
{"Počet protonů", "Počet protonů a neutronů",
"Počet elektronů v obalu", "Počet slupek"},
4, 1},
{"Co jsou izotopy?",
{"Jádra se stejným Z a různým A",
"Jádra se stejným A a různým Z",
"Jádra bez neutronů",
"Atomy bez elektronů"},
4, 0},
{"Co je záření alfa?",
{"Elektromagnetická vlna", "Jádro helia",
"Volný neutron", "Foton z obalu"},
4, 1},
{"Co je poločas rozpadu?",
{"Doba, za kterou se rozpadne polovina jader",
"Doba jednoho oběhu elektronu",
"Doba, za kterou vznikne jádro železa",
"Doba života jednoho neutronu mimo každou látku jako jediný údaj"},
4, 0},
};
static const char *hints[] = {
"Zapiš uhlík 14 jako A = 14, Z = 6, neutronů 8.",
"Stejné protonové číslo znamená stejný prvek.",
"Beta je elektron nebo pozitron. Gama je foton.",
"Po dvou poločasech zbývá čtvrtina původních jader.",
};
return fyz_mcq_page(3, 5, "fyz4unit6", "fyz4_ex6_title", "fyz4_quiz6_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit7_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Štěpení", "Těžká jádra",
"Tip: štěpení uranu uvolní neutrony, které mohou štěpit dál.",
{
"Štěpení rozdělí těžké jádro na dvě středně těžká.",
"Uvolní se energie, protože produkty mají větší vazebnou energii na nukleon.",
"Při štěpení uranu 235 vznikají i neutrony.",
"Řetězová reakce pokračuje, když každý krok uvolní aspoň jeden další neutron.",
NULL,
},
},
{
"2 / 3 • Reaktor", "Řízená reakce",
"Tip: moderátor neutrony zpomaluje, regulační tyče je pohlcují.",
{
"V reaktoru je řetězová reakce řízená.",
"Moderátor, třeba voda, zpomaluje neutrony.",
"Regulační tyče neutrony zachycují a výkon tím klesá.",
"Teplo z reaktoru ohřívá vodu a pohání turbínu.",
NULL,
},
},
{
"3 / 3 • Syntéza", "Slučování lehkých jader",
"Tip: fúze pohání Slunce. Potřebuje vysokou teplotu.",
{
"Jaderná syntéza slučuje lehká jádra, třeba vodík na helium.",
"Uvolní víc energie na nukleon než štěpení.",
"K překonání odpudivé síly je potřeba velmi vysoká teplota.",
"Ochrana před zářením stojí na čase, vzdálenosti a stínění.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[6], "fyz4_unit7", "fyz4_unit7_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit7_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Co je řetězová reakce?",
{"Jedno štěpení, které se dál nešíří",
"Štěpení udržované neutrony z předchozích štěpení",
"Slučování elektronů",
"Rozpad alfa bez neutronů"},
4, 1},
{"K čemu je v reaktoru moderátor?",
{"Aby neutrony zpomalil",
"Aby všechna jádra okamžitě rozbil",
"Aby pohltil všechno teplo bez užitku",
"Aby zvýšil protonové číslo paliva"},
4, 0},
{"Co dělají regulační tyče?",
{"Neutrony zachycují a reakci brzdí",
"Neutrony jen vyrábějí",
"Slouží jako palivo místo uranu",
"Zvyšují teplotu fúze"},
4, 0},
{"Proč fúze potřebuje vysokou teplotu?",
{"Aby jádra překonala elektrické odpuzování",
"Aby se jádra ochladila",
"Aby vznikl moderátor",
"Aby poločas klesl k nule"},
4, 0},
};
static const char *hints[] = {
"Kritické množství znamená, že neutrony nestačí utéct a reakce běží.",
"Pomalý neutron štěpí uran 235 ochotněji než rychlý.",
"Zasunutí tyčí výkon sníží. Vysunutí ho zvýší.",
"Ve Slunci fúzi pomáhá i obrovský tlak. Záření se stíní olovem a betonem.",
};
return fyz_mcq_page(3, 6, "fyz4unit7", "fyz4_ex7_title", "fyz4_quiz7_head",
qs, hints, 4);
}
static GtkWidget *build_fyz4_unit8_page(void) {
static const NetSlide slides[] = {
{
"1 / 3 • Slunce", "Hvězda",
"Tip: Slunce získává energii fúzí vodíku na helium.",
{
"Slunce je hvězda. Svítí díky jaderné syntéze v jádře.",
"Světlo ze Slunce letí na Zemi asi 8 minut.",
"Svítivost a teplota povrchu řadí hvězdy do Hertzsprungova–Russellova diagramu.",
"Většina hvězd leží na hlavní posloupnosti.",
NULL,
},
},
{
"2 / 3 • Vývoj", "Od oblaku k závěru",
"Tip: hmotnější hvězda žije kratší dobu.",
{
"Hvězda vzniká smršťováním oblaku plynu a prachu.",
"Po vyčerpání vodíku se mění stavba hvězdy.",
"Hvězda podobná Slunci skončí jako bílý trpaslík.",
"Velmi hmotná hvězda může skončit výbuchem supernovy a zbyde neutronová hvězda nebo černá díra.",
NULL,
},
},
{
"3 / 3 • Vesmír", "Galaxie a rozpínání",
"Tip: světelný rok je vzdálenost, ne čas.",
{
"Galaxie je soustava hvězd, plynu a prachu. Slunce je v Mléčné dráze.",
"Světelný rok je dráha, kterou světlo urazí za rok.",
"Vzdálené galaxie mají spektrum posunuté k červené. To znamená, že se vzdalují.",
"Rudý posuv podporuje obraz rozpínajícího se vesmíru.",
NULL,
},
},
};
return fyz_unit_page(3, &fyz4_lessons[7], "fyz4_unit8", "fyz4_unit8_sub",
slides, G_N_ELEMENTS(slides));
}
static GtkWidget *build_fyz4_unit8_exercise_page(void) {
static const ChoiceQ qs[] = {
{"Odkud bere Slunce energii?",
{"Z hoření uhlí", "Z fúze vodíku na helium",
"Z štěpení uranu v kůře", "Z odrazu světla planet"},
4, 1},
{"Co je hlavní posloupnost?",
{"Pás, na kterém leží většina hvězd v HR diagramu",
"Dráha Země kolem Slunce",
"Řada planet podle velikosti",
"Spektrum jen bílých trpaslíků"},
4, 0},
{"Co je světelný rok?",
{"Doba oběhu Země", "Vzdálenost, kterou světlo urazí za rok",
"Rychlost světla", "Stáří Slunce"},
4, 1},
{"Co znamená rudý posuv vzdálených galaxií?",
{"Galaxie se přibližují",
"Galaxie se vzdalují",
"Galaxie přestaly svítit",
"Světlo změnilo rychlost ve vakuu"},
4, 1},
};
static const char *hints[] = {
"Fúze v jádře Slunce mění hmotnost na energii podle E = m·c².",
"Obři a bílí trpaslíci leží mimo hlavní posloupnost.",
"Rok je tu doba letu světla, výsledek je délka.",
"Posuv k delší vlnové délce je Dopplerův jev rozpínání.",
};
return fyz_mcq_page(3, 7, "fyz4unit8", "fyz4_ex8_title", "fyz4_quiz8_head",
qs, hints, 4);
}
void add_fyz4_pages(GtkStack *stack) {
typedef GtkWidget *(*FyzBuilder)(void);
static const struct {
const char *unit_name;
const char *ex_name;
FyzBuilder build_unit;
FyzBuilder build_ex;
} pages[] = {
{"fyz4unit1", "fyz4ex1", build_fyz4_unit1_page, build_fyz4_unit1_exercise_page},
{"fyz4unit2", "fyz4ex2", build_fyz4_unit2_page, build_fyz4_unit2_exercise_page},
{"fyz4unit3", "fyz4ex3", build_fyz4_unit3_page, build_fyz4_unit3_exercise_page},
{"fyz4unit4", "fyz4ex4", build_fyz4_unit4_page, build_fyz4_unit4_exercise_page},
{"fyz4unit5", "fyz4ex5", build_fyz4_unit5_page, build_fyz4_unit5_exercise_page},
{"fyz4unit6", "fyz4ex6", build_fyz4_unit6_page, build_fyz4_unit6_exercise_page},
{"fyz4unit7", "fyz4ex7", build_fyz4_unit7_page, build_fyz4_unit7_exercise_page},
{"fyz4unit8", "fyz4ex8", build_fyz4_unit8_page, build_fyz4_unit8_exercise_page},
};
for (guint i = 0; i < G_N_ELEMENTS(pages); i++) {
gtk_stack_add_named(stack, pages[i].build_unit(), pages[i].unit_name);
gtk_stack_add_named(stack, pages[i].build_ex(), pages[i].ex_name);
}
}

720
src/fyzika.c Normal file
View file

@ -0,0 +1,720 @@
#include "graduately.h"
/* Physics across four secondary-school years. Each year is a list of
* lessons, then slides and a quiz. The syllabus follows the gymnasium
* outline: mechanics, thermal physics and waves, electromagnetism,
* then optics, relativity, the micro-world and astrophysics. */
typedef struct {
const char *map_page;
const char *back_page;
const char *title_key;
const char *sub_key;
char **progress_path;
NetLesson *lessons;
const char *unit_keys[FYZ_N];
const char *sub_keys[FYZ_N];
GtkWidget *nodes[FYZ_N];
int note_last_w;
} FyzTrack;
static FyzTrack fyz_tracks[4];
static gboolean fyz_ready;
static void fyz_ensure(void) {
static const char *keys0[] = {
"fyz_unit1", "fyz_unit2", "fyz_unit3", "fyz_unit4",
"fyz_unit5", "fyz_unit6", "fyz_unit7", "fyz_unit8",
};
static const char *subs0[] = {
"fyz_unit1_sub", "fyz_unit2_sub", "fyz_unit3_sub", "fyz_unit4_sub",
"fyz_unit5_sub", "fyz_unit6_sub", "fyz_unit7_sub", "fyz_unit8_sub",
};
static const char *keys1[] = {
"fyz2_unit1", "fyz2_unit2", "fyz2_unit3", "fyz2_unit4",
"fyz2_unit5", "fyz2_unit6", "fyz2_unit7", "fyz2_unit8",
};
static const char *subs1[] = {
"fyz2_unit1_sub", "fyz2_unit2_sub", "fyz2_unit3_sub", "fyz2_unit4_sub",
"fyz2_unit5_sub", "fyz2_unit6_sub", "fyz2_unit7_sub", "fyz2_unit8_sub",
};
static const char *keys2[] = {
"fyz3_unit1", "fyz3_unit2", "fyz3_unit3", "fyz3_unit4",
"fyz3_unit5", "fyz3_unit6", "fyz3_unit7", "fyz3_unit8",
};
static const char *subs2[] = {
"fyz3_unit1_sub", "fyz3_unit2_sub", "fyz3_unit3_sub", "fyz3_unit4_sub",
"fyz3_unit5_sub", "fyz3_unit6_sub", "fyz3_unit7_sub", "fyz3_unit8_sub",
};
static const char *keys3[] = {
"fyz4_unit1", "fyz4_unit2", "fyz4_unit3", "fyz4_unit4",
"fyz4_unit5", "fyz4_unit6", "fyz4_unit7", "fyz4_unit8",
};
static const char *subs3[] = {
"fyz4_unit1_sub", "fyz4_unit2_sub", "fyz4_unit3_sub", "fyz4_unit4_sub",
"fyz4_unit5_sub", "fyz4_unit6_sub", "fyz4_unit7_sub", "fyz4_unit8_sub",
};
static const char *const *all_keys[4] = {keys0, keys1, keys2, keys3};
static const char *const *all_subs[4] = {subs0, subs1, subs2, subs3};
static const char *maps[] = {"fyzmap", "fyz2map", "fyz3map", "fyz4map"};
static const char *titles[] = {
"fyz_year1", "fyz_year2", "fyz_year3", "fyz_year4",
};
static const char *subs[] = {"fyz_sub", "fyz2_sub", "fyz3_sub", "fyz4_sub"};
NetLesson *lessons[4] = {
fyz_lessons, fyz2_lessons, fyz3_lessons, fyz4_lessons,
};
char **paths[4] = {
&app_progress_fyz, &app_progress_fyz2,
&app_progress_fyz3, &app_progress_fyz4,
};
if (fyz_ready)
return;
for (int y = 0; y < 4; y++) {
FyzTrack *T = &fyz_tracks[y];
T->map_page = maps[y];
T->back_page = "fyzyears";
T->title_key = titles[y];
T->sub_key = subs[y];
T->progress_path = paths[y];
T->lessons = lessons[y];
T->note_last_w = -1;
for (int i = 0; i < FYZ_N; i++) {
T->unit_keys[i] = all_keys[y][i];
T->sub_keys[i] = all_subs[y][i];
}
}
fyz_ready = TRUE;
}
static void fyz_save(FyzTrack *T) {
GKeyFile *kf;
char *path;
gchar *data;
if (!T->progress_path || !*T->progress_path)
return;
path = *T->progress_path;
kf = g_key_file_new();
for (int i = 0; i < FYZ_N; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
g_key_file_set_boolean(kf, "done", key, T->lessons[i].done);
}
if (g_mkdir_with_parents(PROGRESS_DIR, 0755) != 0) {
g_key_file_free(kf);
return;
}
data = g_key_file_to_data(kf, NULL, NULL);
g_key_file_free(kf);
if (data) {
GError *err = NULL;
g_file_set_contents(path, data, -1, &err);
if (err)
g_error_free(err);
g_free(data);
}
}
static void fyz_load_one(FyzTrack *T) {
GKeyFile *kf;
GError *err = NULL;
const char *path;
if (!T->progress_path || !*T->progress_path)
return;
path = *T->progress_path;
kf = g_key_file_new();
if (!g_key_file_load_from_file(kf, path, G_KEY_FILE_NONE, &err)) {
if (err)
g_error_free(err);
g_key_file_free(kf);
return;
}
for (int i = 0; i < FYZ_N; i++) {
gchar key[8];
g_snprintf(key, sizeof(key), "%d", i + 1);
T->lessons[i].done = g_key_file_get_boolean(kf, "done", key, NULL);
}
g_key_file_free(kf);
}
void fyz_load_progress(void) {
fyz_ensure();
for (int y = 0; y < 4; y++)
fyz_load_one(&fyz_tracks[y]);
}
void fyz_rail_theme_reset(void) {
}
static void fyz_refresh(FyzTrack *T) {
for (int i = 0; i < FYZ_N; i++) {
GtkWidget *node = T->nodes[i];
GtkWidget *icon = T->lessons[i].done_icon;
if (!node)
continue;
if (T->lessons[i].done) {
gtk_widget_remove_css_class(node, "current");
gtk_widget_add_css_class(node, "done");
if (icon)
gtk_widget_set_visible(icon, TRUE);
} else {
gtk_widget_remove_css_class(node, "done");
gtk_widget_add_css_class(node, "current");
if (icon)
gtk_widget_set_visible(icon, FALSE);
}
}
}
static void fyz_mark_done(FyzTrack *T, int lesson_id) {
if (lesson_id < 0 || lesson_id >= FYZ_N)
return;
if (T->lessons[lesson_id].done)
return;
T->lessons[lesson_id].done = TRUE;
fyz_save(T);
fyz_refresh(T);
refresh_stats_ui();
}
void progress_for_fyz(ProgressSum *out) {
if (!out)
return;
fyz_ensure();
for (int y = 0; y < 4; y++) {
FyzTrack *T = &fyz_tracks[y];
out->total_ex += FYZ_N;
out->open_units += FYZ_N;
for (int i = 0; i < FYZ_N; i++) {
if (T->lessons[i].done) {
out->done_ex++;
out->done_units++;
}
}
}
}
static void fyz_open_unit(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
L->idx = 0;
if (L->stack) {
char name[16];
g_snprintf(name, sizeof(name), "s%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn)
gtk_widget_set_sensitive(L->prev_btn, FALSE);
if (L->next_btn)
gtk_button_set_label(GTK_BUTTON(L->next_btn), tr("net_slide_next"));
}
gtk_stack_set_visible_child_name(main_stack, L->unit_page);
}
static GtkWidget *fyz_lesson_button(FyzTrack *T, int index) {
GtkWidget *btn;
GtkWidget *row;
GtkWidget *num;
GtkWidget *texts;
GtkWidget *title;
GtkWidget *sub;
GtkWidget *icon;
char *num_text;
btn = gtk_button_new();
gtk_widget_add_css_class(btn, "unit-node");
gtk_widget_add_css_class(btn, "current");
gtk_widget_set_can_focus(btn, FALSE);
gtk_widget_set_hexpand(btn, TRUE);
gtk_widget_set_size_request(btn, -1, 84);
row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
gtk_widget_set_halign(row, GTK_ALIGN_START);
gtk_widget_set_valign(row, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start(row, 18);
gtk_widget_set_margin_end(row, 18);
gtk_button_set_child(GTK_BUTTON(btn), row);
num_text = g_strdup_printf("%d", index + 1);
num = gtk_label_new(num_text);
g_free(num_text);
gtk_widget_add_css_class(num, "unit-number");
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(row), num);
texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_widget_set_halign(texts, GTK_ALIGN_START);
gtk_widget_set_hexpand(texts, TRUE);
gtk_box_append(GTK_BOX(row), texts);
title = gtk_label_new(NULL);
i18n_bind(title, T->unit_keys[index], 0);
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_widget_add_css_class(title, "unit-name");
gtk_box_append(GTK_BOX(texts), title);
sub = gtk_label_new(NULL);
i18n_bind(sub, T->sub_keys[index], 0);
gtk_widget_set_halign(sub, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
gtk_label_set_xalign(GTK_LABEL(sub), 0.0);
gtk_widget_add_css_class(sub, "hint");
gtk_box_append(GTK_BOX(texts), sub);
icon = icon_area_new(draw_check_icon, 0.1176, 0.1176, 0.1804, 18);
gtk_widget_set_visible(icon, FALSE);
gtk_widget_set_valign(icon, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(row), icon);
T->lessons[index].done_icon = icon;
T->nodes[index] = btn;
g_signal_connect(btn, "clicked", G_CALLBACK(fyz_open_unit),
&T->lessons[index]);
return btn;
}
static GtkWidget *fyz_build_list(FyzTrack *T) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *list;
fyz_ensure();
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(page, TRUE);
gtk_widget_set_vexpand(page, TRUE);
gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24);
gtk_box_append(GTK_BOX(page),
top_bar(T->back_page, T->title_key, T->sub_key));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 18);
gtk_box_append(GTK_BOX(page), scroll);
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
gtk_widget_set_valign(list, GTK_ALIGN_START);
gtk_widget_set_size_request(list, 520, -1);
gtk_widget_set_margin_bottom(list, 12);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
for (int i = 0; i < FYZ_N; i++)
gtk_box_append(GTK_BOX(list), fyz_lesson_button(T, i));
fyz_refresh(T);
return page;
}
GtkWidget *build_fyzmap_page(void) { return fyz_build_list(&fyz_tracks[0]); }
GtkWidget *build_fyz2map_page(void) { return fyz_build_list(&fyz_tracks[1]); }
GtkWidget *build_fyz3map_page(void) { return fyz_build_list(&fyz_tracks[2]); }
GtkWidget *build_fyz4map_page(void) { return fyz_build_list(&fyz_tracks[3]); }
static GtkWidget *fyz_year_button(int year, const char *target,
const char *title_key, const char *sub_key) {
GtkWidget *btn;
GtkWidget *row;
GtkWidget *num;
GtkWidget *texts;
GtkWidget *title;
GtkWidget *sub;
char *num_text;
btn = gtk_button_new();
gtk_widget_add_css_class(btn, "unit-node");
gtk_widget_add_css_class(btn, "current");
gtk_widget_set_can_focus(btn, FALSE);
gtk_widget_set_hexpand(btn, TRUE);
gtk_widget_set_size_request(btn, -1, 84);
row = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 14);
gtk_widget_set_halign(row, GTK_ALIGN_START);
gtk_widget_set_valign(row, GTK_ALIGN_CENTER);
gtk_widget_set_margin_start(row, 18);
gtk_widget_set_margin_end(row, 18);
gtk_button_set_child(GTK_BUTTON(btn), row);
num_text = g_strdup_printf("%d", year);
num = gtk_label_new(num_text);
g_free(num_text);
gtk_widget_add_css_class(num, "unit-number");
gtk_widget_set_valign(num, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(row), num);
texts = gtk_box_new(GTK_ORIENTATION_VERTICAL, 2);
gtk_widget_set_halign(texts, GTK_ALIGN_START);
gtk_widget_set_hexpand(texts, TRUE);
gtk_box_append(GTK_BOX(row), texts);
title = gtk_label_new(NULL);
i18n_bind(title, title_key, 0);
gtk_widget_set_halign(title, GTK_ALIGN_START);
gtk_widget_add_css_class(title, "unit-name");
gtk_box_append(GTK_BOX(texts), title);
sub = gtk_label_new(NULL);
i18n_bind(sub, sub_key, 0);
gtk_widget_set_halign(sub, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(sub), TRUE);
gtk_label_set_xalign(GTK_LABEL(sub), 0.0);
gtk_widget_add_css_class(sub, "hint");
gtk_box_append(GTK_BOX(texts), sub);
g_object_set_data_full(G_OBJECT(btn), "target", g_strdup(target), g_free);
g_signal_connect(btn, "clicked", G_CALLBACK(on_nav_clicked), NULL);
return btn;
}
GtkWidget *build_fyzyears_page(void) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *list;
static const char *targets[] = {"fyzmap", "fyz2map", "fyz3map", "fyz4map"};
static const char *titles[] = {
"fyz_year1", "fyz_year2", "fyz_year3", "fyz_year4",
};
static const char *subs[] = {"fyz_sub", "fyz2_sub", "fyz3_sub", "fyz4_sub"};
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_hexpand(page, TRUE);
gtk_widget_set_vexpand(page, TRUE);
gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24);
gtk_box_append(GTK_BOX(page),
top_bar("subjects", "Fyzika", "fyz_years_sub"));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 18);
gtk_box_append(GTK_BOX(page), scroll);
list = gtk_box_new(GTK_ORIENTATION_VERTICAL, 12);
gtk_widget_set_halign(list, GTK_ALIGN_CENTER);
gtk_widget_set_valign(list, GTK_ALIGN_CENTER);
gtk_widget_set_size_request(list, 480, -1);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), list);
for (int y = 0; y < 4; y++)
gtk_box_append(GTK_BOX(list),
fyz_year_button(y + 1, targets[y], titles[y], subs[y]));
return page;
}
static void fyz_slide_apply(NetLesson *L) {
char name[16];
if (!L || !L->stack)
return;
g_snprintf(name, sizeof(name), "s%u", L->idx);
gtk_stack_set_visible_child_name(GTK_STACK(L->stack), name);
if (L->prev_btn) {
gtk_button_set_label(GTK_BUTTON(L->prev_btn), tr("net_slide_prev"));
gtk_widget_set_sensitive(L->prev_btn, L->idx > 0);
}
if (L->next_btn) {
gtk_button_set_label(
GTK_BUTTON(L->next_btn),
L->idx + 1 >= L->n_slides ? tr("net_slide_start")
: tr("net_slide_next"));
}
}
static void fyz_slide_prev(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L || L->idx == 0)
return;
L->idx--;
fyz_slide_apply(L);
}
static void fyz_slide_next(GtkButton *button, gpointer data) {
NetLesson *L = data;
(void)button;
if (!L)
return;
if (L->idx + 1 < L->n_slides) {
L->idx++;
fyz_slide_apply(L);
} else {
gtk_stack_set_visible_child_name(main_stack, L->ex_page);
}
}
static void fyz_rescale(FyzTrack *T, int w) {
int body, head, kick;
if (w < 160)
w = 160;
body = w / 46;
if (body < 14) body = 14;
if (body > 26) body = 26;
head = body + 14;
if (head > 46) head = 46;
kick = body - 3;
if (kick < 11) kick = 11;
for (int i = 0; i < FYZ_N; i++)
net_rescale_lesson_notes(&T->lessons[i], body, head, kick);
}
static gboolean fyz_note_tick(GtkWidget *w, GdkFrameClock *clock,
gpointer data) {
FyzTrack *T = data;
int width = gtk_widget_get_allocated_width(w);
(void)clock;
if (width != T->note_last_w) {
T->note_last_w = width;
fyz_rescale(T, width);
}
return G_SOURCE_CONTINUE;
}
GtkWidget *fyz_unit_page(int year, NetLesson *L, const char *title_key,
const char *sub_key, const NetSlide *slides,
guint n_slides) {
FyzTrack *T;
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *nav;
fyz_ensure();
if (year < 0 || year > 3)
year = 0;
T = &fyz_tracks[year];
net_lesson_notes_ensure(L);
net_notes_target = L;
L->n_slides = n_slides;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start(page, 28);
gtk_widget_set_margin_end(page, 28);
gtk_widget_set_margin_top(page, 20);
gtk_widget_set_margin_bottom(page, 20);
gtk_box_append(GTK_BOX(page), top_bar(T->map_page, title_key, sub_key));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 10);
gtk_box_append(GTK_BOX(page), scroll);
L->stack = gtk_stack_new();
gtk_stack_set_transition_type(GTK_STACK(L->stack),
GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), L->stack);
for (guint s = 0; s < n_slides; s++) {
GtkWidget *stage = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
GtkWidget *card;
char name[16];
gtk_widget_set_vexpand(stage, TRUE);
g_snprintf(name, sizeof(name), "s%u", s);
gtk_stack_add_named(GTK_STACK(L->stack), stage, name);
card = gtk_box_new(GTK_ORIENTATION_VERTICAL, 16);
gtk_widget_add_css_class(card, "notes-card");
gtk_widget_set_valign(card, GTK_ALIGN_CENTER);
gtk_box_append(GTK_BOX(stage), card);
net_note_kicker(card, slides[s].kicker);
net_note_title(card, slides[s].title);
for (int i = 0; i < 8 && slides[s].line[i]; i++)
net_note_line(card, slides[s].line[i], FALSE);
if (slides[s].tip)
net_note_line(card, slides[s].tip, TRUE);
}
gtk_widget_add_tick_callback(L->stack, fyz_note_tick, T, NULL);
T->note_last_w = -1;
fyz_rescale(T, 0);
net_notes_target = NULL;
nav = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 10);
gtk_box_set_homogeneous(GTK_BOX(nav), TRUE);
gtk_widget_set_margin_top(nav, 10);
gtk_box_append(GTK_BOX(page), nav);
L->prev_btn = gtk_button_new_with_label(tr("net_slide_prev"));
gtk_widget_add_css_class(L->prev_btn, "btn-primary");
g_signal_connect(L->prev_btn, "clicked", G_CALLBACK(fyz_slide_prev), L);
gtk_box_append(GTK_BOX(nav), L->prev_btn);
L->next_btn = gtk_button_new_with_label(tr("net_slide_next"));
gtk_widget_add_css_class(L->next_btn, "btn-primary");
g_signal_connect(L->next_btn, "clicked", G_CALLBACK(fyz_slide_next), L);
gtk_box_append(GTK_BOX(nav), L->next_btn);
L->idx = 0;
fyz_slide_apply(L);
return page;
}
void fyz_lessons_apply_lang(void) {
fyz_ensure();
for (int y = 0; y < 4; y++) {
for (int i = 0; i < FYZ_N; i++)
fyz_slide_apply(&fyz_tracks[y].lessons[i]);
}
}
typedef struct {
const ChoiceQ *qs;
int n;
int n_opts;
FyzTrack *track;
int lesson_id;
GtkWidget *feedback;
GtkToggleButton **toggles;
GtkWidget **hints;
} FyzMcqCtx;
static void fyz_mcq_check(GtkButton *button, gpointer data) {
FyzMcqCtx *ctx = data;
int ok = 0;
(void)button;
for (int i = 0; i < ctx->n; i++) {
int active = -1;
for (int o = 0; o < ctx->n_opts; o++) {
GtkToggleButton *tb = ctx->toggles[i * ctx->n_opts + o];
gtk_widget_remove_css_class(GTK_WIDGET(tb), "ok");
gtk_widget_remove_css_class(GTK_WIDGET(tb), "wrong");
if (gtk_toggle_button_get_active(tb))
active = o;
}
if (active == ctx->qs[i].correct) {
ok++;
gtk_widget_add_css_class(
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "ok");
} else if (active >= 0) {
gtk_widget_add_css_class(
GTK_WIDGET(ctx->toggles[i * ctx->n_opts + active]), "wrong");
}
if (ctx->hints && ctx->hints[i])
gtk_widget_set_visible(ctx->hints[i], TRUE);
}
if (ok == ctx->n) {
set_feedback(ctx->feedback, TRUE, tr("feedback_ok"));
fyz_mark_done(ctx->track, ctx->lesson_id);
} else {
set_feedback(ctx->feedback, FALSE, tr("feedback_retry_short"));
}
}
GtkWidget *fyz_mcq_page(int year, int lesson_id, const char *back_page,
const char *title_key, const char *heading_key,
const ChoiceQ *qs, const char **hints, int n) {
GtkWidget *page;
GtkWidget *scroll;
GtkWidget *body;
GtkWidget *check;
GtkWidget *btns;
GtkWidget *head;
GtkWidget *intro;
FyzMcqCtx *ctx = g_new0(FyzMcqCtx, 1);
int n_opts = qs[0].n_options;
fyz_ensure();
if (year < 0 || year > 3)
year = 0;
page = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);
gtk_widget_set_margin_start(page, 32);
gtk_widget_set_margin_end(page, 32);
gtk_widget_set_margin_top(page, 24);
gtk_widget_set_margin_bottom(page, 24);
gtk_box_append(GTK_BOX(page), top_bar(back_page, title_key, NULL));
scroll = gtk_scrolled_window_new();
gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
gtk_widget_set_vexpand(scroll, TRUE);
gtk_widget_set_margin_top(scroll, 12);
gtk_box_append(GTK_BOX(page), scroll);
body = gtk_box_new(GTK_ORIENTATION_VERTICAL, 14);
gtk_widget_set_halign(body, GTK_ALIGN_FILL);
gtk_widget_set_hexpand(body, TRUE);
gtk_widget_set_margin_start(body, 4);
gtk_widget_set_margin_end(body, 4);
gtk_scrolled_window_set_child(GTK_SCROLLED_WINDOW(scroll), body);
head = gtk_label_new(NULL);
i18n_bind(head, heading_key, 0);
gtk_widget_set_halign(head, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(head), TRUE);
gtk_widget_set_margin_top(head, 4);
gtk_widget_add_css_class(head, "quiz-heading");
gtk_box_append(GTK_BOX(body), head);
intro = gtk_label_new(NULL);
i18n_bind(intro, "net_quiz_intro", 0);
gtk_widget_set_halign(intro, GTK_ALIGN_START);
gtk_label_set_wrap(GTK_LABEL(intro), TRUE);
gtk_widget_add_css_class(intro, "quiz-intro");
gtk_widget_set_margin_bottom(intro, 4);
gtk_box_append(GTK_BOX(body), intro);
ctx->qs = qs;
ctx->n = n;
ctx->n_opts = n_opts;
ctx->track = &fyz_tracks[year];
ctx->lesson_id = lesson_id;
ctx->toggles = g_new0(GtkToggleButton *, n * n_opts);
ctx->hints = g_new0(GtkWidget *, n);
for (int i = 0; i < n; i++) {
GtkWidget *card = mcq_append_question(body, i + 1, &qs[i],
&ctx->toggles[i * n_opts]);
ctx->hints[i] = meaning_add(card, hints[i]);
}
btns = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
gtk_widget_set_margin_top(btns, 8);
gtk_widget_set_margin_bottom(btns, 8);
gtk_box_append(GTK_BOX(body), btns);
check = gtk_button_new();
i18n_bind(check, "check", 1);
gtk_widget_add_css_class(check, "btn-primary");
gtk_widget_set_halign(check, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(btns), check);
g_signal_connect(check, "clicked", G_CALLBACK(fyz_mcq_check), ctx);
ctx->feedback = gtk_label_new("");
gtk_widget_set_halign(ctx->feedback, GTK_ALIGN_START);
gtk_box_append(GTK_BOX(body), ctx->feedback);
return page;
}

View file

@ -20,6 +20,7 @@
#define HW_SUBJ 2 /* index of "Technické vybavení" */ #define HW_SUBJ 2 /* index of "Technické vybavení" */
#define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */ #define CZ_SUBJ 3 /* index of "Český jazyk a literatura" */
#define ON_SUBJ 4 /* index of "Občanská nauka" */ #define ON_SUBJ 4 /* index of "Občanská nauka" */
#define PHY_SUBJ 7 /* index of "Fyzika" */
#define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */ #define SCI_SUBJ 8 /* index of "Základy Přírodopisných věd" */
#define CSS_FILE "style.css" #define CSS_FILE "style.css"
#define CHANGELOG_FILE "changelog.txt" #define CHANGELOG_FILE "changelog.txt"
@ -62,6 +63,10 @@ extern char *app_progress_lit3;
extern char *app_progress_lit4; extern char *app_progress_lit4;
extern char *app_progress_chem; extern char *app_progress_chem;
extern char *app_progress_bio; extern char *app_progress_bio;
extern char *app_progress_fyz;
extern char *app_progress_fyz2;
extern char *app_progress_fyz3;
extern char *app_progress_fyz4;
#define PROGRESS_DIR app_progress_dir #define PROGRESS_DIR app_progress_dir
#define PROGRESS_U1 app_progress_u1 #define PROGRESS_U1 app_progress_u1
#define PROGRESS_U2 app_progress_u2 #define PROGRESS_U2 app_progress_u2
@ -140,6 +145,7 @@ extern char *app_progress_bio;
#define ON4_LESSONS 16 #define ON4_LESSONS 16
#define LIT_N 8 #define LIT_N 8
#define SCI_N 8 #define SCI_N 8
#define FYZ_N 8
#define HW_SLIDES 3 #define HW_SLIDES 3
#define HW2_SLIDES 6 #define HW2_SLIDES 6
#define HW3_SLIDES 2 #define HW3_SLIDES 2
@ -950,6 +956,29 @@ void add_chem_pages(GtkStack *stack);
void add_bio_pages(GtkStack *stack); void add_bio_pages(GtkStack *stack);
extern NetLesson chem_lessons[SCI_N]; extern NetLesson chem_lessons[SCI_N];
extern NetLesson bio_lessons[SCI_N]; extern NetLesson bio_lessons[SCI_N];
void fyz_load_progress(void);
void fyz_rail_theme_reset(void);
void fyz_lessons_apply_lang(void);
void progress_for_fyz(ProgressSum *out);
GtkWidget *fyz_unit_page(int year, NetLesson *L, const char *title_key,
const char *sub_key, const NetSlide *slides,
guint n_slides);
GtkWidget *fyz_mcq_page(int year, int lesson_id, const char *back_page,
const char *title_key, const char *heading_key,
const ChoiceQ *qs, const char **hints, int n);
GtkWidget *build_fyzyears_page(void);
GtkWidget *build_fyzmap_page(void);
GtkWidget *build_fyz2map_page(void);
GtkWidget *build_fyz3map_page(void);
GtkWidget *build_fyz4map_page(void);
void add_fyz_pages(GtkStack *stack);
void add_fyz2_pages(GtkStack *stack);
void add_fyz3_pages(GtkStack *stack);
void add_fyz4_pages(GtkStack *stack);
extern NetLesson fyz_lessons[FYZ_N];
extern NetLesson fyz2_lessons[FYZ_N];
extern NetLesson fyz3_lessons[FYZ_N];
extern NetLesson fyz4_lessons[FYZ_N];
void hw_lessons_apply_lang(void); void hw_lessons_apply_lang(void);
GtkWidget *build_hwyears_page(void); GtkWidget *build_hwyears_page(void);
GtkWidget *build_hwmap_page(void); GtkWidget *build_hwmap_page(void);
@ -1039,6 +1068,8 @@ void draw_civics_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_science_icon(GtkDrawingArea *area, cairo_t *cr, void draw_science_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data);
void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr, void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data); int width, int height, gpointer data);
void draw_book_icon(GtkDrawingArea *area, cairo_t *cr, void draw_book_icon(GtkDrawingArea *area, cairo_t *cr,

View file

@ -90,12 +90,12 @@ const TrEntry tr_ui[] = {
"Cvičení, nápovědy a vokabeltraining. Aplikace si pamatuje, kde jste skončili.", "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."}, "Exercises, hints and vocabulary drills. The app remembers where you left off."},
{"welcome_feat1_link", "Začít procvičovat", "Start practicing"}, {"welcome_feat1_link", "Začít procvičovat", "Start practicing"},
{"welcome_feat2_title", "Šest otevřených předmětů", "Six open subjects"}, {"welcome_feat2_title", "Sedm otevřených předmětů", "Seven open subjects"},
{"welcome_feat2_body", {"welcome_feat2_body",
"Deutsch, sítě, hardware, čeština, občanská nauka a přírodní vědy. Ostatní předměty čekají na svůj obsah.", "Deutsch, sítě, hardware, čeština, občanská nauka, fyzika a přírodní vědy. Ostatní předměty čekají na svůj obsah.",
"German, networks, hardware, Czech, civics and natural sciences. The other subjects are still locked."}, "German, networks, hardware, Czech, civics, 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_feat2_link", "Jaké předměty jsou otevřené?", "Which subjects are open?"},
{"welcome_stat_value", "6", "6"}, {"welcome_stat_value", "7", "7"},
{"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"}, {"welcome_stat_label", "otevřené\npředměty", "open\nsubjects"},
{"continue", "Pokračuj", "Continue"}, {"continue", "Pokračuj", "Continue"},
{"roadmap_title", "Učební plán", "Learning path"}, {"roadmap_title", "Učební plán", "Learning path"},
@ -1396,6 +1396,183 @@ const TrEntry tr_ui[] = {
{"bio_ex8_title", "Kvíz: ekologie", "Quiz: ecology"}, {"bio_ex8_title", "Kvíz: ekologie", "Quiz: ecology"},
{"bio_quiz8_head", "Ověřte si vztahy v ekosystému", {"bio_quiz8_head", "Ověřte si vztahy v ekosystému",
"Check relationships in an ecosystem"}, "Check relationships in an ecosystem"},
{"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."},
{"fyz_unit1", "Fyzikální veličiny", "Physical quantities"},
{"fyz_unit1_sub", "SI, skalár a vektor, chyba měření.",
"SI, scalar and vector, measurement error."},
{"fyz_ex1_title", "Kvíz: veličiny", "Quiz: quantities"},
{"fyz_quiz1_head", "Ověřte si jednotky a chyby", "Check units and errors"},
{"fyz_unit2", "Kinematika", "Kinematics"},
{"fyz_unit2_sub", "Dráha, rychlost a zrychlení.",
"Distance, speed and acceleration."},
{"fyz_ex2_title", "Kvíz: kinematika", "Quiz: kinematics"},
{"fyz_quiz2_head", "Ověřte si pohyb", "Check motion"},
{"fyz_unit3", "Newtonovy zákony", "Newton's laws"},
{"fyz_unit3_sub", "Setrvačnost, síla a hybnost.",
"Inertia, force and momentum."},
{"fyz_ex3_title", "Kvíz: Newton", "Quiz: Newton"},
{"fyz_quiz3_head", "Ověřte si zákony síly", "Check the laws of force"},
{"fyz_unit4", "Síly v běžných situacích", "Everyday forces"},
{"fyz_unit4_sub", "Tíha, tření, tlak a výslednice.",
"Weight, friction, pressure and the resultant."},
{"fyz_ex4_title", "Kvíz: síly", "Quiz: forces"},
{"fyz_quiz4_head", "Ověřte si tíhu a tlak", "Check weight and pressure"},
{"fyz_unit5", "Práce, energie a výkon", "Work, energy and power"},
{"fyz_unit5_sub", "Mechanická práce, energie a účinnost.",
"Mechanical work, energy and efficiency."},
{"fyz_ex5_title", "Kvíz: energie", "Quiz: energy"},
{"fyz_quiz5_head", "Ověřte si práci a výkon", "Check work and power"},
{"fyz_unit6", "Gravitační pole", "Gravitational field"},
{"fyz_unit6_sub", "Newtonův gravitační zákon a kosmické rychlosti.",
"Newton's law of gravitation and cosmic speeds."},
{"fyz_ex6_title", "Kvíz: gravitace", "Quiz: gravity"},
{"fyz_quiz6_head", "Ověřte si gravitační pole", "Check the gravitational field"},
{"fyz_unit7", "Tuhé těleso", "Rigid body"},
{"fyz_unit7_sub", "Moment síly, páka a těžiště.",
"Moment of a force, the lever and the centre of gravity."},
{"fyz_ex7_title", "Kvíz: tuhé těleso", "Quiz: the rigid body"},
{"fyz_quiz7_head", "Ověřte si otáčení", "Check rotation"},
{"fyz_unit8", "Kapaliny a plyny", "Liquids and gases"},
{"fyz_unit8_sub", "Hydrostatika, Archimédés a Bernoulli.",
"Hydrostatics, Archimedes and Bernoulli."},
{"fyz_ex8_title", "Kvíz: tekutiny", "Quiz: fluids"},
{"fyz_quiz8_head", "Ověřte si tlak v kapalině", "Check pressure in a liquid"},
{"fyz2_unit1", "Částicová stavba", "Particle structure"},
{"fyz2_unit1_sub", "Brownův pohyb, skupenství a vnitřní energie.",
"Brownian motion, states of matter and internal energy."},
{"fyz2_ex1_title", "Kvíz: částice", "Quiz: particles"},
{"fyz2_quiz1_head", "Ověřte si stavbu látky", "Check the structure of matter"},
{"fyz2_unit2", "Teplota a teplo", "Temperature and heat"},
{"fyz2_unit2_sub", "Kelvin, měrná tepelná kapacita a šíření tepla.",
"The kelvin, specific heat capacity and heat transfer."},
{"fyz2_ex2_title", "Kvíz: teplo", "Quiz: heat"},
{"fyz2_quiz2_head", "Ověřte si teplotu a teplo", "Check temperature and heat"},
{"fyz2_unit3", "Kalorimetrie", "Calorimetry"},
{"fyz2_unit3_sub", "Směšování, skupenské teplo a první zákon.",
"Mixing, latent heat and the first law."},
{"fyz2_ex3_title", "Kvíz: kalorimetrie", "Quiz: calorimetry"},
{"fyz2_quiz3_head", "Ověřte si tepelnou výměnu", "Check heat exchange"},
{"fyz2_unit4", "Ideální plyn", "Ideal gas"},
{"fyz2_unit4_sub", "Stavová rovnice a děje.",
"The equation of state and processes."},
{"fyz2_ex4_title", "Kvíz: plyn", "Quiz: gases"},
{"fyz2_quiz4_head", "Ověřte si stavovou rovnici", "Check the equation of state"},
{"fyz2_unit5", "Pevné látky a kapaliny", "Solids and liquids"},
{"fyz2_unit5_sub", "Hooke, roztažnost a povrchové napětí.",
"Hooke, expansion and surface tension."},
{"fyz2_ex5_title", "Kvíz: látky", "Quiz: materials"},
{"fyz2_quiz5_head", "Ověřte si deformaci a povrch", "Check deformation and surfaces"},
{"fyz2_unit6", "Změny skupenství", "Changes of state"},
{"fyz2_unit6_sub", "Tání, var a vlhkost vzduchu.",
"Melting, boiling and humidity."},
{"fyz2_ex6_title", "Kvíz: skupenství", "Quiz: states of matter"},
{"fyz2_quiz6_head", "Ověřte si tání a var", "Check melting and boiling"},
{"fyz2_unit7", "Mechanické kmitání", "Mechanical oscillations"},
{"fyz2_unit7_sub", "Pružina, kyvadlo a rezonance.",
"The spring, the pendulum and resonance."},
{"fyz2_ex7_title", "Kvíz: kmity", "Quiz: oscillations"},
{"fyz2_quiz7_head", "Ověřte si oscilátor", "Check the oscillator"},
{"fyz2_unit8", "Vlnění a zvuk", "Waves and sound"},
{"fyz2_unit8_sub", "Vlnová délka, frekvence a slyšitelný zvuk.",
"Wavelength, frequency and audible sound."},
{"fyz2_ex8_title", "Kvíz: zvuk", "Quiz: sound"},
{"fyz2_quiz8_head", "Ověřte si vlnění", "Check waves"},
{"fyz3_unit1", "Elektrický náboj a pole", "Electric charge and field"},
{"fyz3_unit1_sub", "Coulombův zákon a napětí.",
"Coulomb's law and voltage."},
{"fyz3_ex1_title", "Kvíz: náboj", "Quiz: charge"},
{"fyz3_quiz1_head", "Ověřte si elektrické pole", "Check the electric field"},
{"fyz3_unit2", "Proud v kovech", "Current in metals"},
{"fyz3_unit2_sub", "Ohmův zákon a odpor.",
"Ohm's law and resistance."},
{"fyz3_ex2_title", "Kvíz: proud", "Quiz: current"},
{"fyz3_quiz2_head", "Ověřte si Ohmův zákon", "Check Ohm's law"},
{"fyz3_unit3", "Elektrické obvody", "Electric circuits"},
{"fyz3_unit3_sub", "Sériové a paralelní spojení, výkon.",
"Series and parallel connection, power."},
{"fyz3_ex3_title", "Kvíz: obvody", "Quiz: circuits"},
{"fyz3_quiz3_head", "Ověřte si zapojení", "Check the connections"},
{"fyz3_unit4", "Polovodiče, kapaliny a plyny",
"Semiconductors, liquids and gases"},
{"fyz3_unit4_sub", "Dioda, elektrolýza a výboj.",
"The diode, electrolysis and discharge."},
{"fyz3_ex4_title", "Kvíz: vodivost", "Quiz: conduction"},
{"fyz3_quiz4_head", "Ověřte si polovodiče a ionty",
"Check semiconductors and ions"},
{"fyz3_unit5", "Magnetické pole", "Magnetic field"},
{"fyz3_unit5_sub", "Indukční čáry a síla na vodič.",
"Field lines and the force on a wire."},
{"fyz3_ex5_title", "Kvíz: magnetismus", "Quiz: magnetism"},
{"fyz3_quiz5_head", "Ověřte si magnetické pole", "Check the magnetic field"},
{"fyz3_unit6", "Elektromagnetická indukce", "Electromagnetic induction"},
{"fyz3_unit6_sub", "Faraday, Lenz a transformátor.",
"Faraday, Lenz and the transformer."},
{"fyz3_ex6_title", "Kvíz: indukce", "Quiz: induction"},
{"fyz3_quiz6_head", "Ověřte si indukci", "Check induction"},
{"fyz3_unit7", "Střídavý proud", "Alternating current"},
{"fyz3_unit7_sub", "Efektivní hodnota a fáze.",
"The rms value and phase."},
{"fyz3_ex7_title", "Kvíz: střídavý proud", "Quiz: alternating current"},
{"fyz3_quiz7_head", "Ověřte si síť 230 V", "Check the 230 V grid"},
{"fyz3_unit8", "Elektromagnetické vlnění", "Electromagnetic waves"},
{"fyz3_unit8_sub", "Rychlost světla a spektrum.",
"The speed of light and the spectrum."},
{"fyz3_ex8_title", "Kvíz: spektrum", "Quiz: the spectrum"},
{"fyz3_quiz8_head", "Ověřte si elektromagnetickou vlnu",
"Check the electromagnetic wave"},
{"fyz4_unit1", "Vlnová optika", "Wave optics"},
{"fyz4_unit1_sub", "Odraz, lom, interference a ohyb.",
"Reflection, refraction, interference and diffraction."},
{"fyz4_ex1_title", "Kvíz: optika", "Quiz: optics"},
{"fyz4_quiz1_head", "Ověřte si lom a odraz", "Check refraction and reflection"},
{"fyz4_unit2", "Zobrazování", "Image formation"},
{"fyz4_unit2_sub", "Zrcadla, čočky a oko.",
"Mirrors, lenses and the eye."},
{"fyz4_ex2_title", "Kvíz: čočky", "Quiz: lenses"},
{"fyz4_quiz2_head", "Ověřte si zobrazování", "Check image formation"},
{"fyz4_unit3", "Speciální relativita", "Special relativity"},
{"fyz4_unit3_sub", "Postuláty, dilatace a klidová energie.",
"The postulates, dilation and rest energy."},
{"fyz4_ex3_title", "Kvíz: relativita", "Quiz: relativity"},
{"fyz4_quiz3_head", "Ověřte si dva postuláty", "Check the two postulates"},
{"fyz4_unit4", "Kvantové jevy", "Quantum phenomena"},
{"fyz4_unit4_sub", "Foton, fotoefekt a de Broglie.",
"The photon, the photoelectric effect and de Broglie."},
{"fyz4_ex4_title", "Kvíz: kvanta", "Quiz: quanta"},
{"fyz4_quiz4_head", "Ověřte si foton", "Check the photon"},
{"fyz4_unit5", "Stavba atomu", "Structure of the atom"},
{"fyz4_unit5_sub", "Rutherford, Bohr a spektra.",
"Rutherford, Bohr and spectra."},
{"fyz4_ex5_title", "Kvíz: atom", "Quiz: the atom"},
{"fyz4_quiz5_head", "Ověřte si hladiny", "Check the energy levels"},
{"fyz4_unit6", "Atomové jádro", "The atomic nucleus"},
{"fyz4_unit6_sub", "Nukleony, vazebná energie a radioaktivita.",
"Nucleons, binding energy and radioactivity."},
{"fyz4_ex6_title", "Kvíz: jádro", "Quiz: the nucleus"},
{"fyz4_quiz6_head", "Ověřte si rozpad", "Check radioactive decay"},
{"fyz4_unit7", "Jaderné reakce", "Nuclear reactions"},
{"fyz4_unit7_sub", "Štěpení, reaktor a fúze.",
"Fission, the reactor and fusion."},
{"fyz4_ex7_title", "Kvíz: reakce", "Quiz: reactions"},
{"fyz4_quiz7_head", "Ověřte si štěpení a fúzi", "Check fission and fusion"},
{"fyz4_unit8", "Astrofyzika", "Astrophysics"},
{"fyz4_unit8_sub", "Hvězdy, galaxie a rozpínání vesmíru.",
"Stars, galaxies and the expanding universe."},
{"fyz4_ex8_title", "Kvíz: vesmír", "Quiz: the universe"},
{"fyz4_quiz8_head", "Ověřte si hvězdy a galaxie", "Check stars and galaxies"},
{"czech_sub", "Vyberte oblast a začněte procvičovat.", {"czech_sub", "Vyberte oblast a začněte procvičovat.",
"Pick an area and start practicing."}, "Pick an area and start practicing."},
{"Literatura", "Literatura", "Literature"}, {"Literatura", "Literatura", "Literature"},
@ -2425,6 +2602,7 @@ void on3_lessons_apply_lang(void);
void on4_lessons_apply_lang(void); void on4_lessons_apply_lang(void);
void lit_lessons_apply_lang(void); void lit_lessons_apply_lang(void);
void sci_lessons_apply_lang(void); void sci_lessons_apply_lang(void);
void fyz_lessons_apply_lang(void);
void net_rail_theme_reset(void); void net_rail_theme_reset(void);
void apply_language(void) { void apply_language(void) {
@ -2445,5 +2623,6 @@ void apply_language(void) {
on4_lessons_apply_lang(); on4_lessons_apply_lang();
lit_lessons_apply_lang(); lit_lessons_apply_lang();
sci_lessons_apply_lang(); sci_lessons_apply_lang();
fyz_lessons_apply_lang();
search_apply_lang(); search_apply_lang();
} }

View file

@ -228,6 +228,39 @@ void draw_science_icon(GtkDrawingArea *area, cairo_t *cr,
cairo_stroke(cr); cairo_stroke(cr);
} }
/* Atom glyph for Fyzika: nucleus and two orbits. */
void draw_physics_icon(GtkDrawingArea *area, cairo_t *cr,
int width, int height, gpointer data) {
const double s = MIN(width, height);
const double cx = width / 2.0;
const double cy = height / 2.0;
const Rgb c = mix_rgb(color_from_hex(app_theme.on_accent),
color_from_hex(app_theme.accent2), 0.22);
(void)area;
(void)data;
cairo_set_source_rgb(cr, c.r, c.g, c.b);
cairo_set_line_width(cr, MAX(1.6, s * 0.06));
cairo_arc(cr, cx, cy, s * 0.10, 0, 2 * G_PI);
cairo_fill(cr);
cairo_save(cr);
cairo_translate(cr, cx, cy);
cairo_scale(cr, 1.0, 0.38);
cairo_arc(cr, 0, 0, s * 0.34, 0, 2 * G_PI);
cairo_restore(cr);
cairo_stroke(cr);
cairo_save(cr);
cairo_translate(cr, cx, cy);
cairo_rotate(cr, 1.05);
cairo_scale(cr, 1.0, 0.38);
cairo_arc(cr, 0, 0, s * 0.34, 0, 2 * G_PI);
cairo_restore(cr);
cairo_stroke(cr);
}
/* Open book glyph, painted in the given colour. */ /* Open book glyph, painted in the given colour. */
static void book_paint(cairo_t *cr, int width, int height, Rgb c) { static void book_paint(cairo_t *cr, int width, int height, Rgb c) {
const double w = width; const double w = width;

View file

@ -390,6 +390,10 @@ static gboolean run_smoke_test(gpointer data) {
"czechmap", "czechmap",
"lityears", "litmap", "lit2map", "lit3map", "lit4map", "lityears", "litmap", "lit2map", "lit3map", "lit4map",
"scimap", "chemmap", "biomap", "scimap", "chemmap", "biomap",
"fyzyears", "fyzmap", "fyz2map", "fyz3map", "fyz4map",
"fyzunit1", "fyzex1", "fyzunit8", "fyzex8",
"fyz2unit1", "fyz2ex1", "fyz3unit1", "fyz4unit1",
"fyz4unit8", "fyz4ex8",
"chemunit1", "chemex1", "chemunit8", "chemex8", "chemunit1", "chemex1", "chemunit8", "chemex8",
"biounit1", "bioex1", "biounit8", "bioex8", "biounit1", "bioex1", "biounit8", "bioex8",
"litunit1", "litex1", "litunit8", "litex8", "litunit1", "litex1", "litunit8", "litex8",
@ -528,6 +532,15 @@ void activate(GtkApplication *app, gpointer user_data) {
add_on3_pages(main_stack); add_on3_pages(main_stack);
gtk_stack_add_named(main_stack, build_on4map_page(), "on4map"); gtk_stack_add_named(main_stack, build_on4map_page(), "on4map");
add_on4_pages(main_stack); add_on4_pages(main_stack);
gtk_stack_add_named(main_stack, build_fyzyears_page(), "fyzyears");
gtk_stack_add_named(main_stack, build_fyzmap_page(), "fyzmap");
add_fyz_pages(main_stack);
gtk_stack_add_named(main_stack, build_fyz2map_page(), "fyz2map");
add_fyz2_pages(main_stack);
gtk_stack_add_named(main_stack, build_fyz3map_page(), "fyz3map");
add_fyz3_pages(main_stack);
gtk_stack_add_named(main_stack, build_fyz4map_page(), "fyz4map");
add_fyz4_pages(main_stack);
gtk_stack_add_named(main_stack, build_scimap_page(), "scimap"); gtk_stack_add_named(main_stack, build_scimap_page(), "scimap");
gtk_stack_add_named(main_stack, build_chemmap_page(), "chemmap"); gtk_stack_add_named(main_stack, build_chemmap_page(), "chemmap");
add_chem_pages(main_stack); add_chem_pages(main_stack);

View file

@ -60,6 +60,7 @@ void load_progress(void) {
on4_load_progress(); on4_load_progress();
lit_load_progress(); lit_load_progress();
sci_load_progress(); sci_load_progress();
fyz_load_progress();
} }
/* Redraw the exercise rails of every unit that already has a page. */ /* Redraw the exercise rails of every unit that already has a page. */

View file

@ -23,7 +23,7 @@ static GPtrArray *search_items;
static gboolean subject_open(int i) { static gboolean subject_open(int i) {
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ || i == SCI_SUBJ; || i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
} }
static const char *subject_page(int i) { static const char *subject_page(int i) {
@ -37,6 +37,8 @@ static const char *subject_page(int i) {
return "czechmap"; return "czechmap";
if (i == ON_SUBJ) if (i == ON_SUBJ)
return "onyears"; return "onyears";
if (i == PHY_SUBJ)
return "fyzyears";
if (i == SCI_SUBJ) if (i == SCI_SUBJ)
return "scimap"; return "scimap";
return "subjects"; return "subjects";
@ -273,6 +275,38 @@ static void catalog_rebuild(void) {
g_snprintf(page, sizeof(page), "lit4ex%d", i); g_snprintf(page, sizeof(page), "lit4ex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz"); catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
} }
catalog_add_key("fyz_year1", "search_lesson", "fyzmap", FALSE, 1, "fyzika rocnik");
catalog_add_key("fyz_year2", "search_lesson", "fyz2map", FALSE, 1, "fyzika rocnik");
catalog_add_key("fyz_year3", "search_lesson", "fyz3map", FALSE, 1, "fyzika rocnik");
catalog_add_key("fyz_year4", "search_lesson", "fyz4map", FALSE, 1, "fyzika rocnik");
for (i = 1; i <= FYZ_N; i++) {
g_snprintf(key, sizeof(key), "fyz_unit%d", i);
g_snprintf(page, sizeof(page), "fyzunit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika");
g_snprintf(page, sizeof(page), "fyzex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
for (i = 1; i <= FYZ_N; i++) {
g_snprintf(key, sizeof(key), "fyz2_unit%d", i);
g_snprintf(page, sizeof(page), "fyz2unit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika");
g_snprintf(page, sizeof(page), "fyz2ex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
for (i = 1; i <= FYZ_N; i++) {
g_snprintf(key, sizeof(key), "fyz3_unit%d", i);
g_snprintf(page, sizeof(page), "fyz3unit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika");
g_snprintf(page, sizeof(page), "fyz3ex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
for (i = 1; i <= FYZ_N; i++) {
g_snprintf(key, sizeof(key), "fyz4_unit%d", i);
g_snprintf(page, sizeof(page), "fyz4unit%d", i);
catalog_add_key(key, "search_lesson", page, FALSE, 2, "fyzika");
g_snprintf(page, sizeof(page), "fyz4ex%d", i);
catalog_add(tr(key), tr("search_exercise"), page, FALSE, 3, "kviz quiz");
}
catalog_add_key("Chemie", "search_lesson", "chemmap", FALSE, 1, "chemie"); catalog_add_key("Chemie", "search_lesson", "chemmap", FALSE, 1, "chemie");
catalog_add_key("Biologie", "search_lesson", "biomap", FALSE, 1, "biologie"); catalog_add_key("Biologie", "search_lesson", "biomap", FALSE, 1, "biologie");
for (i = 1; i <= SCI_N; i++) { for (i = 1; i <= SCI_N; i++) {

View file

@ -77,6 +77,8 @@ void refresh_stats_ui(void) {
} }
else if (s == SCI_SUBJ) else if (s == SCI_SUBJ)
progress_for_sci(&all); progress_for_sci(&all);
else if (s == PHY_SUBJ)
progress_for_fyz(&all);
else else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &all); progress_for_units(sub_unit_start[s], sub_unit_count[s], &all);
} }
@ -107,7 +109,8 @@ void refresh_stats_ui(void) {
ProgressSum sp = {0}; ProgressSum sp = {0};
gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ) gboolean has_content = (s == NET_SUBJ) || (s == HW_SUBJ)
|| (s == ON_SUBJ) || (s == CZ_SUBJ) || (s == ON_SUBJ) || (s == CZ_SUBJ)
|| (s == SCI_SUBJ) || sub_unit_count[s] > 0; || (s == SCI_SUBJ) || (s == PHY_SUBJ)
|| sub_unit_count[s] > 0;
if (!count || !bar) if (!count || !bar)
continue; continue;
@ -123,6 +126,8 @@ void refresh_stats_ui(void) {
} }
else if (s == SCI_SUBJ) else if (s == SCI_SUBJ)
progress_for_sci(&sp); progress_for_sci(&sp);
else if (s == PHY_SUBJ)
progress_for_fyz(&sp);
else else
progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp); progress_for_units(sub_unit_start[s], sub_unit_count[s], &sp);
if (has_content) { if (has_content) {
@ -393,7 +398,8 @@ GtkWidget *build_stats_page(void) {
GtkWidget *bar; GtkWidget *bar;
gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ) gboolean has_units = (s == NET_SUBJ) || (s == HW_SUBJ)
|| (s == CZ_SUBJ) || (s == ON_SUBJ) || (s == CZ_SUBJ) || (s == ON_SUBJ)
|| (s == SCI_SUBJ) || sub_unit_count[s] > 0; || (s == SCI_SUBJ) || (s == PHY_SUBJ)
|| sub_unit_count[s] > 0;
gboolean has_deutsch_units = sub_unit_count[s] > 0; gboolean has_deutsch_units = sub_unit_count[s] > 0;
subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0); subject = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0);

View file

@ -5,7 +5,8 @@
/* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */
/* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ /* Index 0 (Deutsch), NET_SUBJ (networks), HW_SUBJ (hardware), CZ_SUBJ
* (Czech), ON_SUBJ (civics) and SCI_SUBJ (natural sciences) are unlocked. */ * (Czech), ON_SUBJ (civics), PHY_SUBJ (physics) and SCI_SUBJ
* (natural sciences) are unlocked. */
const char *sub_keys[NUM_SUBJECTS] = { const char *sub_keys[NUM_SUBJECTS] = {
"Deutsch", "Deutsch",
"Správa počítačových sítí", "Správa počítačových sítí",
@ -154,7 +155,7 @@ void draw_czech_flag(GtkDrawingArea *area, cairo_t *cr,
static gboolean subject_is_open(int i) { static gboolean subject_is_open(int i) {
return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ return i == 0 || i == NET_SUBJ || i == HW_SUBJ || i == CZ_SUBJ
|| i == ON_SUBJ || i == SCI_SUBJ; || i == ON_SUBJ || i == PHY_SUBJ || i == SCI_SUBJ;
} }
static GtkWidget *subject_icon(int i) { static GtkWidget *subject_icon(int i) {
@ -177,6 +178,9 @@ static GtkWidget *subject_icon(int i) {
else if (i == ON_SUBJ) else if (i == ON_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_civics_icon, NULL, NULL); draw_civics_icon, NULL, NULL);
else if (i == PHY_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_physics_icon, NULL, NULL);
else if (i == SCI_SUBJ) else if (i == SCI_SUBJ)
gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area), gtk_drawing_area_set_draw_func(GTK_DRAWING_AREA(area),
draw_science_icon, NULL, NULL); draw_science_icon, NULL, NULL);
@ -196,6 +200,8 @@ static const char *subject_target(int i) {
return "czechmap"; return "czechmap";
if (i == ON_SUBJ) if (i == ON_SUBJ)
return "onyears"; return "onyears";
if (i == PHY_SUBJ)
return "fyzyears";
if (i == SCI_SUBJ) if (i == SCI_SUBJ)
return "scimap"; return "scimap";
return NULL; return NULL;

View file

@ -433,5 +433,6 @@ void apply_theme(void) {
czech_rail_theme_reset(); czech_rail_theme_reset();
lit_rail_theme_reset(); lit_rail_theme_reset();
sci_rail_theme_reset(); sci_rail_theme_reset();
fyz_rail_theme_reset();
book_rail_theme_reset(); book_rail_theme_reset();
} }

View file

@ -32,6 +32,10 @@ char *app_progress_lit3;
char *app_progress_lit4; char *app_progress_lit4;
char *app_progress_chem; char *app_progress_chem;
char *app_progress_bio; char *app_progress_bio;
char *app_progress_fyz;
char *app_progress_fyz2;
char *app_progress_fyz3;
char *app_progress_fyz4;
static char *dup_dirname(const char *path) { static char *dup_dirname(const char *path) {
char *tmp = g_strdup(path); char *tmp = g_strdup(path);
@ -180,6 +184,10 @@ static void set_progress_paths(const char *write_root) {
g_free(app_progress_lit4); g_free(app_progress_lit4);
g_free(app_progress_chem); g_free(app_progress_chem);
g_free(app_progress_bio); g_free(app_progress_bio);
g_free(app_progress_fyz);
g_free(app_progress_fyz2);
g_free(app_progress_fyz3);
g_free(app_progress_fyz4);
app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL); app_progress_dir = g_build_filename(write_root, PROGRESS_DIR_NAME, NULL);
app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL); app_progress_u1 = g_build_filename(app_progress_dir, "unit1.conf", NULL);
@ -200,6 +208,10 @@ static void set_progress_paths(const char *write_root) {
app_progress_lit4 = g_build_filename(app_progress_dir, "lit4.conf", NULL); app_progress_lit4 = g_build_filename(app_progress_dir, "lit4.conf", NULL);
app_progress_chem = g_build_filename(app_progress_dir, "chem.conf", NULL); app_progress_chem = g_build_filename(app_progress_dir, "chem.conf", NULL);
app_progress_bio = g_build_filename(app_progress_dir, "bio.conf", NULL); app_progress_bio = g_build_filename(app_progress_dir, "bio.conf", NULL);
app_progress_fyz = g_build_filename(app_progress_dir, "fyz.conf", NULL);
app_progress_fyz2 = g_build_filename(app_progress_dir, "fyz2.conf", NULL);
app_progress_fyz3 = g_build_filename(app_progress_dir, "fyz3.conf", NULL);
app_progress_fyz4 = g_build_filename(app_progress_dir, "fyz4.conf", NULL);
} }
/* Resolve read-only asset root, chdir there, and point progress at a writable /* Resolve read-only asset root, chdir there, and point progress at a writable