Files
keevault/MyPass/ContentView.swift
T

134 lines
3.7 KiB
Swift
Raw Normal View History

2026-05-21 22:04:13 +02:00
//
// ContentView.swift
// MyPass
//
// Created by Christophe Vila on 21/05/2026.
//
import SwiftUI
import MyPassCore
#if os(macOS)
import AppKit
#endif
2026-05-21 22:04:13 +02:00
struct ContentView: View {
@StateObject private var session = VaultSession()
2026-05-21 22:04:13 +02:00
var body: some View {
SwiftUI.Group {
if session.isLocked {
UnlockView(vm: UnlockViewModel(session: session))
} else {
vaultView
2026-05-21 22:04:13 +02:00
}
}
.onReceive(
NotificationCenter.default.publisher(for: sceneBackgroundNotification)
) { _ in
session.lock()
}
2026-05-21 22:04:13 +02:00
}
@ViewBuilder
private var vaultView: some View {
let vaultVM = VaultViewModel(session: session)
#if os(macOS)
MacVaultView(vm: vaultVM)
#else
NavigationStack {
if let root = session.database?.root {
GroupBrowserView(vm: vaultVM, group: root)
2026-05-21 22:04:13 +02:00
}
}
#endif
}
private var sceneBackgroundNotification: Notification.Name {
#if os(iOS)
UIScene.didEnterBackgroundNotification
#else
NSApplication.didResignActiveNotification
#endif
2026-05-21 22:04:13 +02:00
}
}
// MARK: - macOS 3-column layout
#if os(macOS)
private struct MacVaultView: View {
@ObservedObject var vm: VaultViewModel
@State private var selectedGroup: MyPassCore.Group?
@State private var selectedEntry: Entry?
@State private var showAddEntry = false
private var activeGroup: MyPassCore.Group? { selectedGroup ?? vm.session.database?.root }
2026-05-21 22:04:13 +02:00
var body: some View {
NavigationSplitView {
GroupSidebarView(vm: vm, selectedGroup: $selectedGroup)
} content: {
if vm.isSearching {
SearchView(vm: vm, selectedEntry: $selectedEntry)
} else if let group = activeGroup {
entryList(for: group)
} else {
Text("Select a group").foregroundStyle(.secondary)
}
2026-05-21 22:04:13 +02:00
} detail: {
if let entry = selectedEntry {
EntryDetailView(entry: entry, session: vm.session)
} else {
Text("Select an entry").foregroundStyle(.secondary)
}
}
.searchable(text: $vm.searchQuery, prompt: "Search all entries…")
.sheet(isPresented: $showAddEntry) {
if let group = activeGroup {
EntryEditView(vm: EntryEditViewModel(session: vm.session, groupId: group.id))
}
}
}
private func entryList(for group: MyPassCore.Group) -> some View {
List(group.entries, selection: $selectedEntry) { entry in
EntryListRow(entry: entry).tag(entry)
}
.navigationTitle(group.name)
.toolbar {
ToolbarItem {
Button { showAddEntry = true } label: { Image(systemName: "plus") }
}
2026-05-21 22:04:13 +02:00
}
}
}
private struct GroupSidebarView: View {
@ObservedObject var vm: VaultViewModel
@Binding var selectedGroup: MyPassCore.Group?
var body: some View {
List(selection: $selectedGroup) {
if let root = vm.session.database?.root {
GroupNode(group: root)
}
}
.navigationTitle("Groups")
}
2026-05-21 22:04:13 +02:00
}
private struct GroupNode: View {
let group: MyPassCore.Group
var body: some View {
if group.subgroups.isEmpty {
Label(group.name, systemImage: "folder").tag(group)
} else {
DisclosureGroup {
ForEach(group.subgroups) { sub in GroupNode(group: sub) }
} label: {
Label(group.name, systemImage: "folder").tag(group)
}
}
}
}
#endif