chore(repo): iOS-App als ios/ ins Hauptrepo uebernehmen
Calendarr-IOS wird mit vollstaendiger Historie als Unterverzeichnis ios/ eingebunden. Damit liegen Server, Web, Android und iOS in einem gemeinsamen Repo. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,121 @@
|
||||
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()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user