Mac: näher am iPad — Schrift, Seitenleiste, Formulare
Drei Gründe, warum der Mac anders aussah, und keiner davon war Geschmack. **Die Schrift.** macOS rendert dieselben Textstile deutlich kleiner als iOS: `largeTitle` misst dort 26 Punkt statt 34, Fließtext 13 statt 17. Eine Oberfläche, die auf dem iPad stimmt, wirkt damit zusammengeschrumpft. Auf dem Mac stehen jetzt feste Größen — die von iOS. Das kostet dort Dynamic Type; vertretbar, weil macOS keine systemweite Textgröße kennt und man ein Fenster stattdessen größer ziehen kann. **Der Aufmacher wächst mit.** Das war der zweite, größere Teil: das Bild ist so breit wie das Fenster, die Schrift darauf war es nicht. Bei 1280 Punkt Breite wirkt ein 34-Punkt-Titel verloren, bei 834 auf dem iPad genau richtig. Titel, Schriftzug, Angaben und Abstände richten sich jetzt nach der Breite des Aufmachers — mit dem iPad als Bezug und einem Deckel, damit ein Fenster über die ganze Wand nicht in Plakatschrift endet. Die Beschreibung bekommt außerdem eine Zeilenbreite: ein Satz über 1280 Punkt ist unlesbar, das Auge verliert die Zeile. **Die Seitenleiste.** Sie richtet sich nach ihrem Inhalt, und bei Wörtern wie „Start" fällt sie auf eine Breite zusammen, in der schon der Kontoname darunter nicht mehr Platz hatte — daher das „S…". Die Beschriftungen haben jetzt eine Mindestbreite. **Die Formulare.** Auf dem Mac nahm SwiftUI den Spalten-Stil: Beschriftungen rechts ausgerichtet, keine abgesetzten Abschnitte. Jetzt derselbe gruppierte Stil wie auf iOS. Dazu: Scrollbalken sind weg. Der graue Streifen unter dem Aufmacher kam daher, dass in den Systemeinstellungen „Scrollbalken immer einblenden" steht — dagegen reicht `.hidden` nicht, `.never` schon. Und das Fenster öffnet mit 1280 × 820 statt in Notizzettelgröße, ohne graue Titelleiste. 263 Tests grün, iOS baut unverändert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
d54954b9c2
commit
f8ada44a8c
@@ -67,6 +67,12 @@ struct FlixyApp: App {
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
RootView()
|
||||
// Eine Untergrenze, unter der die Oberfläche nicht mehr
|
||||
// aufgeht: schmaler als das hier klappt die Seitenleiste zu
|
||||
// und die Reihen zeigen anderthalb Kacheln.
|
||||
#if os(macOS)
|
||||
.frame(minWidth: 900, minHeight: 620)
|
||||
#endif
|
||||
.environment(session)
|
||||
.environment(playback)
|
||||
.environment(playbackSettings)
|
||||
@@ -102,6 +108,17 @@ struct FlixyApp: App {
|
||||
}
|
||||
}
|
||||
.modelContainer(modelContainer)
|
||||
#if os(macOS)
|
||||
// Ohne diese Angabe öffnet macOS ein Fenster von der Größe eines
|
||||
// Notizzettels — und darin fällt der Anmeldebildschirm in sich
|
||||
// zusammen, weil seine Abstände dehnbar sind.
|
||||
.defaultSize(width: 1280, height: 820)
|
||||
// Das Fenster darf nicht kleiner werden, als der Inhalt verträgt.
|
||||
.windowResizability(.contentMinSize)
|
||||
// Keine graue Titelleiste über einem dunklen Bild: die Ampelknöpfe
|
||||
// schweben über dem Inhalt, wie man es von Fernseh-Apps kennt.
|
||||
.windowStyle(.hiddenTitleBar)
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ struct ParentLinks: View {
|
||||
}
|
||||
.padding(.horizontal, FlixySpacing.screenMargin)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ struct PlayableDetailView: View {
|
||||
}
|
||||
.padding(.bottom, FlixySpacing.section)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
|
||||
private var currentMedia: MediaVersion? {
|
||||
@@ -162,7 +162,7 @@ struct TagStrip: View {
|
||||
}
|
||||
.padding(.horizontal, FlixySpacing.screenMargin)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,7 @@ struct CastRow: View {
|
||||
}
|
||||
.padding(.horizontal, FlixySpacing.screenMargin)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ struct SeriesDetailView: View {
|
||||
}
|
||||
.padding(.bottom, FlixySpacing.section)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
.task(id: item.id) { await loadChildren() }
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,10 @@ struct HeroCarousel: View {
|
||||
}
|
||||
.scrollTargetBehavior(.paging)
|
||||
.scrollPosition(id: $shownID)
|
||||
.scrollIndicators(.hidden)
|
||||
// `.never`, nicht `.hidden`: wer in den Systemeinstellungen
|
||||
// „Scrollbalken immer einblenden" gewählt hat, bekam sonst einen
|
||||
// grauen Streifen quer unter den Aufmacher gelegt.
|
||||
.scrollIndicators(.never)
|
||||
.aspectRatio(FlixyAspectRatio.hero, contentMode: .fit)
|
||||
// Jede Berührung hält das Weiterwandern an — auch die, die nur ein
|
||||
// Antippen wird. Gleichzeitig und ohne Mindestweg, damit weder dem
|
||||
|
||||
@@ -65,7 +65,7 @@ struct HomeView: View {
|
||||
}
|
||||
.padding(.bottom, FlixySpacing.section)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
|
||||
/// Bringt die Reihen vom Server in die Form, die eingestellt ist.
|
||||
|
||||
@@ -37,7 +37,7 @@ struct MediaRow: View {
|
||||
.scrollTargetLayout()
|
||||
}
|
||||
.scrollTargetBehavior(.viewAligned)
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,6 +70,7 @@ struct LibraryFilterSheet: View {
|
||||
// gehen können — sichtbar an Picker-Werten, die in der Standardfarbe
|
||||
// leuchteten, während die Schalter daneben schon die gewählte trugen.
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("filter.title"))
|
||||
|
||||
@@ -56,7 +56,7 @@ struct ProgramGuide: View {
|
||||
lanes
|
||||
}
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
}
|
||||
.background(Color.flixyCanvas)
|
||||
.sheet(item: $selection) { selection in
|
||||
@@ -193,7 +193,7 @@ struct ProgramGuide: View {
|
||||
}
|
||||
.frame(width: layout.totalWidth, alignment: .topLeading)
|
||||
}
|
||||
.scrollIndicators(.hidden)
|
||||
.scrollIndicators(.never)
|
||||
.frame(height: CGFloat(channels.count) * rowHeight)
|
||||
.onScrollGeometryChange(for: CGFloat.self) { geometry in
|
||||
geometry.contentOffset.x
|
||||
|
||||
@@ -94,8 +94,11 @@ struct AccountBar: View {
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
// Ohne Vorrang staucht SwiftUI den Text vor dem Zwischenraum:
|
||||
// in einer schmalen Leiste stand vom Namen nur noch „S…" da.
|
||||
.layoutPriority(1)
|
||||
|
||||
Spacer(minLength: FlixySpacing.snug)
|
||||
Spacer(minLength: 0)
|
||||
|
||||
// Zeigt nach oben: dorthin geht die Karte auf.
|
||||
Image(systemName: "chevron.up")
|
||||
|
||||
@@ -187,11 +187,11 @@ struct RootView: View {
|
||||
TabSection {
|
||||
ForEach(session.libraries) { library in
|
||||
Tab(
|
||||
library.title,
|
||||
systemImage: symbol(for: library.kind),
|
||||
value: SidebarDestination.library(key: library.id, title: library.title)
|
||||
) {
|
||||
mediaStack { LibraryView(library: library) }
|
||||
} label: {
|
||||
tabLabel(library.title, symbol: symbol(for: library.kind))
|
||||
}
|
||||
.customizationID("flixy.tab.library.\(library.id)")
|
||||
}
|
||||
@@ -201,37 +201,33 @@ struct RootView: View {
|
||||
.customizationID("flixy.tab.libraries")
|
||||
|
||||
if session.supportsLiveTV {
|
||||
Tab(
|
||||
"sidebar.liveTV",
|
||||
systemImage: "dot.radiowaves.left.and.right",
|
||||
value: SidebarDestination.liveTV
|
||||
) {
|
||||
Tab(value: SidebarDestination.liveTV) {
|
||||
mediaStack { LiveTVView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.liveTV", symbol: "dot.radiowaves.left.and.right")
|
||||
}
|
||||
.customizationID("flixy.tab.liveTV")
|
||||
}
|
||||
} else {
|
||||
Tab(
|
||||
"sidebar.library",
|
||||
systemImage: "books.vertical",
|
||||
value: SidebarDestination.libraryHub
|
||||
) {
|
||||
Tab(value: SidebarDestination.libraryHub) {
|
||||
mediaStack { LibraryHubView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.library", symbol: "books.vertical")
|
||||
}
|
||||
.customizationID("flixy.tab.libraryHub")
|
||||
}
|
||||
|
||||
Tab(
|
||||
"sidebar.downloads",
|
||||
systemImage: "arrow.down.circle",
|
||||
value: SidebarDestination.downloads
|
||||
) {
|
||||
Tab(value: SidebarDestination.downloads) {
|
||||
mediaStack { DownloadsView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.downloads", symbol: "arrow.down.circle")
|
||||
}
|
||||
.customizationID("flixy.tab.downloads")
|
||||
|
||||
Tab("sidebar.settings", systemImage: "gearshape", value: SidebarDestination.settings) {
|
||||
Tab(value: SidebarDestination.settings) {
|
||||
mediaStack { SettingsView() }
|
||||
} label: {
|
||||
tabLabel("sidebar.settings", symbol: "gearshape")
|
||||
}
|
||||
.customizationID("flixy.tab.settings")
|
||||
|
||||
@@ -274,6 +270,32 @@ struct RootView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Die Beschriftung eines Eintrags.
|
||||
///
|
||||
/// Auf dem Mac mit einer Mindestbreite. Die Seitenleiste richtet sich nach
|
||||
/// ihrem Inhalt, und bei kurzen Wörtern wie „Start" fällt sie auf eine
|
||||
/// Breite zusammen, in der schon der Kontoname darunter nicht mehr Platz
|
||||
/// hat — auf dem iPad ist sie dreimal so breit, und genau dieser
|
||||
/// Unterschied macht die beiden Oberflächen ungleich.
|
||||
private func tabLabel(_ title: LocalizedStringKey, symbol: String) -> some View {
|
||||
let label = Label(title, systemImage: symbol)
|
||||
#if os(macOS)
|
||||
return label.frame(minWidth: 170, alignment: .leading)
|
||||
#else
|
||||
return label
|
||||
#endif
|
||||
}
|
||||
|
||||
/// Dieselbe Beschriftung, wenn der Titel erst vom Server kommt.
|
||||
private func tabLabel(_ title: String, symbol: String) -> some View {
|
||||
let label = Label(title, systemImage: symbol)
|
||||
#if os(macOS)
|
||||
return label.frame(minWidth: 170, alignment: .leading)
|
||||
#else
|
||||
return label
|
||||
#endif
|
||||
}
|
||||
|
||||
private func symbol(for kind: MediaLibrary.Kind) -> String {
|
||||
switch kind {
|
||||
case .movie: "film"
|
||||
|
||||
@@ -67,6 +67,7 @@ struct AppearanceView: View {
|
||||
PreviewRow()
|
||||
}
|
||||
}
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("appearance.title"))
|
||||
|
||||
@@ -115,6 +115,7 @@ struct DiagnosticsView: View {
|
||||
// gehen können — sichtbar an Picker-Werten, die in der Standardfarbe
|
||||
// leuchteten, während die Schalter daneben schon die gewählte trugen.
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("diagnostics.title"))
|
||||
|
||||
@@ -73,6 +73,7 @@ struct DownloadSettingsView: View {
|
||||
}
|
||||
}
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("sidebar.downloads"))
|
||||
|
||||
@@ -59,6 +59,7 @@ struct LanguagesView: View {
|
||||
}
|
||||
}
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("languages.title"))
|
||||
|
||||
@@ -86,6 +86,7 @@ struct PlaybackSettingsView: View {
|
||||
}
|
||||
}
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("settings.playback"))
|
||||
|
||||
@@ -46,6 +46,7 @@ struct ServerSettingsView: View {
|
||||
}
|
||||
}
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("settings.server"))
|
||||
|
||||
@@ -80,6 +80,7 @@ struct SettingsView: View {
|
||||
// gehen können — sichtbar an Picker-Werten, die in der Standardfarbe
|
||||
// leuchteten, während die Schalter daneben schon die gewählte trugen.
|
||||
.tint(accent)
|
||||
.flixyForm()
|
||||
.scrollContentBackground(.hidden)
|
||||
.background(Color.flixyCanvas)
|
||||
.navigationTitle(Text("sidebar.settings"))
|
||||
|
||||
Reference in New Issue
Block a user