2026-05-21 22:04:13 +02:00
|
|
|
//
|
|
|
|
|
// ContentView.swift
|
|
|
|
|
// MyPass
|
|
|
|
|
//
|
|
|
|
|
// Created by Christophe Vila on 21/05/2026.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import SwiftUI
|
2026-09-19 16:02:23 +02:00
|
|
|
import MyPassCore
|
|
|
|
|
#if os(macOS)
|
|
|
|
|
import AppKit
|
|
|
|
|
#endif
|
2026-05-21 22:04:13 +02:00
|
|
|
|
|
|
|
|
struct ContentView: View {
|
2026-09-19 16:02:23 +02:00
|
|
|
@StateObject private var session = VaultSession()
|
2026-05-21 22:04:13 +02:00
|
|
|
|
|
|
|
|
var body: some View {
|
2026-09-19 16:02:23 +02:00
|
|
|
SwiftUI.Group {
|
|
|
|
|
if session.isLocked {
|
|
|
|
|
UnlockView(vm: UnlockViewModel(session: session))
|
|
|
|
|
} else {
|
|
|
|
|
vaultView
|
2026-05-21 22:04:13 +02:00
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 16:02:23 +02:00
|
|
|
.onReceive(
|
|
|
|
|
NotificationCenter.default.publisher(for: sceneBackgroundNotification)
|
|
|
|
|
) { _ in
|
|
|
|
|
session.lock()
|
|
|
|
|
}
|
2026-05-21 22:04:13 +02:00
|
|
|
}
|
|
|
|
|
|
2026-09-19 16:02:23 +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
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 16:02:23 +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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 16:02:23 +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 {
|
2026-09-19 16:02:23 +02:00
|
|
|
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: {
|
2026-09-19 16:02:23 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 16:02:23 +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
|
|
|
}
|
2026-09-19 16:02:23 +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
|