diff --git a/MyPass/ContentView.swift b/MyPass/ContentView.swift index ebd9db4..8ee1416 100644 --- a/MyPass/ContentView.swift +++ b/MyPass/ContentView.swift @@ -19,7 +19,7 @@ struct ContentView: View { if session.isLocked { UnlockView(vm: UnlockViewModel(session: session)) } else { - vaultView + VaultRootView(session: session) } } .onReceive( @@ -29,20 +29,6 @@ struct ContentView: View { } } - @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) - } - } - #endif - } - private var sceneBackgroundNotification: Notification.Name { #if os(iOS) UIScene.didEnterBackgroundNotification @@ -52,82 +38,41 @@ struct ContentView: View { } } -// MARK: - macOS 3-column layout +/// 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 + @State private var columnVisibility: NavigationSplitViewVisibility = .all -#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 } + init(session: VaultSession) { + _vaultVM = StateObject(wrappedValue: VaultViewModel(session: session)) + _filterVM = StateObject(wrappedValue: GroupFilterViewModel(session: session)) + } 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) + NavigationSplitView(columnVisibility: $columnVisibility) { + if let root = vaultVM.session.database?.root { + GroupFilterView(vm: filterVM, rootGroup: root) } } detail: { - if let entry = selectedEntry { - EntryDetailView(entry: entry, session: vm.session) - } else { - Text("Select an entry").foregroundStyle(.secondary) + NavigationStack { + EntryListView(vm: vaultVM) + .toolbar { + ToolbarItem(placement: .navigation) { + Button { + columnVisibility = columnVisibility == .all ? .detailOnly : .all + } label: { + Image(systemName: "line.3.horizontal") + } + } + } } } - .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") } - } + .onChange(of: filterVM.selectedGroupIds) { _, newValue in + vaultVM.selectedGroupIds = newValue } } } - -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") - } -} - -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