// // ContentView.swift // MyPass // // Created by Christophe Vila on 21/05/2026. // import SwiftUI import MyPassCore #if os(macOS) import AppKit #endif struct ContentView: View { @StateObject private var session = VaultSession() var body: some View { SwiftUI.Group { if session.isLocked { UnlockView(vm: UnlockViewModel(session: session)) } else { VaultRootView(session: session) } } .onReceive( NotificationCenter.default.publisher(for: sceneBackgroundNotification) ) { _ in session.lock() } } private var sceneBackgroundNotification: Notification.Name { #if os(iOS) UIScene.didEnterBackgroundNotification #else NSApplication.didResignActiveNotification #endif } } /// Owns the vault/group-filter view models for as long as the vault stays unlocked -- their /// state (search query, group filter selection) persists across re-renders but resets if the /// vault locks and is unlocked again (a fresh VaultRootView is created), which matches the /// expected "locking clears your filter" behavior. private struct VaultRootView: View { @StateObject private var vaultVM: VaultViewModel @StateObject private var filterVM: GroupFilterViewModel #if os(macOS) @State private var columnVisibility: NavigationSplitViewVisibility = .all #else @State private var showFilterSheet = false #endif init(session: VaultSession) { _vaultVM = StateObject(wrappedValue: VaultViewModel(session: session)) _filterVM = StateObject(wrappedValue: GroupFilterViewModel(session: session)) } var body: some View { content .onChange(of: filterVM.selectedGroupIds) { _, newValue in vaultVM.selectedGroupIds = newValue } .onChange(of: filterVM.lastToggledGroupId) { _, newValue in vaultVM.lastToggledGroupId = newValue } } #if os(macOS) // macOS has room for a persistent sidebar; NavigationSplitView's columnVisibility // toggle works correctly at regular width. private var content: some View { NavigationSplitView(columnVisibility: $columnVisibility) { if let root = vaultVM.session.database?.root { GroupFilterView(vm: filterVM, rootGroup: root) } } detail: { NavigationStack { EntryListView(vm: vaultVM) .toolbar { ToolbarItem(placement: .navigation) { Button { columnVisibility = columnVisibility == .all ? .detailOnly : .all } label: { Image(systemName: "line.3.horizontal") } } } } } } #else // NavigationSplitView's compact-width collapse only pushes to detail when the // sidebar drives navigation via row selection. Our sidebar is a multi-select // filter, not a picker, so on iPhone it would just show the sidebar forever with // no way to reach the entry list. Use a plain stack with the entry list as the // root, and present the group filter as a sheet instead. private var content: some View { NavigationStack { EntryListView(vm: vaultVM) .toolbar { ToolbarItem(placement: .navigation) { Button { showFilterSheet = true } label: { Image(systemName: "line.3.horizontal") } } } } .sheet(isPresented: $showFilterSheet) { NavigationStack { if let root = vaultVM.session.database?.root { GroupFilterView(vm: filterVM, rootGroup: root) .toolbar { ToolbarItem(placement: .confirmationAction) { Button("Done") { showFilterSheet = false } } } } } } } #endif }