import SwiftUI import UniformTypeIdentifiers enum Tab: String, CaseIterable, Identifiable { case overview = "Übersicht", history = "Verlauf", persons = "Personen", tasks = "Aufgaben" var id: String { rawValue } } /// Zustand der Oberfläche (welcher Bereich, welcher Eintrag wird gerade bearbeitet). @MainActor final class UIState: ObservableObject { @Published var tab: Tab = .overview @Published var draft: Entry? @Published var toast: String? func newEntry(task: String = "", person: String = "", partner: String = "") { draft = Entry(id: "", date: Day.today, task: task, person: person, partner: partner) } func show(_ message: String) { toast = message Task { try? await Task.sleep(for: .seconds(3)); if toast == message { toast = nil } } } } @main struct MawariApp: App { @StateObject private var store = Store() @StateObject private var ui = UIState() @StateObject private var updates = UpdateController() var body: some Scene { Window("Mawari", id: "main") { ContentView() .environmentObject(store) .environmentObject(ui) .frame(minWidth: 960, minHeight: 600) } .defaultSize(width: 1280, height: 820) .commands { CommandGroup(after: .appInfo) { Button("Nach Updates suchen …") { updates.checkForUpdates() } .disabled(!updates.canCheck) } CommandGroup(replacing: .newItem) { Button("Neuer Eintrag") { ui.newEntry() }.keyboardShortcut("n") } CommandGroup(replacing: .importExport) { Button("Export für Numbers …") { Exporter.exportXlsx(store: store, ui: ui) }.keyboardShortcut("e") Divider() Button("Sicherung speichern …") { Exporter.saveBackup(store: store, ui: ui) } Button("Sicherung / HTML-Daten laden …") { Exporter.importBackup(store: store, ui: ui) } Button("Datenordner im Finder zeigen") { NSWorkspace.shared.activateFileViewerSelecting([store.folder]) } } CommandMenu("Ansicht") { ForEach(Array(Tab.allCases.enumerated()), id: \.element) { i, tab in Button(tab.rawValue) { ui.tab = tab }.keyboardShortcut(KeyEquivalent(Character("\(i + 1)"))) } } } } } struct ContentView: View { @EnvironmentObject var store: Store @EnvironmentObject var ui: UIState var body: some View { Group { switch ui.tab { case .overview: OverviewView() case .history: HistoryView() case .persons: PersonsView() case .tasks: TasksView() } } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) .background(Color(nsColor: .textBackgroundColor)) .toolbar { ToolbarItem(placement: .principal) { Picker("Bereich", selection: $ui.tab) { ForEach(Tab.allCases) { Text($0.rawValue).tag($0) } } .pickerStyle(.segmented) .frame(width: 380) } ToolbarItemGroup(placement: .primaryAction) { Button { Exporter.exportXlsx(store: store, ui: ui) } label: { Label("Export für Numbers", systemImage: "tablecells") } .help("Aktuellen Stand als Tabelle für Numbers speichern (⌘E)") Button { ui.newEntry() } label: { Label("Neuer Eintrag", systemImage: "plus") } .help("Neuer Eintrag (⌘N)") } } .sheet(item: $ui.draft) { draft in EntrySheet(entry: draft) } .overlay(alignment: .bottom) { if let t = ui.toast ?? store.lastError { Text(t) .font(.callout) .padding(.horizontal, 14).padding(.vertical, 8) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 8)) .padding(.bottom, 18) .transition(.opacity) } } .animation(.easeInOut(duration: 0.2), value: ui.toast) .tint(Theme.tokiwa) } } extension Entry { var isNew: Bool { id.isEmpty } }