feat: copper accent theme, always-expanded group filter, app icon; rename MyPass to KeeVault
- Custom copper AccentColor and warm VaultBackground color assets (light/dark), applied to both the main app and the AutoFill extension (which needed its own copy of the asset catalog plus ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME and an explicit .tint() modifier, since extensions don't pick up the app-wide accent color the same way a full SwiftUI App/Scene does). - GroupFilterView no longer uses DisclosureGroup for subgroups -- the whole tree renders always-expanded (indented per depth) so active filters are visible at a glance instead of requiring tap-to-expand. - Added a placeholder app icon (padlock glyph, light/dark/tinted variants plus macOS sizes) -- previously the icon slot existed but had no actual images, which blocks App Store/TestFlight archive validation. - Added ITSAppUsesNonExemptEncryption = NO (standard encryption only, via KeePassKit) to skip the export-compliance prompt on each upload. - Renamed the app from MyPass to KeeVault throughout: Xcode project/targets/ schemes, the MyPassCore package (now KeeVaultCore) and every import site, folder and file names, bundle identifiers (org.antiloop222.keevault) and their App Group/Keychain-group entitlements, and remaining UI/string references -- MyPass was already taken as an App Store app name.
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
//
|
||||
// ContentView.swift
|
||||
// KeeVault
|
||||
//
|
||||
// Created by Christophe Vila on 21/05/2026.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import KeeVaultCore
|
||||
#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)
|
||||
}
|
||||
}
|
||||
.background(Color("VaultBackground"))
|
||||
.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
|
||||
}
|
||||
Reference in New Issue
Block a user