From cc4415a8a6581118733988cf9aead2e93eff65a1 Mon Sep 17 00:00:00 2001 From: pepaskopec-blip Date: Tue, 22 Sep 2026 12:48:28 +0200 Subject: [PATCH] Enlarge Mac UI density for readable desktop layouts. Bump typography, controls, path-map nodes, and window size so macOS no longer feels phone-scaled. Co-authored-by: Cursor --- ios/Maturita/MaturitaApp.swift | 14 ++ ios/Maturita/UI/Components.swift | 116 ++++++++++++++- ios/Maturita/UI/Exercises.swift | 9 +- ios/Maturita/UI/PathMap.swift | 69 +++++++-- ios/Maturita/UI/Screens.swift | 245 +++++++++++++++++++++---------- macos/Maturita/MaturitaApp.swift | 21 ++- 6 files changed, 376 insertions(+), 98 deletions(-) diff --git a/ios/Maturita/MaturitaApp.swift b/ios/Maturita/MaturitaApp.swift index 99ea68b..e92d0dc 100644 --- a/ios/Maturita/MaturitaApp.swift +++ b/ios/Maturita/MaturitaApp.swift @@ -8,6 +8,10 @@ struct MaturitaApp: App { WindowGroup { NativeRoot(vm: vm) } + #if os(macOS) + .defaultSize(width: 1280, height: 840) + .windowResizability(.contentMinSize) + #endif } } @@ -25,6 +29,9 @@ struct NativeRoot: View { .tint(vm.palette.tint) .preferredColorScheme(vm.mode == .dark ? .dark : .light) .onAppear { vm.checkUpdate(false) } + #if os(macOS) + .frame(minWidth: 980, minHeight: 640) + #endif } private var showsUpdateAccessory: Bool { @@ -68,11 +75,18 @@ private struct UpdateAccessory: View { var body: some View { HStack { Text(bannerText) + #if os(macOS) + .font(.body) + #else .font(.subheadline) + #endif Spacer() if vm.update.canInstall { Button(vm.tr("update_install")) { vm.installUpdate() } .buttonStyle(.borderedProminent) + #if os(macOS) + .controlSize(.large) + #endif } } .padding(.horizontal, 4) diff --git a/ios/Maturita/UI/Components.swift b/ios/Maturita/UI/Components.swift index 72371b7..489dda9 100644 --- a/ios/Maturita/UI/Components.swift +++ b/ios/Maturita/UI/Components.swift @@ -1,5 +1,17 @@ import SwiftUI +#if os(macOS) +enum MacChrome { + static let contentMaxWidth: CGFloat = 1080 + static let exerciseMaxWidth: CGFloat = 900 + static let cardPadding: CGFloat = 24 + static let listRowMinHeight: CGFloat = 52 + static let homeTileMin: CGFloat = 260 + static let homeTileHeight: CGFloat = 176 + static let pathNode: CGFloat = 84 +} +#endif + struct FlowLayout: Layout { var spacing: CGFloat = 6 @@ -48,6 +60,9 @@ struct PrimaryButton: View { Text(label) .font(.body.weight(.semibold)) .frame(maxWidth: .infinity) + #if os(macOS) + .padding(.vertical, 4) + #endif } #if os(macOS) .buttonStyle(.borderedProminent) @@ -66,6 +81,9 @@ struct PillButton: View { var body: some View { Button(label, action: action) .buttonStyle(.bordered) + #if os(macOS) + .controlSize(.large) + #endif } } @@ -74,7 +92,11 @@ struct GlassCard: View { var body: some View { content() + #if os(macOS) + .padding(MacChrome.cardPadding) + #else .padding(16) + #endif .frame(maxWidth: .infinity, alignment: .leading) .softSurface(cornerRadius: 22) } @@ -107,7 +129,11 @@ struct FeedbackLine: View { } icon: { Image(systemName: kind == "ok" ? "checkmark.circle.fill" : kind == "warn" ? "exclamationmark.triangle.fill" : "xmark.circle.fill") } + #if os(macOS) + .font(.body.weight(.semibold)) + #else .font(.subheadline.weight(.semibold)) + #endif .foregroundStyle(kind == "ok" ? palette.success : kind == "warn" ? palette.warning : palette.error) } } @@ -121,7 +147,11 @@ struct Meaning: View { var body: some View { if visible && !text.isEmpty { Text(text) + #if os(macOS) + .font(.body) + #else .font(.footnote) + #endif .foregroundStyle(.secondary) .padding(.bottom, 4) } @@ -139,13 +169,20 @@ struct WordField: View { TextField(placeholder, text: $value) .textInputAutocapitalization(.never) .autocorrectionDisabled() + #if os(macOS) + .font(.title3) + #endif if let mark { Image(systemName: mark ? "checkmark.circle.fill" : "xmark.circle.fill") .foregroundStyle(mark ? (palette?.success ?? .green) : (palette?.error ?? .red)) } } .padding(.horizontal, 12) + #if os(macOS) + .padding(.vertical, 14) + #else .padding(.vertical, 10) + #endif .background(Color(.secondarySystemGroupedBackground), in: RoundedRectangle(cornerRadius: 12, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 12, style: .continuous) @@ -169,20 +206,35 @@ struct SegChip: View { Button(label, action: action) .buttonStyle(.bordered) .tint(selected ? .accentColor : .secondary) + #if os(macOS) + .controlSize(.large) + #endif } } struct Hint: View { let text: String var body: some View { - Text(text).font(.footnote).foregroundStyle(.secondary).padding(.bottom, 6) + Text(text) + #if os(macOS) + .font(.body) + #else + .font(.footnote) + #endif + .foregroundStyle(.secondary) + .padding(.bottom, 6) } } struct Prompt: View { let text: String var body: some View { - Text(text).font(.body.weight(.medium)) + Text(text) + #if os(macOS) + .font(.title3.weight(.medium)) + #else + .font(.body.weight(.medium)) + #endif } } @@ -194,7 +246,11 @@ struct WordChip: View { Button(text, action: action) .buttonStyle(.bordered) .tint(.primary) + #if os(macOS) + .controlSize(.large) + #else .controlSize(.small) + #endif } } @@ -205,8 +261,13 @@ struct WordSlot: View { var body: some View { content() + #if os(macOS) + .frame(maxWidth: .infinity, minHeight: 56, alignment: .leading) + .padding(12) + #else .frame(maxWidth: .infinity, minHeight: 44, alignment: .leading) .padding(8) + #endif .background(Color(.tertiarySystemFill), in: RoundedRectangle(cornerRadius: 14, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: 14, style: .continuous) @@ -224,18 +285,29 @@ struct OptionChip: View { var body: some View { Button(action: action) { - HStack { + HStack(spacing: 10) { Image(systemName: symbol) + .font(.title3) .foregroundStyle(symbolColor) Text(text) + #if os(macOS) + .font(.title3) + #endif .foregroundStyle(.primary) .multilineTextAlignment(.leading) .frame(maxWidth: .infinity, alignment: .leading) } + #if os(macOS) + .padding(.vertical, 8) + #else .padding(.vertical, 4) + #endif } .buttonStyle(.bordered) .tint(tint) + #if os(macOS) + .controlSize(.large) + #endif .padding(.bottom, 4) } @@ -296,8 +368,8 @@ extension View { /// Full-width, leading-aligned content for exercise screens. func exerciseContent() -> some View { #if os(macOS) - frame(maxWidth: 720, alignment: .leading) - .padding(20) + frame(maxWidth: MacChrome.exerciseMaxWidth, alignment: .leading) + .padding(28) .frame(maxWidth: .infinity, alignment: .leading) #else frame(maxWidth: .infinity, alignment: .leading) @@ -311,6 +383,9 @@ extension View { func appListStyle() -> some View { #if os(macOS) listStyle(.inset) + .environment(\.defaultMinListRowHeight, MacChrome.listRowMinHeight) + .scrollContentBackground(.hidden) + .padding(.horizontal, 8) #else self #endif @@ -345,8 +420,39 @@ struct BottomAction: View { PrimaryButton(label: label, action: action) } .padding(.horizontal) + #if os(macOS) + .padding(.top, 12) + .padding(.bottom, 10) + .frame(maxWidth: MacChrome.exerciseMaxWidth) + .frame(maxWidth: .infinity) + #else .padding(.top, 8) .padding(.bottom, 4) + #endif + } +} + +/// Primary + secondary text sized for desktop lists. +struct ListRowLabel: View { + let title: String + var subtitle: String? = nil + + var body: some View { + VStack(alignment: .leading, spacing: 3) { + Text(title) + #if os(macOS) + .font(.title3) + #endif + if let subtitle, !subtitle.isEmpty { + Text(subtitle) + #if os(macOS) + .font(.body) + #else + .font(.caption) + #endif + .foregroundStyle(.secondary) + } + } } } diff --git a/ios/Maturita/UI/Exercises.swift b/ios/Maturita/UI/Exercises.swift index 93a752a..558facc 100644 --- a/ios/Maturita/UI/Exercises.swift +++ b/ios/Maturita/UI/Exercises.swift @@ -384,7 +384,14 @@ private struct AssignEx: View { SegChip(label: groups[i], selected: active == i) { active = i } } } - Text(vm.tr("wordbank")).font(.caption).foregroundStyle(.secondary).padding(.top, 10) + Text(vm.tr("wordbank")) + #if os(macOS) + .font(.body) + #else + .font(.caption) + #endif + .foregroundStyle(.secondary) + .padding(.top, 10) FlowLayout(spacing: 6) { ForEach(items.indices, id: \.self) { i in if loc[i] == -1 { diff --git a/ios/Maturita/UI/PathMap.swift b/ios/Maturita/UI/PathMap.swift index 948341d..17555af 100644 --- a/ios/Maturita/UI/PathMap.swift +++ b/ios/Maturita/UI/PathMap.swift @@ -24,7 +24,11 @@ private struct Serpentine { } /// Keep enough room under/above each node for a two-line caption. +#if os(macOS) +private let labelBand: CGFloat = 56 +#else private let labelBand: CGFloat = 44 +#endif private func serpentine( count n: Int, @@ -138,7 +142,7 @@ struct PathMap: View { nodeSize: nodeSize, lift: branch == nil ? 0 : 56 ) - let labelWidth = min(118, max(72, layout.spacing - 16)) + let labelWidth = min(140, max(88, layout.spacing - 16)) let canvasH = layout.height + labelBand + 24 ScrollView([.horizontal, .vertical]) { ZStack(alignment: .topLeading) { @@ -151,7 +155,11 @@ struct PathMap: View { nodeView(node, index: i) .position(x: pt.x, y: pt.y) Text(node.label) + #if os(macOS) + .font(.callout.weight(.semibold)) + #else .font(.caption2.weight(.semibold)) + #endif .foregroundStyle(node.locked ? palette.overlay : palette.text) .multilineTextAlignment(.center) .lineLimit(2) @@ -167,19 +175,23 @@ struct PathMap: View { if let branch, branch.index < layout.points.count { let base = layout.points[branch.index] - let tip = NodePos(x: base.x + 52, y: base.y - 110, row: base.row) + let tip = NodePos(x: base.x + 64, y: base.y - 128, row: base.row) Path { p in p.move(to: CGPoint(x: base.x, y: base.y - nodeSize / 2)) p.addQuadCurve( to: CGPoint(x: tip.x, y: tip.y + nodeSize / 2), - control: CGPoint(x: base.x + 12, y: tip.y + 24) + control: CGPoint(x: base.x + 14, y: tip.y + 28) ) } - .stroke(palette.rail, style: StrokeStyle(lineWidth: 8, lineCap: .round)) + .stroke(palette.rail, style: StrokeStyle(lineWidth: 10, lineCap: .round)) nodeView(branch.node, index: nil) .position(x: tip.x, y: tip.y) Text(branch.node.label) + #if os(macOS) + .font(.callout.weight(.semibold)) + #else .font(.caption2.weight(.semibold)) + #endif .foregroundStyle(palette.text) .multilineTextAlignment(.center) .lineLimit(2) @@ -195,7 +207,11 @@ struct PathMap: View { .padding(.horizontal, 12) } } + #if os(macOS) + .frame(minHeight: 520) + #else .frame(minHeight: 420) + #endif } @ViewBuilder @@ -240,25 +256,60 @@ struct PathMap: View { } if node.finish { VStack(spacing: 2) { - if let index { Text("\(index + 1)").font(.headline.weight(.black)) } + if let index { + Text("\(index + 1)") + #if os(macOS) + .font(.title2.weight(.black)) + #else + .font(.headline.weight(.black)) + #endif + } FinishFlagBadge(palette: palette) } .foregroundStyle(palette.overlay) } else if node.locked { VStack(spacing: 2) { - if let index { Text("\(index + 1)").font(.headline.weight(.black)) } - Image(systemName: "lock.fill").font(.caption2.weight(.bold)) + if let index { + Text("\(index + 1)") + #if os(macOS) + .font(.title2.weight(.black)) + #else + .font(.headline.weight(.black)) + #endif + } + Image(systemName: "lock.fill") + #if os(macOS) + .font(.caption.weight(.bold)) + #else + .font(.caption2.weight(.bold)) + #endif } .foregroundStyle(palette.overlay) } else if node.done { VStack(spacing: 2) { - if let index { Text("\(index + 1)").font(.headline.weight(.black)) } - Image(systemName: "checkmark").font(.caption2.weight(.bold)) + if let index { + Text("\(index + 1)") + #if os(macOS) + .font(.title2.weight(.black)) + #else + .font(.headline.weight(.black)) + #endif + } + Image(systemName: "checkmark") + #if os(macOS) + .font(.caption.weight(.bold)) + #else + .font(.caption2.weight(.bold)) + #endif } .foregroundStyle(palette.onAccent) } else if let index { Text("\(index + 1)") + #if os(macOS) + .font(.title2.weight(.black)) + #else .font(.headline.weight(.black)) + #endif .foregroundStyle(node.current ? palette.onAccent : palette.text) } } diff --git a/ios/Maturita/UI/Screens.swift b/ios/Maturita/UI/Screens.swift index 26ec309..61fcf24 100644 --- a/ios/Maturita/UI/Screens.swift +++ b/ios/Maturita/UI/Screens.swift @@ -37,15 +37,15 @@ struct PracticeHome: View { VStack(alignment: .leading, spacing: 16) { Text(vm.tr("subjects_title")) .font(.title2.weight(.semibold)) - LazyVGrid(columns: [GridItem(.adaptive(minimum: 220), spacing: 16)], spacing: 16) { + LazyVGrid(columns: [GridItem(.adaptive(minimum: MacChrome.homeTileMin), spacing: 18)], spacing: 18) { ForEach(Array(vm.content.subjects.enumerated()), id: \.offset) { _, s in subjectTile(s) } } } } - .padding(28) - .frame(maxWidth: 920, alignment: .leading) + .padding(32) + .frame(maxWidth: MacChrome.contentMaxWidth, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading) } .navigationTitle("Maturita") @@ -70,28 +70,28 @@ struct PracticeHome: View { } private func subjectTileBody(_ s: J, open: Bool, progress: Double, part: ProgressSum) -> some View { - VStack(alignment: .leading, spacing: 14) { - SubjectIcon(icon: s.str("icon"), open: open, size: 36) + VStack(alignment: .leading, spacing: 16) { + SubjectIcon(icon: s.str("icon"), open: open, size: 44) Text(vm.tr(s.str("key"))) - .font(.headline) + .font(.title3.weight(.semibold)) .foregroundStyle(open ? .primary : .secondary) .multilineTextAlignment(.leading) Spacer(minLength: 0) if open && part.totalEx > 0 { ProgressView(value: progress) Text(vm.fmt("stats_ex_fmt", part.doneEx, part.totalEx)) - .font(.caption) + .font(.subheadline) .foregroundStyle(.secondary) .monospacedDigit() } else if !open { Text(vm.tr("stats_locked")) - .font(.caption) + .font(.subheadline) .foregroundStyle(.secondary) } } - .padding(18) - .frame(maxWidth: .infinity, minHeight: 148, alignment: .topLeading) - .softSurface(cornerRadius: 20) + .padding(22) + .frame(maxWidth: .infinity, minHeight: MacChrome.homeTileHeight, alignment: .topLeading) + .softSurface(cornerRadius: 22) } #endif @@ -193,10 +193,11 @@ struct ChangelogCard: View { Button(vm.tr("changelog_dismiss")) { vm.dismissChangelog() } #if os(macOS) .buttonStyle(.borderedProminent) + .controlSize(.large) #else .buttonStyle(.glassProminent) - #endif .controlSize(.small) + #endif } } } @@ -233,20 +234,43 @@ struct SettingsScreen: View { Text(vm.tr("mode_light")).tag(ColorMode.light) } .pickerStyle(.segmented) + #if os(macOS) + .controlSize(.large) + #endif } Section(vm.tr("theme")) { ForEach(Array(ThemeId.allCases), id: \.self) { id in Button { vm.setTheme(id) } label: { - HStack { - Circle().fill(themePalette(id, vm.mode).tint).frame(width: 14, height: 14) - Text(themeNames[id.rawValue]).foregroundStyle(.primary) + HStack(spacing: 12) { + Circle().fill(themePalette(id, vm.mode).tint) + .frame(width: { + #if os(macOS) + 18 + #else + 14 + #endif + }(), height: { + #if os(macOS) + 18 + #else + 14 + #endif + }()) + Text(themeNames[id.rawValue]) + #if os(macOS) + .font(.title3) + #endif + .foregroundStyle(.primary) Spacer() if vm.themeId == id { Image(systemName: "checkmark").fontWeight(.semibold) } } + #if os(macOS) + .padding(.vertical, 4) + #endif } } } @@ -256,12 +280,25 @@ struct SettingsScreen: View { Text("English").tag(UiLang.en) } .pickerStyle(.segmented) + #if os(macOS) + .controlSize(.large) + #endif } Section(vm.tr("updates")) { - Text(updateText).foregroundStyle(.secondary) + Text(updateText) + #if os(macOS) + .font(.body) + #endif + .foregroundStyle(.secondary) Button(vm.tr("update_check")) { vm.checkUpdate(true) } + #if os(macOS) + .controlSize(.large) + #endif if vm.update.canInstall { Button(vm.tr("update_install")) { vm.installUpdate() } + #if os(macOS) + .controlSize(.large) + #endif } LabeledContent("Build", value: "\(AppConfig.versionName) · \(String(AppConfig.commit.prefix(7)))") } @@ -316,9 +353,16 @@ struct StatsScreen: View { VStack(alignment: .leading, spacing: 6) { HStack { Text(vm.tr(s.str("key"))) + #if os(macOS) + .font(.title3) + #endif Spacer() Text(open ? vm.fmt("stats_ex_fmt", part.doneEx, part.totalEx) : vm.tr("stats_locked")) + #if os(macOS) + .font(.body) + #else .font(.caption) + #endif .foregroundStyle(.secondary) .monospacedDigit() } @@ -327,7 +371,13 @@ struct StatsScreen: View { } } } icon: { - SubjectIcon(icon: s.str("icon"), open: open) + SubjectIcon(icon: s.str("icon"), open: open, size: { + #if os(macOS) + 32 + #else + 26 + #endif + }()) } .foregroundStyle(open ? .primary : .secondary) } @@ -339,8 +389,20 @@ struct StatsScreen: View { private func stat(_ label: String, _ value: String) -> some View { VStack(alignment: .leading, spacing: 2) { - Text(label).font(.caption).foregroundStyle(.secondary) - Text(value).font(.title3.bold()).monospacedDigit() + Text(label) + #if os(macOS) + .font(.subheadline) + #else + .font(.caption) + #endif + .foregroundStyle(.secondary) + Text(value) + #if os(macOS) + .font(.title2.bold()) + #else + .font(.title3.bold()) + #endif + .monospacedDigit() } .frame(maxWidth: .infinity, alignment: .leading) } @@ -393,10 +455,7 @@ struct SearchScreen: View { #endif if !hit.locked { vm.openFromSearch(hit.target) } } label: { - VStack(alignment: .leading, spacing: 2) { - Text(hit.title) - Text(hit.sub).font(.caption).foregroundStyle(.secondary) - } + ListRowLabel(title: hit.title, subtitle: hit.sub) } .disabled(hit.locked) .foregroundStyle(hit.locked ? .secondary : .primary) @@ -542,14 +601,14 @@ struct RoadmapScreen: View { }, palette: vm.palette, litUntil: lit, - nodeSize: 68, - mx: 88, - my: 120, - spac: 168, - gap: 240, - wave: 16 + nodeSize: MacChrome.pathNode, + mx: 100, + my: 140, + spac: 196, + gap: 280, + wave: 18 ) - .padding(20) + .padding(28) .navigationTitle(vm.tr("roadmap_title")) .navigationSubtitle(vm.tr("roadmap_sub")) } @@ -628,15 +687,15 @@ struct UnitMapScreen: View { }, palette: vm.palette, litUntil: lit, - nodeSize: 64, - mx: 88, - my: 118, - spac: 164, - gap: 230, - wave: 14, + nodeSize: MacChrome.pathNode, + mx: 100, + my: 136, + spac: 190, + gap: 268, + wave: 16, branch: branch ) - .padding(20) + .padding(28) .navigationTitle(u.str("title")) .navigationSubtitle(vm.tr(u.str("sub"))) } @@ -674,17 +733,11 @@ struct NetYearsScreen: View { var body: some View { List { NavigationLink(value: Route.netMap) { - VStack(alignment: .leading) { - Text(vm.tr("net_year1")) - Text(vm.tr("net_sub")).font(.caption).foregroundStyle(.secondary) - } + ListRowLabel(title: vm.tr("net_year1"), subtitle: vm.tr("net_sub")) } ForEach(["net_year2", "net_year3", "net_year4"], id: \.self) { key in Label { - VStack(alignment: .leading) { - Text(vm.tr(key)) - Text(vm.tr("net_year_locked_sub")).font(.caption) - } + ListRowLabel(title: vm.tr(key), subtitle: vm.tr("net_year_locked_sub")) } icon: { Image(systemName: "lock.fill") } @@ -734,14 +787,14 @@ struct LessonListScreen: View { }, palette: vm.palette, litUntil: CGFloat(next ?? rows.count), - nodeSize: 64, - mx: 86, - my: 116, - spac: 160, - gap: 236, - wave: 14 + nodeSize: MacChrome.pathNode, + mx: 98, + my: 134, + spac: 186, + gap: 272, + wave: 16 ) - .padding(20) + .padding(28) .navigationTitle(title) .navigationSubtitle(subtitle) } @@ -833,25 +886,39 @@ struct SlidesScreen: View { private func slidePage(_ slide: J) -> some View { ScrollView { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 14) { Text(slide.str("kicker")) + #if os(macOS) + .font(.subheadline.weight(.semibold)) + #else .font(.caption.weight(.semibold)) + #endif .foregroundStyle(.secondary) - Text(slide.str("title")).font(.title2.bold()) + Text(slide.str("title")) + #if os(macOS) + .font(.title.bold()) + #else + .font(.title2.bold()) + #endif if let tip = slide.strOrNull("tip") { GlassCard { Label(tip, systemImage: "lightbulb.fill") } } ForEach(Array(slide.strs("lines").enumerated()), id: \.offset) { _, line in - Text(line).padding(.vertical, 4) + Text(line) + #if os(macOS) + .font(.title3) + #endif + .padding(.vertical, 4) } } .padding() .padding(.bottom, 24) .frame(maxWidth: .infinity, alignment: .leading) #if os(macOS) - .frame(maxWidth: 720, alignment: .leading) + .frame(maxWidth: MacChrome.exerciseMaxWidth, alignment: .leading) + .padding(12) #endif } } @@ -862,13 +929,34 @@ struct CzechMapScreen: View { var body: some View { List { - Label(vm.tr("Literatura"), systemImage: "lock.fill") - .foregroundStyle(.secondary) + Label { + Text(vm.tr("Literatura")) + #if os(macOS) + .font(.title3) + #endif + } icon: { + Image(systemName: "lock.fill") + } + .foregroundStyle(.secondary) NavigationLink(value: Route.mluvnice) { - Label(vm.tr("Mluvnice"), systemImage: "textformat") + Label { + Text(vm.tr("Mluvnice")) + #if os(macOS) + .font(.title3) + #endif + } icon: { + Image(systemName: "textformat") + } } NavigationLink(value: Route.readingList) { - Label(vm.tr("Maturitní četba"), systemImage: "books.vertical") + Label { + Text(vm.tr("Maturitní četba")) + #if os(macOS) + .font(.title3) + #endif + } icon: { + Image(systemName: "books.vertical") + } } } .navigationTitle(vm.tr("Český jazyk a literatura")) @@ -885,15 +973,18 @@ struct BookListScreen: View { ForEach(Array(vm.content.books.enumerated()), id: \.offset) { _, b in NavigationLink(value: Route.book(b.str("id"))) { Label { - VStack(alignment: .leading) { - Text(b.str("title")) - let genre = b.str("genre") - Text(genre.isEmpty ? vm.tr(b.str("subKey")) : genre) - .font(.caption) - .foregroundStyle(.secondary) - } + ListRowLabel( + title: b.str("title"), + subtitle: { + let genre = b.str("genre") + return genre.isEmpty ? vm.tr(b.str("subKey")) : genre + }() + ) } icon: { Image(systemName: "book.fill") + #if os(macOS) + .font(.title3) + #endif } } } @@ -916,24 +1007,24 @@ struct BookScreen: View { if hasQuiz { NavigationLink(value: Route.bookQuiz(id)) { Label { - VStack(alignment: .leading) { - Text(vm.tr(b.str("quizTitle"))) - Text(vm.tr(b.str("quizSub"))).font(.caption).foregroundStyle(.secondary) - } + ListRowLabel(title: vm.tr(b.str("quizTitle")), subtitle: vm.tr(b.str("quizSub"))) } icon: { Image(systemName: "questionmark.circle") + #if os(macOS) + .font(.title3) + #endif } } } if hasPlot { NavigationLink(value: Route.bookPlot(id)) { Label { - VStack(alignment: .leading) { - Text(vm.tr(b.str("plotTitle"))) - Text(vm.tr(b.str("plotSub"))).font(.caption).foregroundStyle(.secondary) - } + ListRowLabel(title: vm.tr(b.str("plotTitle")), subtitle: vm.tr(b.str("plotSub"))) } icon: { Image(systemName: "list.number") + #if os(macOS) + .font(.title3) + #endif } } } @@ -941,6 +1032,10 @@ struct BookScreen: View { Section(note.str("title")) { ForEach(Array(note.strs("lines").enumerated()), id: \.offset) { _, line in Text(line) + #if os(macOS) + .font(.title3) + .padding(.vertical, 4) + #endif } } } diff --git a/macos/Maturita/MaturitaApp.swift b/macos/Maturita/MaturitaApp.swift index ae75923..c8b7c4a 100644 --- a/macos/Maturita/MaturitaApp.swift +++ b/macos/Maturita/MaturitaApp.swift @@ -8,7 +8,8 @@ struct MaturitaApp: App { WindowGroup { MacRoot(vm: vm) } - .defaultSize(width: 1100, height: 720) + .defaultSize(width: 1280, height: 840) + .windowResizability(.contentMinSize) .windowToolbarStyle(.unified) .windowStyle(.automatic) .commands { @@ -37,7 +38,7 @@ struct MaturitaApp: App { Settings { SettingsScreen(vm: vm) .formStyle(.grouped) - .frame(minWidth: 420, minHeight: 460) + .frame(minWidth: 480, minHeight: 520) .tint(vm.palette.tint) .preferredColorScheme(vm.mode == .dark ? .dark : .light) } @@ -58,7 +59,7 @@ struct MacRoot: View { } } .onAppear { vm.checkUpdate(false) } - .frame(minWidth: 880, minHeight: 540) + .frame(minWidth: 980, minHeight: 640) } private var showsUpdateAccessory: Bool { @@ -108,6 +109,7 @@ private struct MacWindowToolbar: ToolbarContent { } label: { Label(vm.tr("search"), systemImage: "magnifyingglass") } + .controlSize(.large) .help(vm.tr("search")) Button { @@ -115,6 +117,7 @@ private struct MacWindowToolbar: ToolbarContent { } label: { Label(vm.tr("stats"), systemImage: "chart.bar.fill") } + .controlSize(.large) .help(vm.tr("stats")) } @@ -126,6 +129,7 @@ private struct MacWindowToolbar: ToolbarContent { } label: { Label(vm.tr("settings"), systemImage: "gearshape") } + .controlSize(.large) .help(vm.tr("settings")) } } @@ -137,19 +141,20 @@ private struct UpdateBanner: View { var body: some View { HStack(spacing: 12) { Image(systemName: "arrow.down.app.fill") + .font(.title3) Text(bannerText) - .font(.subheadline) + .font(.body) Spacer() if vm.update.canInstall { Button(vm.tr("update_install")) { vm.installUpdate() } .buttonStyle(.borderedProminent) - .controlSize(.small) + .controlSize(.large) } } - .padding(.horizontal, 16) - .padding(.vertical, 12) + .padding(.horizontal, 18) + .padding(.vertical, 14) .softSurface(cornerRadius: 16) - .frame(maxWidth: 560) + .frame(maxWidth: 640) } private var bannerText: String {