Im Band ist Platz für mehr als einen Wert

Die Begründung für „ein Wert je Seite" war falsch gerechnet: der Arm ist bei
fünf Kacheln je 376 Punkte breit, und ein Wert braucht davon knapp sechzig. Die
Zahl im Kopf war die der schmalen Insel, nicht die des ausgefahrenen Arms.

Jetzt bis zu vier Elemente je Seite, in wählbarer Reihenfolge — Uhr, Wetter,
Akku, CPU, Speicher, Temperatur. Kein Ziehen zum Umsortieren: bei höchstens vier
Einträgen ist Herausnehmen und wieder Hinzufügen schneller als eine Ziehgeste,
die man erst finden muss.

„Nichts" ist keine Wahl mehr, sondern die leere Liste — zwei Wege, dasselbe zu
sagen, waren einer zu viel.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-09-05 21:32:24 +02:00
co-authored by Claude Opus 5
parent e3b215a1dd
commit fd29df51cf
4 changed files with 92 additions and 55 deletions
+19 -9
View File
@@ -85,22 +85,32 @@ final class AppModel {
}
}
/// Was links neben der Notch steht.
var bandLeading: BandAccessory {
/// Was links neben der Notch steht, in dieser Reihenfolge.
///
/// Eine Liste und keine einzelne Wahl: der Arm ist bei fünf Kacheln rund
/// 376 Punkte breit, und ein Wert braucht davon knapp sechzig.
var bandLeading: [BandAccessory] {
didSet {
guard bandLeading != oldValue else { return }
defaults.set(bandLeading.rawValue, forKey: Keys.bandLeading)
defaults.set(bandLeading.map(\.rawValue), forKey: Keys.bandLeading)
}
}
/// Was rechts daneben steht.
var bandTrailing: BandAccessory {
var bandTrailing: [BandAccessory] {
didSet {
guard bandTrailing != oldValue else { return }
defaults.set(bandTrailing.rawValue, forKey: Keys.bandTrailing)
defaults.set(bandTrailing.map(\.rawValue), forKey: Keys.bandTrailing)
}
}
/// Wie viele Elemente je Seite höchstens.
///
/// Vier passen bei fünf Kacheln bequem. Wer weniger Kacheln zeigt, hat
/// schmalere Arme — dann wird das Letzte abgeschnitten, und das ist die
/// ehrlichere Grenze als eine Zahl, die für jede Panelbreite stimmen müsste.
static let bandLimit = 4
/// Notch oder Insel — die Form, die Onyx oben annimmt.
var notchStyle: NotchStyle {
didSet {
@@ -206,10 +216,10 @@ final class AppModel {
// Die bisherige feste Verdrahtung als Voreinstellung: wer nichts
// einstellt, sieht, was vorher auch dastand.
self.bandLeading = defaults.string(forKey: Keys.bandLeading)
.flatMap(BandAccessory.init(rawValue:)) ?? .weather
self.bandTrailing = defaults.string(forKey: Keys.bandTrailing)
.flatMap(BandAccessory.init(rawValue:)) ?? .battery
self.bandLeading = (defaults.stringArray(forKey: Keys.bandLeading)
.map { $0.compactMap(BandAccessory.init(rawValue:)) }) ?? [.weather]
self.bandTrailing = (defaults.stringArray(forKey: Keys.bandTrailing)
.map { $0.compactMap(BandAccessory.init(rawValue:)) }) ?? [.battery]
self.notchWidths = (defaults.dictionary(forKey: Keys.notchWidths) as? [String: Double] ?? [:])
.reduce(into: [CGDirectDisplayID: CGFloat]()) { result, entry in
guard let id = CGDirectDisplayID(entry.key) else { return }
+2 -18
View File
@@ -217,22 +217,6 @@
}
}
},
"band.none": {
"localizations": {
"de": {
"stringUnit": {
"state": "translated",
"value": "Nichts"
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "Nothing"
}
}
}
},
"band.temperature": {
"localizations": {
"de": {
@@ -2893,13 +2877,13 @@
"de": {
"stringUnit": {
"state": "translated",
"value": "Was auf Menüleistenhöhe links und rechts der Notch steht, wenn das Panel offen ist. Je Seite ein Wert — für mehr ist der Streifen zu schmal."
"value": "Was auf Menüleistenhöhe links und rechts der Notch steht, wenn das Panel offen ist. Bis zu vier je Seite — bei wenigen Kacheln sind die Arme schmaler, dann wird das Letzte abgeschnitten."
}
},
"en": {
"stringUnit": {
"state": "translated",
"value": "What sits at menu bar height to the left and right of the notch while the panel is open. One value per side — the strip is too narrow for more."
"value": "What sits at menu bar height left and right of the notch while the panel is open. Up to four per side — with few tiles the arms are narrower and the last one gets clipped."
}
}
}
+17 -14
View File
@@ -202,12 +202,10 @@ struct BrightnessActivity: NotchActivity {
/// Die Elemente, die links und rechts der Notch sitzen können.
///
/// **Eine Wahl je Seite, keine Liste.** Der Arm ist bei fünf Kacheln rund 200
/// Punkte breit und liegt auf Menüleistenhöhe — dort passt ein Wert, nicht drei.
/// Eine Mehrfachauswahl anzubieten hieße, ein Gedränge zu ermöglichen, das man
/// hinterher wieder wegräumen muss.
/// Mehrere je Seite: der Arm ist bei fünf Kacheln rund 376 Punkte breit, und ein
/// Wert braucht davon knapp sechzig. Vier nebeneinander sind kein Gedränge,
/// sondern eine Zeile.
enum BandAccessory: String, Codable, CaseIterable, Sendable {
case none
case clock
case weather
case battery
@@ -217,7 +215,6 @@ enum BandAccessory: String, Codable, CaseIterable, Sendable {
var localizedName: LocalizedStringKey {
switch self {
case .none: "band.none"
case .clock: "band.clock"
case .weather: "band.weather"
case .battery: "band.battery"
@@ -240,22 +237,28 @@ struct BandSlotView: View {
let weather: WeatherModel?
let metrics: MetricsModel?
private var kind: BandAccessory {
private var kinds: [BandAccessory] {
edge == .leading ? model.bandLeading : model.bandTrailing
}
var body: some View {
content
.padding(.leading, edge == .leading ? Onyx.Space.xl : Onyx.Space.lg)
.padding(.trailing, edge == .leading ? Onyx.Space.lg : Onyx.Space.xl)
.frame(maxWidth: .infinity, alignment: edge == .leading ? .leading : .trailing)
HStack(spacing: Onyx.Space.xl) {
ForEach(kinds, id: \.self) { kind in
content(kind)
}
}
.padding(.leading, edge == .leading ? Onyx.Space.xl : Onyx.Space.lg)
.padding(.trailing, edge == .leading ? Onyx.Space.lg : Onyx.Space.xl)
.frame(maxWidth: .infinity, alignment: edge == .leading ? .leading : .trailing)
// Was nicht mehr hineinpasst, wird abgeschnitten statt den Arm über die
// Notch hinauszuschieben. Die Obergrenze in den Einstellungen sorgt
// dafür, dass es dazu im Normalfall nicht kommt.
.fixedSize(horizontal: true, vertical: false)
}
@ViewBuilder
private var content: some View {
private func content(_ kind: BandAccessory) -> some View {
switch kind {
case .none:
EmptyView()
case .clock:
// Die eine Angabe, die keine Quelle braucht.
TimelineView(.everyMinute) { context in
+54 -14
View File
@@ -203,22 +203,10 @@ private struct NotchSettings: View {
OnyxFormCard(String(localized: "settings.band"),
hint: String(localized: "settings.band.hint")) {
OnyxFormRow(String(localized: "settings.band.leading")) {
Picker("", selection: $model.bandLeading) {
ForEach(BandAccessory.allCases, id: \.self) { kind in
Text(kind.localizedName).tag(kind)
}
}
.labelsHidden()
.frame(width: 200)
BandEditor(items: $model.bandLeading)
}
OnyxFormRow(String(localized: "settings.band.trailing")) {
Picker("", selection: $model.bandTrailing) {
ForEach(BandAccessory.allCases, id: \.self) { kind in
Text(kind.localizedName).tag(kind)
}
}
.labelsHidden()
.frame(width: 200)
BandEditor(items: $model.bandTrailing)
}
}
@@ -735,3 +723,55 @@ private struct NotchWidthSettings: View {
set: { model.setNotchWidth($0, for: id) })
}
}
/// Die Elemente einer Bandseite: was drin ist, in welcher Reihenfolge, und was
/// sich hinzufügen lässt.
///
/// Kein Ziehen zum Umsortieren. Die Liste hat höchstens vier Einträge, und für
/// vier Einträge ist Herausnehmen und wieder Hinzufügen schneller als jede
/// Ziehgeste — die man außerdem erst finden muss.
private struct BandEditor: View {
@Binding var items: [BandAccessory]
private var available: [BandAccessory] {
BandAccessory.allCases.filter { !items.contains($0) }
}
var body: some View {
HStack(spacing: Onyx.Space.sm) {
ForEach(items, id: \.self) { item in
HStack(spacing: Onyx.Space.xs) {
Text(item.localizedName)
.font(Onyx.Font.footnote)
Button {
items.removeAll { $0 == item }
} label: {
Image(systemName: "xmark")
.font(.system(size: Onyx.Symbol.caption))
}
.buttonStyle(.plain)
.foregroundStyle(.secondary)
}
.padding(.horizontal, Onyx.Space.sm)
.padding(.vertical, Onyx.Space.xs)
.background(.quaternary, in: .capsule)
}
if items.count < AppModel.bandLimit, !available.isEmpty {
Menu {
ForEach(available, id: \.self) { candidate in
Button { items.append(candidate) } label: {
Text(candidate.localizedName)
}
}
} label: {
Image(systemName: "plus")
}
.menuStyle(.borderlessButton)
.menuIndicator(.hidden)
.fixedSize()
}
}
}
}