Completes Phase 4 (Task 17) -- ContentView now routes between UnlockView
and the vault UI (NavigationStack on iOS, 3-column NavigationSplitView on
macOS) instead of the leftover SwiftData template, finally resolving the
long-standing "cannot find type Item" build break. Vault locks on
scene-background (iOS) / resign-active (macOS).
Fixes two more real bugs surfaced now that everything actually links
together: EntryEditViewModel.save's dismiss closure needed @escaping
(used inside Task {}), and UnlockView's UTType(filenameExtension:)
needed `import UniformTypeIdentifiers`.
Also restores MyPass.entitlements, which had regressed to an empty
App Group array with Keychain Sharing missing entirely since the
"wire up AutoFill extension target" commit -- likely clobbered by
Xcode reconciling capabilities at some point without a rebuild in
between to catch it. Verified clean `xcodebuild` on both iOS Simulator
and macOS destinations, and `swift test` still passes all 18 tests.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WYqycDFsynHH9VnnK7LNSf
87 lines
2.9 KiB
Swift
87 lines
2.9 KiB
Swift
//
|
|
// EntryEditViewModel.swift
|
|
// MyPass
|
|
//
|
|
|
|
import Foundation
|
|
import Combine
|
|
import SwiftUI
|
|
import MyPassCore
|
|
|
|
@MainActor
|
|
final class EntryEditViewModel: ObservableObject {
|
|
@Published var title: String
|
|
@Published var username: String
|
|
@Published var password: String
|
|
@Published var url: String
|
|
@Published var notes: String
|
|
@Published var customFields: [CustomField]
|
|
@Published var errorMessage: String?
|
|
@Published var isSaving: Bool = false
|
|
|
|
private let session: VaultSession
|
|
private let groupId: UUID?
|
|
private let existingEntry: Entry?
|
|
|
|
/// `groupId` is required when adding a new entry, and unused when editing an existing one
|
|
/// (an existing entry's group is looked up internally by `VaultSession.updateEntry`).
|
|
init(session: VaultSession, groupId: UUID? = nil, existing: Entry? = nil) {
|
|
self.session = session
|
|
self.groupId = groupId
|
|
self.existingEntry = existing
|
|
title = existing?.title ?? ""
|
|
username = existing?.username ?? ""
|
|
password = existing?.password.reveal() ?? ""
|
|
url = existing?.url ?? ""
|
|
notes = existing?.notes ?? ""
|
|
customFields = existing?.customFields ?? []
|
|
}
|
|
|
|
var isEditing: Bool { existingEntry != nil }
|
|
|
|
func save(dismiss: @escaping () -> Void) {
|
|
Task {
|
|
isSaving = true
|
|
errorMessage = nil
|
|
do {
|
|
if var entry = existingEntry {
|
|
entry.title = title
|
|
entry.username = username
|
|
entry.password = ProtectedString(password, isProtected: true)
|
|
entry.url = url
|
|
entry.notes = notes
|
|
entry.customFields = customFields
|
|
try session.updateEntry(entry)
|
|
} else {
|
|
guard let groupId else {
|
|
errorMessage = "No group selected for the new entry."
|
|
isSaving = false
|
|
return
|
|
}
|
|
let entry = Entry(
|
|
title: title,
|
|
username: username,
|
|
password: ProtectedString(password, isProtected: true),
|
|
url: url,
|
|
notes: notes,
|
|
customFields: customFields
|
|
)
|
|
try session.addEntry(entry, toGroupId: groupId)
|
|
}
|
|
dismiss()
|
|
} catch {
|
|
errorMessage = error.localizedDescription
|
|
}
|
|
isSaving = false
|
|
}
|
|
}
|
|
|
|
func addCustomField() {
|
|
customFields.append(CustomField(key: "", value: ProtectedString("", isProtected: false)))
|
|
}
|
|
|
|
func removeCustomField(at offsets: IndexSet) {
|
|
customFields.remove(atOffsets: offsets)
|
|
}
|
|
}
|