feat: top bar declutter — view/filter/groups/sync into one burger popup (iOS)

The top bar now shows only nav + title + a single burger. Tapping it opens a
compact menu: View (with a fixed icon, no longer per-view), Filter, Groups
(if any), Sync, and an "Einstellungen" entry that opens the existing full menu.
Removed the separate group/view/filter icons from both the flat bar and the
Liquid Glass toolbar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Scarriffle
2026-06-06 16:23:56 +02:00
parent 587a0e65fa
commit f480b438cb

View File

@@ -126,12 +126,7 @@ struct CalendarHostView: View {
} }
} }
ToolbarItem(placement: .navigationBarTrailing) { ToolbarItem(placement: .navigationBarTrailing) {
HStack(spacing: 8) { menuButton
if !groups.isEmpty { groupMenu }
viewPickerMenu
filterButton
Button { showMenu = true } label: { Image(systemName: "line.3.horizontal") }
}
} }
} }
.safeAreaInset(edge: .top, spacing: 0) { .safeAreaInset(edge: .top, spacing: 0) {
@@ -200,15 +195,8 @@ struct CalendarHostView: View {
.minimumScaleFactor(0.7) .minimumScaleFactor(0.7)
.layoutPriority(1) .layoutPriority(1)
Spacer(minLength: 6) Spacer(minLength: 6)
if !groups.isEmpty { groupMenu } menuButton
viewPickerMenu .padding(.trailing, 2)
filterButton
Button { showMenu = true } label: {
Image(systemName: "line.3.horizontal")
.font(.system(size: 18, weight: .medium))
.frame(width: 36, height: 36)
}
.padding(.trailing, 2)
} }
.frame(height: 48) .frame(height: 48)
} }
@@ -217,39 +205,6 @@ struct CalendarHostView: View {
barContents.background(.bar) barContents.background(.bar)
} }
private var filterButton: some View {
Button { showFilter = true } label: {
Image(systemName: "line.3.horizontal.decrease.circle")
.font(.system(size: 17, weight: .medium))
.foregroundStyle(store.hiddenCalendarKeys.isEmpty ? .primary : Color.accentColor)
.frame(width: 36, height: 36)
}
.accessibilityLabel(L10n.t("filter.button", appLang))
}
// Group switcher: "Persönlich" + each group. Selecting a group flips the
// calendar into the combined overlay (like the web's group view).
private var groupMenu: some View {
Menu {
Button { switchGroup(nil) } label: {
Label(L10n.t("groups.personal", appLang),
systemImage: store.activeGroup == nil ? "checkmark" : "person")
}
ForEach(groups) { g in
Button { switchGroup(g) } label: {
Label(g.name,
systemImage: store.activeGroup?.id == g.id ? "checkmark" : GroupIcons.symbol(g.icon))
}
}
} label: {
Image(systemName: store.activeGroup == nil ? "person.2" : "person.2.fill")
.font(.system(size: 17, weight: .medium))
.foregroundStyle(store.activeGroup == nil ? .primary : Color.accentColor)
.frame(width: 36, height: 36)
}
.accessibilityLabel(L10n.t("groups.title", appLang))
}
@ViewBuilder private var groupBanner: some View { @ViewBuilder private var groupBanner: some View {
if let g = store.activeGroup { if let g = store.activeGroup {
HStack(spacing: 6) { HStack(spacing: 6) {
@@ -273,20 +228,58 @@ struct CalendarHostView: View {
Task { await forceReload() } Task { await forceReload() }
} }
private var viewPickerMenu: some View { /// The single top-bar action: a compact popup holding view / filter /
/// groups / sync, plus an "Einstellungen" entry that opens the full menu.
/// (Replaces the separate view / filter / group icons in the bar.)
private var menuButton: some View {
Menu { Menu {
ForEach(CalViewType.allCases, id: \.self) { vt in // View (fixed icon, not per-view)
Button { store.viewType = vt } label: { Menu {
Label(vt.label(appLang), systemImage: vt.systemImage) ForEach(CalViewType.allCases, id: \.self) { vt in
Button { store.viewType = vt } label: {
Label(vt.label(appLang), systemImage: store.viewType == vt ? "checkmark" : vt.systemImage)
}
}
} label: {
Label(L10n.t("view.change", appLang), systemImage: "rectangle.3.group")
}
// Filter
Button { showFilter = true } label: {
Label(L10n.t("filter.button", appLang), systemImage: "line.3.horizontal.decrease.circle")
}
// Groups
if !groups.isEmpty {
Menu {
Button { switchGroup(nil) } label: {
Label(L10n.t("groups.personal", appLang),
systemImage: store.activeGroup == nil ? "checkmark" : "person")
}
ForEach(groups) { g in
Button { switchGroup(g) } label: {
Label(g.name,
systemImage: store.activeGroup?.id == g.id ? "checkmark" : GroupIcons.symbol(g.icon))
}
}
} label: {
Label(L10n.t("groups.title", appLang), systemImage: "person.2")
} }
} }
// Sync
Button { Task { await SettingsSync.pull(api: api); await forceReload() } } label: {
Label(L10n.t("menu.sync", appLang), systemImage: "arrow.triangle.2.circlepath")
}
Divider()
// Full settings menu
Button { showMenu = true } label: {
Label(L10n.t("menu.section.settings", appLang), systemImage: "gearshape")
}
} label: { } label: {
Image(systemName: store.viewType.systemImage) Image(systemName: "line.3.horizontal")
.font(.system(size: 17, weight: .medium)) .font(.system(size: 18, weight: .medium))
.foregroundStyle(.primary) .foregroundStyle(store.activeGroup == nil && store.hiddenCalendarKeys.isEmpty ? .primary : Color.accentColor)
.frame(width: 36, height: 36) .frame(width: 36, height: 36)
} }
.accessibilityLabel(L10n.t("view.change", appLang)) .accessibilityLabel(L10n.t("nav.menu", appLang))
} }
// MARK: Error banner // MARK: Error banner