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:
@@ -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,14 +195,7 @@ 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
|
|
||||||
filterButton
|
|
||||||
Button { showMenu = true } label: {
|
|
||||||
Image(systemName: "line.3.horizontal")
|
|
||||||
.font(.system(size: 18, weight: .medium))
|
|
||||||
.frame(width: 36, height: 36)
|
|
||||||
}
|
|
||||||
.padding(.trailing, 2)
|
.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 {
|
||||||
|
// View (fixed icon, not per-view)
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(CalViewType.allCases, id: \.self) { vt in
|
ForEach(CalViewType.allCases, id: \.self) { vt in
|
||||||
Button { store.viewType = vt } label: {
|
Button { store.viewType = vt } label: {
|
||||||
Label(vt.label(appLang), systemImage: vt.systemImage)
|
Label(vt.label(appLang), systemImage: store.viewType == vt ? "checkmark" : vt.systemImage)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: store.viewType.systemImage)
|
Label(L10n.t("view.change", appLang), systemImage: "rectangle.3.group")
|
||||||
.font(.system(size: 17, weight: .medium))
|
}
|
||||||
.foregroundStyle(.primary)
|
// 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: {
|
||||||
|
Image(systemName: "line.3.horizontal")
|
||||||
|
.font(.system(size: 18, weight: .medium))
|
||||||
|
.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
|
||||||
|
|||||||
Reference in New Issue
Block a user