Files
Calendarr-IOS/Calendarr iOS/Views/MenuSheet.swift
Scarriffle 9fac13f99c feat: iOS Gruppen – Liste, Erstellen/Verwalten, kombinierte Ansicht
- Menü-Eintrag "Gruppen" -> GroupsView (Liste, Erstellen mit Icon-Auswahl +
  Mitglieder, Verwalten: umbenennen/Icon/Mitglieder/Mitglieder-Farben/löschen).
- GroupCombinedView: monatsweise Agenda der überlagerten Mitglieder-Kalender
  + Gruppenkalender; Termine mit Besitzer-Vorname bzw. 👥 + Ersteller,
  server-definierte Farben (display_color).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-05-31 20:08:53 +02:00

122 lines
4.7 KiB
Swift

import SwiftUI
struct MenuSheet: View {
let api: CalendarrAPI
@Environment(AppState.self) var appState
@Environment(\.dismiss) var dismiss
@AppStorage("appLanguage") private var appLang = "system"
@State private var isSyncing = false
var body: some View {
NavigationStack {
List {
// User info header
Section {
HStack(spacing: 12) {
Circle()
.fill(Color.accentColor)
.frame(width: 44, height: 44)
.overlay {
Text(appState.username.prefix(1).uppercased())
.font(.title3.bold())
.foregroundStyle(.white)
}
VStack(alignment: .leading, spacing: 2) {
Text(appState.username).font(.headline)
Text(appState.serverURL
.replacingOccurrences(of: "https://", with: "")
.replacingOccurrences(of: "http://", with: ""))
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(1)
}
if appState.isAdmin {
Spacer()
Text(L10n.t("menu.admin", appLang))
.font(.caption2.weight(.semibold))
.padding(.horizontal, 8).padding(.vertical, 3)
.background(Color.accentColor.opacity(0.15))
.foregroundStyle(Color.accentColor)
.clipShape(Capsule())
}
}
.padding(.vertical, 4)
}
Section(L10n.t("menu.section.settings", appLang)) {
NavigationLink {
ProfileView(api: api)
} label: {
Label(L10n.t("menu.profile", appLang), systemImage: "person.circle")
}
NavigationLink {
SettingsView(api: api)
} label: {
Label(L10n.t("menu.appearance", appLang), systemImage: "paintpalette")
}
NavigationLink {
AccountsView(api: api)
} label: {
Label(L10n.t("menu.accounts", appLang), systemImage: "tray.2")
}
NavigationLink {
GroupsView(api: api)
} label: {
Label(L10n.t("groups.title", appLang), systemImage: "person.2")
}
NavigationLink {
ServerView()
} label: {
Label(L10n.t("menu.server", appLang), systemImage: "server.rack")
}
}
Section(L10n.t("menu.sync.section", appLang)) {
Button {
Task { await syncNow() }
} label: {
HStack {
Label(L10n.t("menu.sync", appLang), systemImage: "arrow.triangle.2.circlepath")
Spacer()
if isSyncing { ProgressView() }
}
}
.disabled(isSyncing)
}
Section {
Button(role: .destructive) {
dismiss()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
appState.logout()
}
} label: {
Label(L10n.t("menu.logout", appLang), systemImage: "rectangle.portrait.and.arrow.right")
}
}
}
.navigationTitle(L10n.t("nav.menu", appLang))
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button(L10n.t("nav.done", appLang)) { dismiss() }
}
}
}
}
/// Manual sync: pull appearance/behaviour settings from the server, then
/// ask the calendar host to re-fetch events (cache-busting).
private func syncNow() async {
isSyncing = true
await SettingsSync.pull(api: api)
NotificationCenter.default.post(name: .manualSyncRequested, object: nil)
isSyncing = false
dismiss()
}
}