Merge branch 'main' into newsetup

This commit is contained in:
Max Goedjen
2025-08-27 23:50:06 -07:00
27 changed files with 421 additions and 479 deletions

View File

@@ -61,13 +61,17 @@ extension Preview {
var name: String { "Modifiable Preview Store" }
let secrets: [Secret]
var supportedKeyTypes: [KeyType] {
[
.init(algorithm: .ecdsa, size: 256),
.init(algorithm: .mldsa, size: 65),
.init(algorithm: .mldsa, size: 87),
]
if #available(macOS 26, *) {
[
.ecdsa256,
.mldsa65,
.mldsa87,
]
} else {
[.ecdsa256]
}
}
init(secrets: [Secret]) {
self.secrets = secrets
}
@@ -92,7 +96,8 @@ extension Preview {
}
func create(name: String, attributes: Attributes) throws {
func create(name: String, attributes: Attributes) throws -> Secret {
fatalError()
}
func delete(secret: Preview.Secret) throws {

View File

@@ -0,0 +1,24 @@
import SwiftUI
struct PrimaryButtonModifier: ViewModifier {
@Environment(\.colorScheme) var colorScheme
func body(content: Content) -> some View {
// Tinted glass prominent is really hard to read on 26.0.
if #available(macOS 26.0, *), colorScheme == .dark {
content.buttonStyle(.glassProminent)
} else {
content.buttonStyle(.borderedProminent)
}
}
}
extension View {
func primary() -> some View {
modifier(PrimaryButtonModifier())
}
}

View File

@@ -103,6 +103,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
showing = false
}
Button(.createSecretCreateButton, action: save)
.primary()
.disabled(name.isEmpty)
}
.padding()

View File

@@ -1,63 +1,56 @@
import SwiftUI
import SecretKit
struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
extension View {
@State var store: StoreType
let secret: StoreType.SecretType
var dismissalBlock: (Bool) -> ()
@State private var confirm = ""
@State var errorText: String?
var body: some View {
VStack {
HStack {
Image(nsImage: NSApplication.shared.applicationIconImage)
.resizable()
.frame(width: 64, height: 64)
.padding()
VStack {
HStack {
Text(.deleteConfirmationTitle(secretName: secret.name)).bold()
Spacer()
}
HStack {
Text(.deleteConfirmationDescription(secretName: secret.name, confirmSecretName: secret.name))
Spacer()
}
HStack {
Text(.deleteConfirmationConfirmNameLabel)
TextField(secret.name, text: $confirm)
}
}
}
if let errorText {
Text(verbatim: errorText)
.foregroundStyle(.red)
.font(.callout)
}
HStack {
Spacer()
Button(.deleteConfirmationDeleteButton, action: delete)
.disabled(confirm != secret.name)
Button(.deleteConfirmationCancelButton) {
dismissalBlock(false)
}
.keyboardShortcut(.cancelAction)
}
}
.padding()
.frame(minWidth: 400)
.onExitCommand {
dismissalBlock(false)
}
func showingDeleteConfirmation(isPresented: Binding<Bool>, _ secret: AnySecret, _ store: AnySecretStoreModifiable?, dismissalBlock: @escaping (Bool) -> ()) -> some View {
modifier(DeleteSecretConfirmationModifier(isPresented: isPresented, secret: secret, store: store, dismissalBlock: dismissalBlock))
}
}
struct DeleteSecretConfirmationModifier: ViewModifier {
var isPresented: Binding<Bool>
var secret: AnySecret
var store: AnySecretStoreModifiable?
var dismissalBlock: (Bool) -> ()
@State var confirmedSecretName = ""
@State private var errorText: String?
func body(content: Content) -> some View {
content
.confirmationDialog(
.deleteConfirmationTitle(secretName: secret.name),
isPresented: isPresented,
titleVisibility: .visible,
actions: {
TextField(secret.name, text: $confirmedSecretName)
if let errorText {
Text(verbatim: errorText)
.foregroundStyle(.red)
.font(.callout)
}
Button(.deleteConfirmationDeleteButton, action: delete)
.disabled(confirmedSecretName != secret.name)
Button(.deleteConfirmationCancelButton, role: .cancel) {
dismissalBlock(false)
}
},
message: {
Text(.deleteConfirmationDescription(secretName: secret.name, confirmSecretName: secret.name))
}
)
.dialogIcon(Image(systemName: "lock.trianglebadge.exclamationmark.fill"))
.onExitCommand {
dismissalBlock(false)
}
}
func delete() {
Task {
do {
try await store.delete(secret: secret)
try await store!.delete(secret: secret)
dismissalBlock(true)
} catch {
errorText = error.localizedDescription

View File

@@ -12,18 +12,6 @@ struct SecretListItemView: View {
var deletedSecret: (AnySecret) -> Void
var renamedSecret: (AnySecret) -> Void
private var showingPopup: Binding<Bool> {
Binding(
get: { isDeleting || isRenaming },
set: {
if $0 == false {
isDeleting = false
isRenaming = false
}
}
)
}
var body: some View {
NavigationLink(value: secret) {
if secret.authenticationRequirement.required {
@@ -48,21 +36,17 @@ struct SecretListItemView: View {
}
}
}
.sheet(isPresented: showingPopup) {
.showingDeleteConfirmation(isPresented: $isDeleting, secret, store as? AnySecretStoreModifiable) { deleted in
if deleted {
deletedSecret(secret)
}
}
.sheet(isPresented: $isRenaming) {
if let modifiable = store as? AnySecretStoreModifiable {
if isDeleting {
DeleteSecretView(store: modifiable, secret: secret) { deleted in
isDeleting = false
if deleted {
deletedSecret(secret)
}
}
} else if isRenaming {
EditSecretView(store: modifiable, secret: secret) { renamed in
isRenaming = false
if renamed {
renamedSecret(secret)
}
EditSecretView(store: modifiable, secret: secret) { renamed in
isRenaming = false
if renamed {
renamedSecret(secret)
}
}
}

View File

@@ -12,6 +12,8 @@ struct StoreListView: View {
}
private func secretRenamed(secret: AnySecret) {
// Toggle so name updates in list.
activeSecret = nil
activeSecret = secret
}
@@ -56,7 +58,7 @@ struct StoreListView: View {
extension StoreListView {
private var nextDefaultSecret: AnySecret? {
return storeList.stores.first(where: { !$0.secrets.isEmpty })?.secrets.first
return storeList.allSecrets.first
}
}