Bearbeiten, Suche auf dem Handy, und zwei Knöpfe, die man treffen kann
Die Seitenleiste lässt sich wieder bearbeiten: Ziehen zum Umsortieren, Haken zum Ein- und Ausblenden. Das Anordnen macht `Arrangement` aus MediaKit — dieselbe Rechnung samt Tests, die schon die Reihen der Startseite ordnet, mit ihren kniffligen Fällen: eine Mediathek, die neu auftaucht, und eine, die aus der gespeicherten Reihenfolge verschwindet. Gespeichert wird über eine feste Kennung **ohne** den Titel, damit eine umbenannte Mediathek ihren Platz behält. Die Suche ist auf dem iPhone kein eigener Reiter mehr. Die Leiste unten hat fünf Plätze, und einer davon für etwas, das man in einer Mediathek sucht, ist einer zu viel — sie sitzt jetzt über der Mediathek, neben Filter und Ansicht. Der Umschalter zwischen Seite und oben ließ sich auf dem Mac kaum treffen: ein Knopf aus einem Zeichen mit Rand hat ohne `contentShape` nur das Zeichen als Ziel. Beide Umschalter haben jetzt eine Fläche. Die Leiste oben trägt dieselbe Schriftgröße wie die Seitenleiste. In Beschriftungsgröße war sie auf einem großen Bildschirm winzig. Und eine Maske: der Aufmacher greift nach links unter die Seitenleiste, scrollt dabei aber, während die Leiste steht — über und unter ihr blieb je ein Streifen Bild stehen. Die Maske sitzt im festen Teil und lässt genau zwei Flächen übrig: alles ab der Inhaltskante, und die Fläche der Leiste. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
dba645d168
commit
e2779f2519
@@ -43,6 +43,10 @@ struct RootView: View {
|
||||
@AppStorage("flixy.navigation.placement")
|
||||
private var placement: NavigationPlacement = .sidebar
|
||||
|
||||
/// Was der Nutzer an der Navigation umgestellt hat: Reihenfolge und was
|
||||
/// überhaupt dastehen soll.
|
||||
@State private var navigationLayout = NavigationLayoutSettings()
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
switch session.phase {
|
||||
@@ -245,13 +249,18 @@ struct RootView: View {
|
||||
}
|
||||
.customizationID("flixy.tab.settings")
|
||||
|
||||
// Die Suche bekommt ihre eigene Rolle: das System stellt sie
|
||||
// getrennt von den übrigen Einträgen dar, so wie man es aus jeder
|
||||
// anderen App kennt.
|
||||
Tab(value: SidebarDestination.search, role: .search) {
|
||||
mediaStack { SearchView() }
|
||||
// Die Suche nur dort, wo Platz für sie ist.
|
||||
//
|
||||
// Die Leiste unten hat fünf Plätze. Mit der Suche wäre einer davon
|
||||
// für etwas belegt, das man in einer Mediathek sucht und nicht in
|
||||
// einer Leiste — deshalb sitzt sie auf dem iPhone über der
|
||||
// Mediathek, neben Filter und Ansicht.
|
||||
if sizeClass == .regular {
|
||||
Tab(value: SidebarDestination.search, role: .search) {
|
||||
mediaStack { SearchView() }
|
||||
}
|
||||
.customizationID("flixy.tab.search")
|
||||
}
|
||||
.customizationID("flixy.tab.search")
|
||||
}
|
||||
.tabViewStyle(.sidebarAdaptable)
|
||||
.tabViewCustomization($customization)
|
||||
@@ -277,6 +286,7 @@ struct RootView: View {
|
||||
if sizeClass == .regular {
|
||||
tabs
|
||||
.background { TabChromeSuppressor().frame(width: 0, height: 0) }
|
||||
.mask(alignment: .topLeading) { chromeMask }
|
||||
.overlay(alignment: placement == .sidebar ? .topLeading : .top) {
|
||||
chrome
|
||||
}
|
||||
@@ -294,19 +304,57 @@ struct RootView: View {
|
||||
private var chrome: some View {
|
||||
switch placement {
|
||||
case .sidebar:
|
||||
FlixySidebar(groups: navigationGroups, selection: $selection, placement: $placement)
|
||||
FlixySidebar(
|
||||
groups: navigationGroups,
|
||||
selection: $selection,
|
||||
placement: $placement,
|
||||
layout: navigationLayout
|
||||
)
|
||||
case .top:
|
||||
// In der Mitte, und zwar erzwungen: die Leiste ist so breit wie
|
||||
// ihr Inhalt, und eine Überlagerung richtet sie dann nicht
|
||||
// zuverlässig mittig aus.
|
||||
HStack {
|
||||
Spacer(minLength: 0)
|
||||
FlixyTopBar(groups: navigationGroups, selection: $selection, placement: $placement)
|
||||
FlixyTopBar(
|
||||
groups: navigationGroups.map(visible),
|
||||
selection: $selection,
|
||||
placement: $placement
|
||||
)
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Wo der Inhalt überhaupt zu sehen sein darf.
|
||||
///
|
||||
/// Der Aufmacher greift nach links unter die Seitenleiste. Er scrollt
|
||||
/// dabei, die Leiste nicht — also blieb über und unter ihr je ein
|
||||
/// Streifen Bild stehen, den nichts abdeckte. Eine Maske kann das
|
||||
/// abschneiden, weil sie **hier** liegt, im festen Teil, und nicht im
|
||||
/// Inhalt, der wandert.
|
||||
///
|
||||
/// Sichtbar ist die Vereinigung aus zwei Flächen: alles ab der
|
||||
/// Inhaltskante, und dazu genau die Fläche der Leiste. Dazwischen — links
|
||||
/// der Inhaltskante, ober- und unterhalb der Leiste — bleibt nichts übrig.
|
||||
@ViewBuilder
|
||||
private var chromeMask: some View {
|
||||
switch placement {
|
||||
case .sidebar:
|
||||
ZStack(alignment: .topLeading) {
|
||||
Color.black
|
||||
.padding(.leading, FlixySidebar.width + FlixySidebar.margin * 2)
|
||||
RoundedRectangle(cornerRadius: FlixyRadius.panel, style: .continuous)
|
||||
.frame(width: FlixySidebar.width)
|
||||
.padding(FlixySidebar.margin)
|
||||
}
|
||||
case .top:
|
||||
// Oben greift nichts über den Rand hinaus; eine Maske hätte nichts
|
||||
// zu tun und könnte nur schaden.
|
||||
Color.black
|
||||
}
|
||||
}
|
||||
|
||||
private var chromeInsets: EdgeInsets {
|
||||
switch placement {
|
||||
case .sidebar:
|
||||
@@ -329,10 +377,10 @@ struct RootView: View {
|
||||
private var navigationGroups: [NavigationGroup] {
|
||||
var groups: [NavigationGroup] = [
|
||||
NavigationGroup(id: "main", title: nil, entries: [
|
||||
NavigationEntry(destination: .home, title: Text("sidebar.home"), symbol: "house"),
|
||||
NavigationEntry(destination: .downloads, title: Text("sidebar.downloads"), symbol: "arrow.down.circle"),
|
||||
NavigationEntry(destination: .settings, title: Text("sidebar.settings"), symbol: "gearshape"),
|
||||
NavigationEntry(destination: .search, title: Text("sidebar.search"), symbol: "magnifyingglass"),
|
||||
NavigationEntry(destination: .home, title: Text("sidebar.home"), symbol: "house", plainTitle: String(localized: "sidebar.home")),
|
||||
NavigationEntry(destination: .downloads, title: Text("sidebar.downloads"), symbol: "arrow.down.circle", plainTitle: String(localized: "sidebar.downloads")),
|
||||
NavigationEntry(destination: .settings, title: Text("sidebar.settings"), symbol: "gearshape", plainTitle: String(localized: "sidebar.settings")),
|
||||
NavigationEntry(destination: .search, title: Text("sidebar.search"), symbol: "magnifyingglass", plainTitle: String(localized: "sidebar.search")),
|
||||
])
|
||||
]
|
||||
|
||||
@@ -340,14 +388,16 @@ struct RootView: View {
|
||||
NavigationEntry(
|
||||
destination: .library(key: library.id, title: library.title),
|
||||
title: Text(library.title),
|
||||
symbol: symbol(for: library.kind)
|
||||
symbol: symbol(for: library.kind),
|
||||
plainTitle: library.title
|
||||
)
|
||||
}
|
||||
if session.supportsLiveTV {
|
||||
media.append(NavigationEntry(
|
||||
destination: .liveTV,
|
||||
title: Text("sidebar.liveTV"),
|
||||
symbol: "dot.radiowaves.left.and.right"
|
||||
symbol: "dot.radiowaves.left.and.right",
|
||||
plainTitle: String(localized: "sidebar.liveTV")
|
||||
))
|
||||
}
|
||||
if !media.isEmpty {
|
||||
@@ -357,6 +407,17 @@ struct RootView: View {
|
||||
return groups
|
||||
}
|
||||
|
||||
/// Eine Gruppe so, wie sie dastehen soll — ausgeblendete Einträge raus,
|
||||
/// Reihenfolge angewandt. Die Seitenleiste macht das selbst, weil sie zum
|
||||
/// Bearbeiten auch die ausgeblendeten braucht.
|
||||
private func visible(_ group: NavigationGroup) -> NavigationGroup {
|
||||
NavigationGroup(
|
||||
id: group.id,
|
||||
title: group.title,
|
||||
entries: navigationLayout.apply(to: group.entries)
|
||||
)
|
||||
}
|
||||
|
||||
/// Ein Navigationsstapel, in dem Detailseiten aufgehen.
|
||||
///
|
||||
/// Das Ziel wird **einmal je Stapel** angemeldet, an seiner Wurzel. Vorher
|
||||
@@ -380,6 +441,9 @@ struct RootView: View {
|
||||
.navigationDestination(for: LiveTVRoute.self) { _ in
|
||||
chromed { LiveTVView() }
|
||||
}
|
||||
.navigationDestination(for: SearchRoute.self) { _ in
|
||||
chromed { SearchView() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user