From 3ffe01af1da4689c80cc70739af5fbaa28d831c2 Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Thu, 8 Oct 2026 10:23:29 +0200 Subject: [PATCH] Show study slides as one column and fill in the empty lessons. Co-authored-by: Cursor --- .../java/org/maturita/maturita/ui/Screens.kt | 26 +++- ios/Graduately/UI/Screens.swift | 131 ++++++++++++------ src/hw.c | 69 +++++---- src/net.c | 38 ++--- 4 files changed, 174 insertions(+), 90 deletions(-) diff --git a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt index d019ca1..16a60ce 100644 --- a/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt +++ b/android/app/src/main/java/org/maturita/maturita/ui/Screens.kt @@ -1034,8 +1034,30 @@ fun SlidesScreen( } } } - slide.strs("lines").forEach { line -> - Text(line, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.padding(vertical = 6.dp)) + slide.strs("lines").forEachIndexed { index, line -> + val numbered = Regex("""^(\d+)\.\s+(.*)$""").find(line) + val badge = numbered?.groupValues?.get(1) ?: "${index + 1}" + val text = numbered?.groupValues?.get(2) ?: line + Row( + Modifier + .fillMaxWidth() + .padding(vertical = 5.dp) + .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f), androidx.compose.foundation.shape.RoundedCornerShape(16.dp)) + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.Top, + ) { + Box( + Modifier + .padding(top = 2.dp) + .size(28.dp) + .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.14f), CircleShape), + contentAlignment = Alignment.Center, + ) { + Text(badge, style = MaterialTheme.typography.labelLarge, color = MaterialTheme.colorScheme.primary) + } + Spacer(Modifier.width(12.dp)) + Text(text, style = MaterialTheme.typography.bodyLarge, modifier = Modifier.weight(1f)) + } } Spacer(Modifier.height(24.dp)) } diff --git a/ios/Graduately/UI/Screens.swift b/ios/Graduately/UI/Screens.swift index 56b1f43..1a87703 100644 --- a/ios/Graduately/UI/Screens.swift +++ b/ios/Graduately/UI/Screens.swift @@ -1519,84 +1519,129 @@ struct SlidesScreen: View { } private func slidePage(_ slide: J) -> some View { - #if os(macOS) - GeometryReader { geo in - ScrollView { - slideStack(slide) - .padding(.horizontal, MacChrome.pageInset) - .padding(.vertical, 28) - .frame(maxWidth: .infinity, minHeight: geo.size.height, alignment: .center) - } - } - #else ScrollView { slideStack(slide) - .padding() - .padding(.bottom, 24) - .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal, MacChrome.pageInset) + .padding(.top, 22) + .padding(.bottom, 28) + .frame(maxWidth: 860) + .frame(maxWidth: .infinity, alignment: .top) } - #endif } private func slideStack(_ slide: J) -> some View { - VStack(alignment: .leading, spacing: 16) { + VStack(alignment: .leading, spacing: 18) { Text(slide.str("kicker")) #if os(macOS) .font(.subheadline.weight(.semibold)) #else .font(.caption.weight(.semibold)) #endif - .foregroundStyle(.secondary) + .foregroundStyle(Color.accentColor) Text(slide.str("title")) #if os(macOS) .font(.title.bold()) #else .font(.title2.bold()) #endif + .fixedSize(horizontal: false, vertical: true) if let tip = slide.strOrNull("tip") { - GlassCard { - Label(tip, systemImage: "lightbulb.fill") + HStack(alignment: .top, spacing: 12) { + Image(systemName: "lightbulb.fill") + .foregroundStyle(Color.accentColor) + .padding(.top, 2) + Text(tip) + #if os(macOS) + .font(.body) + #else + .font(.subheadline) + #endif + .fixedSize(horizontal: false, vertical: true) + .frame(maxWidth: .infinity, alignment: .leading) } + .padding(16) + .frame(maxWidth: .infinity, alignment: .leading) + .background(Color.accentColor.opacity(0.12), in: RoundedRectangle(cornerRadius: 16, style: .continuous)) } slideLines(slide.strs("lines")) } .frame(maxWidth: .infinity, alignment: .leading) } - @ViewBuilder private func slideLines(_ lines: [String]) -> some View { - #if os(macOS) - if lines.count >= 3 { - LazyVGrid( - columns: [ - GridItem(.flexible(), spacing: 28, alignment: .topLeading), - GridItem(.flexible(), spacing: 28, alignment: .topLeading), - ], - alignment: .leading, - spacing: 12 - ) { - ForEach(Array(lines.enumerated()), id: \.offset) { _, line in - Text(line) + let points = studyPoints(lines) + return VStack(alignment: .leading, spacing: 10) { + ForEach(Array(points.enumerated()), id: \.offset) { _, point in + HStack(alignment: .top, spacing: 14) { + Text(point.0) + .font(.callout.weight(.bold)) + .foregroundStyle(Color.accentColor) + .frame(minWidth: 28, minHeight: 28) + .background(Color.accentColor.opacity(0.14), in: Circle()) + Text(point.1) + #if os(macOS) .font(.title3) + #else + .font(.body) + #endif .fixedSize(horizontal: false, vertical: true) .frame(maxWidth: .infinity, alignment: .leading) + .padding(.top, 3) } - } - } else { - VStack(alignment: .leading, spacing: 12) { - ForEach(Array(lines.enumerated()), id: \.offset) { _, line in - Text(line) - .font(.title3) - .fixedSize(horizontal: false, vertical: true) + .padding(.horizontal, 16) + .padding(.vertical, 14) + .frame(maxWidth: .infinity, alignment: .leading) + .background { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .fill(Color.primary.opacity(0.045)) + } + .overlay { + RoundedRectangle(cornerRadius: 16, style: .continuous) + .strokeBorder(Color.primary.opacity(0.08), lineWidth: 1) } } } - #else - ForEach(Array(lines.enumerated()), id: \.offset) { _, line in - Text(line) - .padding(.vertical, 4) + } + + /// Lines that end in the middle of a sentence stay one point. A leading + /// "1. " is the point number, not part of the sentence. + private func studyPoints(_ lines: [String]) -> [(String, String)] { + var chunks: [String] = [] + var buffer = "" + func flush() { + let text = buffer.trimmingCharacters(in: .whitespacesAndNewlines) + if !text.isEmpty { chunks.append(text) } + buffer = "" } - #endif + for line in lines { + let trimmed = line.trimmingCharacters(in: .whitespacesAndNewlines) + if trimmed.isEmpty { continue } + if buffer.isEmpty { + buffer = trimmed + } else if continuesStudyPoint(buffer) { + buffer += " " + trimmed + } else { + flush() + buffer = trimmed + } + } + flush() + return chunks.enumerated().map { index, chunk in + let pattern = #"^(\d+)\.\s+(.*)$"# + if let regex = try? NSRegularExpression(pattern: pattern), + let match = regex.firstMatch(in: chunk, range: NSRange(chunk.startIndex..., in: chunk)), + let number = Range(match.range(at: 1), in: chunk), + let rest = Range(match.range(at: 2), in: chunk), + !continuesStudyPoint(String(chunk[rest])) { + return (String(chunk[number]), String(chunk[rest])) + } + return (String(index + 1), chunk) + } + } + + private func continuesStudyPoint(_ text: String) -> Bool { + guard let last = text.last else { return false } + return last == "," || last == ":" || last == ";" || last == "–" || last == "-" || last == "(" } } diff --git a/src/hw.c b/src/hw.c index 45760e6..2ed989b 100644 --- a/src/hw.c +++ b/src/hw.c @@ -877,64 +877,79 @@ GtkWidget *build_hw_unit2_page(void) { static const NetSlide slides[HW2_SLIDES] = { { "1 / 6 • Přehled", "Historie počítačů", - "Generace se dělí podle klíčové technologie své doby.", + "Generace se dělí podle součástky, která v té době počítala: relé, elektronka, tranzistor, integrovaný obvod a nakonec mikroprocesor.", { - "1. Předchůdci – do 30. let 19. století", - "2. Nultá generace – 1938–1944", - "3. První generace – 1944–1955", - "4. Druhá generace – 1955–1964", - "5. Třetí generace – 1964–1971", - "Další snímky shrnují znaky jednotlivých generací.", + "Předchůdci do 19. století jsou mechanické pomůcky. Pascaline sčítala ozubenými koly, Babbage navrhl analytický stroj a Ada Lovelace k němu popsala postup výpočtu. Elektronika ještě chyběla.", + "Nultá generace, zhruba 1938–1944, spíná elektromagnetická relé. Stroje jsou kusové a často vojenské.", + "První generace, 1944–1955, bere elektronku. Počítač zabere sál a program se dává děrnými štítky.", + "Druhá generace, 1955–1964, nahrazuje elektronku tranzistorem. Objevují se vyšší programovací jazyky.", + "Třetí generace, 1964–1971, staví na integrovaném obvodu. Vznikají rodiny počítačů a operační systémy.", + "Čtvrtá generace od roku 1971 staví na mikroprocesoru. Počítač se vejde na stůl a později do kapsy.", NULL, }, }, { "2 / 6 • 0. generace", "Počítače 0. generace (1938–1944)", - "Tip: počítačů bylo málo a často sloužily armádě.", + "Tip: počítačů byly jednotky kusů a často sloužily armádě. Základem je relé, ne elektronka.", { - "Existovalo jen několik počítačů na světě.", - "Většinou měly vojenský účel.", + "Relé je elektromagnetický spínač. Je pomalejší a hlučnější než pozdější elektronka, ale umí už skládat logické obvody.", + "Konrad Zuse dokončil v roce 1941 Z3: programovatelný binární stroj s plovoucí řádovou čárkou.", + "Harvard Mark I (1944) byl elektromechanický a počítal balistické tabulky pro americkou armádu.", + "Každý stroj se stavěl na zakázku. Obsluhoval ho tým, ne jeden člověk u stolu.", + "Program býval na děrné pásce nebo na přepínačích, ne v dnešní operační paměti.", + "Typická práce byla vojenská: šifry a tabulky střelby, ne kancelář nebo domácnost.", NULL, }, }, { "3 / 6 • 1. generace", "Počítače 1. generace (1944–1955)", - "Klíčový objev: elektronka", + "Klíčový objev je elektronka. Spíná rychleji než relé, ale hřeje, často se pálí a počítač kvůli ní zabere celý sál.", { - "Objev elektronky umožnil tuto generaci.", - "Ještě neexistoval software v dnešním smyslu.", - "Data a programy se zadávaly pomocí děrných štítků.", + "ENIAC (1945) měl kolem 18 tisíc elektronek a počítal hlavně balistiku.", + "Von Neumannova architektura ukládá program i data do stejné paměti. Na tom stojí i dnešní počítače.", + "UNIVAC I (1951) byl první sériově prodávaný komerční počítač v USA.", + "Program a data se děrovaly do štítků. Operátor vložil balík a čekal na výsledek.", + "Operační systém ani vyšší jazyk ještě nebyl. Psalo se ve strojovém kódu.", + "Sál potřeboval klimatizaci a stálý dohled. Do domácnosti se takový stroj nevešel.", NULL, }, }, { "4 / 6 • 2. generace", "Počítače 2. generace (1955–1964)", - "Klíčový objev: tranzistor", + "Klíčový objev je tranzistor. V Bellových laboratořích vznikl roku 1947 a do počítačů se dostal zhruba od roku 1955.", { - "Objev tranzistoru nahradil elektronky.", - "Používala se hlavní externí paměť.", - "Počítače měly menší rozměry než v 1. generaci.", + "Tranzistor nahradí elektronku: je menší, spolehlivější a méně se hřeje.", + "Stroj už nemusí zabírat celý sál, pořád je to ale zařízení pro firmu nebo ústav.", + "Vnitřní paměť bývá feritová. Vnější paměť jsou magnetické pásky a bubny.", + "FORTRAN (1957) a COBOL (1959) dovolí psát program blíž lidské řeči než strojový kód.", + "Úlohy se zpracovávají dávkově. Operátor seřadí balíky a počítač je jede postupně.", + "Typické stroje této doby jsou IBM 1401 a IBM 7090.", NULL, }, }, { "5 / 6 • 3. generace", "Počítače 3. generace (1964–1971)", - "Klíčový objev: integrovaný obvod", + "Klíčový objev je integrovaný obvod: mnoho tranzistorů na jednom křemíkovém čipu. Kilby a Noyce ho představili na konci 50. let.", { - "Objev integrovaného obvodu.", - "Stále se používala hlavní externí paměť.", - "Rozměry počítačů se dál zmenšovaly.", + "IBM System/360 (1964) je rodina počítačů se stejným instrukčním souborem. Program z menšího modelu běží i na větším.", + "Operační systém už řídí více úloh a periferie. Objevuje se sdílení času mezi uživateli.", + "Magnetický disk doplňuje pásku a zkracuje čekání na data.", + "Minipočítače jako PDP-8 se vejdou do jedné skříně. Kupují je laboratoře a firmy, ne jen stát.", + "Stroj je menší a levnější než sál první generace, pořád to ale není osobní počítač na stůl.", + "Hlavní vnější pamětí zůstává magnetické médium, ne děrný štítek.", NULL, }, }, { "6 / 6 • 4. generace", "Počítače 4. generace", - "Klíčové: programovatelné mikroprocesory", + "Klíčová součástka je programovatelný mikroprocesor: celý procesor na jednom čipu.", { - "Nastupují programovatelné mikroprocesory.", - "1972 – první počítačová hra", - "1973 – první disketa", - "1979 – první CD", + "Intel 4004 z roku 1971 je první komerční mikroprocesor. Pozdější čipy z něj udělaly mozek osobního počítače.", + "Na stůl se počítač dostává postupně: Altair 1975, Apple II 1977 a IBM PC 1981.", + "Roku 1972 vychází Pong, první komerčně úspěšná arkádová hra. Lekce ji bere jako první počítačovou hru.", + "Roku 1971 představuje IBM osmipalcovou disketu. Menší formáty přišly až v dalších letech.", + "Roku 1979 Philips a Sony předvádějí kompaktní disk. V obchodech je od roku 1982.", + "Paměti a disky rostou, takže počítač opouští sál a později se vejde i do kapsy.", NULL, }, }, diff --git a/src/net.c b/src/net.c index 3cd859d..9276dc7 100644 --- a/src/net.c +++ b/src/net.c @@ -1029,43 +1029,45 @@ GtkWidget *build_net_unit5_page(void) { static const NetSlide slides[NET5_SLIDES] = { { "1 / 4 • Signál", "Analogový a digitální signál", - "Tip: f = 1/T", + "Tip: frekvence a perioda jsou převrácené hodnoty. Platí f = 1/T.", { - "Analogový: spojitý, nabývá libovolných hodnot.", - "Digitální: diskrétní, hodnoty 0 a 1.", - "Parametry signálu:", - "perioda T, frekvence f = 1/T,", - "amplituda A, fázový posun Φ.", + "Analogový signál je spojitý. Mezi dvěma hodnotami nabývá i všech hodnot mezi nimi, třeba napětí na mikrofonu nebo teplota.", + "Digitální signál je diskrétní. V počítači nabývá jen hodnot 0 a 1, takže se dá uložit a zkopírovat bez postupného šumu.", + "Perioda T je doba jednoho opakování. Frekvence f = 1/T říká, kolikrát za sekundu se průběh zopakuje. Jednotka je hertz.", + "Amplituda A je velikost výchylky od středu. Fázový posun Φ říká, o kolik je průběh posunutý proti jinému signálu se stejnou frekvencí.", NULL, }, }, { "2 / 4 • Modulace", "Základní typy modulace", - "Modulace = přenos v přeloženém pásmu", + "Modulace nasadí zprávu na nosnou vlnu, aby prošla kabelem nebo éterem. Tomu se říká přenos v přeloženém pásmu.", { - "AM – amplitudová modulace.", - "FM – frekvenční modulace.", - "PM – fázová modulace.", + "AM, amplitudová modulace, mění velikost nosné podle zprávy. Používá ji třeba rozhlas na středních vlnách. Rušení amplitudu snadno pokazí.", + "FM, frekvenční modulace, mění kmitočet nosné a amplitudu nechává. VKV rádio je odolnější proti šumu než AM.", + "PM, fázová modulace, posouvá fázi nosné. Změna fáze a změna frekvence spolu souvisejí, proto se FM a PM často popisují podobně.", + "Bez modulace by pomalá zpráva, třeba hlas, na rádiové frekvenci sama neodešla. Nosná je rychlá vlna, zpráva ji jen tvaruje.", NULL, }, }, { "3 / 4 • Kombinované", "QPSK a 256-QAM", - NULL, + "Jeden stav signálu, symbol, může nést víc než jeden bit. Čím víc stavů, tím víc bitů, ale tím snáz se stavy zamění.", { - "QPSK: 2 bity v jednom prvku (symbolu).", - "256-QAM: 8 bitů v jednom prvku.", + "QPSK má čtyři fázové stavy. Čtyři možnosti jsou dva bity, takže jeden symbol nese 2 bity.", + "256-QAM skládá amplitudu i fázi do 256 stavů. 256 = 2^8, takže jeden symbol nese 8 bitů.", + "Wi-Fi a kabelové modemy QAM používají, protože za stejnou šířku pásma přenesou víc dat než prosté AM.", + "Hustší konstelace chce čistší signál. Když je rušení velké, přijímač spadne na řidší modulaci s menším počtem bitů na symbol.", NULL, }, }, { "4 / 4 • Rychlosti", "Modulační, přenosová a šířka pásma", - NULL, + "Baud a bit za sekundu nejsou totéž. Baud počítá změny signálu, bit za sekundu počítá přenesenou informaci.", { - "Modulační rychlost: počet změn signálu za sekundu [Baud, Bd/s].", - "Přenosová rychlost: velikost přenesené informace [bit/s, bps].", - "Šířka pásma: rozsah frekvencí –", - "určuje maximální možnou rychlost.", + "Modulační rychlost je počet změn signálu za sekundu. Jednotka je baud, Bd/s. U QPSK jedna změna nese 2 bity, takže bitů je víc než baudů.", + "Přenosová rychlost je množství informace za sekundu, v bit/s. Když symbol nese víc bitů, přenosová rychlost je vyšší než modulační.", + "Šířka pásma je rozsah frekvencí, který kanál propustí. U vozovky je to počet pruhů: užší pásmo znamená nižší strop rychlosti.", + "Nyquistův a Shannonův vztah z šířky pásma a šumu odvozují, kolik bitů za sekundu kanál vůbec unese. Větší pásmo nebo čistší signál strop zvedne.", NULL, }, },