mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-31 01:20:57 +00:00
Move delete to use a confirmation dialog + various other fixes. (#645)
This commit is contained in:
parent
c5052dd457
commit
fb4dec383b
@ -4,27 +4,27 @@ import Foundation
|
|||||||
public struct AnySecret: Secret, @unchecked Sendable {
|
public struct AnySecret: Secret, @unchecked Sendable {
|
||||||
|
|
||||||
public let base: any Secret
|
public let base: any Secret
|
||||||
private let hashable: AnyHashable
|
|
||||||
private let _id: () -> AnyHashable
|
private let _id: () -> AnyHashable
|
||||||
private let _name: () -> String
|
private let _name: () -> String
|
||||||
private let _publicKey: () -> Data
|
private let _publicKey: () -> Data
|
||||||
private let _attributes: () -> Attributes
|
private let _attributes: () -> Attributes
|
||||||
|
private let _eq: (AnySecret) -> Bool
|
||||||
|
|
||||||
public init<T>(_ secret: T) where T: Secret {
|
public init<T>(_ secret: T) where T: Secret {
|
||||||
if let secret = secret as? AnySecret {
|
if let secret = secret as? AnySecret {
|
||||||
base = secret.base
|
base = secret.base
|
||||||
hashable = secret.hashable
|
|
||||||
_id = secret._id
|
_id = secret._id
|
||||||
_name = secret._name
|
_name = secret._name
|
||||||
_publicKey = secret._publicKey
|
_publicKey = secret._publicKey
|
||||||
_attributes = secret._attributes
|
_attributes = secret._attributes
|
||||||
|
_eq = secret._eq
|
||||||
} else {
|
} else {
|
||||||
base = secret
|
base = secret
|
||||||
self.hashable = secret
|
|
||||||
_id = { secret.id as AnyHashable }
|
_id = { secret.id as AnyHashable }
|
||||||
_name = { secret.name }
|
_name = { secret.name }
|
||||||
_publicKey = { secret.publicKey }
|
_publicKey = { secret.publicKey }
|
||||||
_attributes = { secret.attributes }
|
_attributes = { secret.attributes }
|
||||||
|
_eq = { secret == $0.base as? T }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,11 +45,11 @@ public struct AnySecret: Secret, @unchecked Sendable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool {
|
public static func == (lhs: AnySecret, rhs: AnySecret) -> Bool {
|
||||||
lhs.hashable == rhs.hashable
|
lhs._eq(rhs)
|
||||||
}
|
}
|
||||||
|
|
||||||
public func hash(into hasher: inout Hasher) {
|
public func hash(into hasher: inout Hasher) {
|
||||||
hashable.hash(into: &hasher)
|
id.hash(into: &hasher)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,10 @@ extension SecureEnclave {
|
|||||||
self.attributes = attributes
|
self.attributes = attributes
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static func ==(lhs: Self, rhs: Self) -> Bool {
|
||||||
|
lhs.id == rhs.id
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
50B8550D24138C4F009958AC /* DeleteSecretView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50B8550C24138C4F009958AC /* DeleteSecretView.swift */; };
|
||||||
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
50BB046B2418AAAE00D6E079 /* EmptyStoreView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */; };
|
||||||
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
50C385A52407A76D00AF2719 /* SecretDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50C385A42407A76D00AF2719 /* SecretDetailView.swift */; };
|
||||||
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */; };
|
||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXContainerItemProxy section */
|
/* Begin PBXContainerItemProxy section */
|
||||||
@ -140,6 +141,7 @@
|
|||||||
50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = "<group>"; };
|
50B8550C24138C4F009958AC /* DeleteSecretView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteSecretView.swift; sourceTree = "<group>"; };
|
||||||
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
50BB046A2418AAAE00D6E079 /* EmptyStoreView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyStoreView.swift; sourceTree = "<group>"; };
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecretDetailView.swift; sourceTree = "<group>"; };
|
||||||
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionButtonStyle.swift; sourceTree = "<group>"; };
|
||||||
/* End PBXFileReference section */
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
/* Begin PBXFrameworksBuildPhase section */
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
@ -241,6 +243,7 @@
|
|||||||
children = (
|
children = (
|
||||||
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
50617D8423FCE48E0099B055 /* ContentView.swift */,
|
||||||
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
5065E312295517C500E16645 /* ToolbarButtonStyle.swift */,
|
||||||
|
50CF4ABB2E601B0F005588DC /* ActionButtonStyle.swift */,
|
||||||
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
5079BA0E250F29BF00EA86F4 /* StoreListView.swift */,
|
||||||
50153E21250DECA300525160 /* SecretListItemView.swift */,
|
50153E21250DECA300525160 /* SecretListItemView.swift */,
|
||||||
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
50C385A42407A76D00AF2719 /* SecretDetailView.swift */,
|
||||||
@ -435,6 +438,7 @@
|
|||||||
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
5066A6C22516F303004B5A36 /* SetupView.swift in Sources */,
|
||||||
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
5065E313295517C500E16645 /* ToolbarButtonStyle.swift in Sources */,
|
||||||
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
50617D8523FCE48E0099B055 /* ContentView.swift in Sources */,
|
||||||
|
50CF4ABC2E601B0F005588DC /* ActionButtonStyle.swift in Sources */,
|
||||||
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
50571E0324393C2600F76F6C /* JustUpdatedChecker.swift in Sources */,
|
||||||
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
5079BA0F250F29BF00EA86F4 /* StoreListView.swift in Sources */,
|
||||||
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
50617DD223FCEFA90099B055 /* PreviewStore.swift in Sources */,
|
||||||
|
24
Sources/Secretive/Views/ActionButtonStyle.swift
Normal file
24
Sources/Secretive/Views/ActionButtonStyle.swift
Normal 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())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -103,6 +103,7 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
|||||||
showing = false
|
showing = false
|
||||||
}
|
}
|
||||||
Button(.createSecretCreateButton, action: save)
|
Button(.createSecretCreateButton, action: save)
|
||||||
|
.primary()
|
||||||
.disabled(name.isEmpty)
|
.disabled(name.isEmpty)
|
||||||
}
|
}
|
||||||
.padding()
|
.padding()
|
||||||
|
@ -1,63 +1,56 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import SecretKit
|
import SecretKit
|
||||||
|
|
||||||
struct DeleteSecretView<StoreType: SecretStoreModifiable>: View {
|
extension View {
|
||||||
|
|
||||||
@State var store: StoreType
|
func showingDeleteConfirmation(isPresented: Binding<Bool>, _ secret: AnySecret, _ store: AnySecretStoreModifiable?, dismissalBlock: @escaping (Bool) -> ()) -> some View {
|
||||||
let secret: StoreType.SecretType
|
modifier(DeleteSecretConfirmationModifier(isPresented: isPresented, secret: secret, store: store, dismissalBlock: dismissalBlock))
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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() {
|
func delete() {
|
||||||
Task {
|
Task {
|
||||||
do {
|
do {
|
||||||
try await store.delete(secret: secret)
|
try await store!.delete(secret: secret)
|
||||||
dismissalBlock(true)
|
dismissalBlock(true)
|
||||||
} catch {
|
} catch {
|
||||||
errorText = error.localizedDescription
|
errorText = error.localizedDescription
|
||||||
|
@ -12,18 +12,6 @@ struct SecretListItemView: View {
|
|||||||
var deletedSecret: (AnySecret) -> Void
|
var deletedSecret: (AnySecret) -> Void
|
||||||
var renamedSecret: (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 {
|
var body: some View {
|
||||||
NavigationLink(value: secret) {
|
NavigationLink(value: secret) {
|
||||||
if secret.authenticationRequirement.required {
|
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 let modifiable = store as? AnySecretStoreModifiable {
|
||||||
if isDeleting {
|
EditSecretView(store: modifiable, secret: secret) { renamed in
|
||||||
DeleteSecretView(store: modifiable, secret: secret) { deleted in
|
isRenaming = false
|
||||||
isDeleting = false
|
if renamed {
|
||||||
if deleted {
|
renamedSecret(secret)
|
||||||
deletedSecret(secret)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if isRenaming {
|
|
||||||
EditSecretView(store: modifiable, secret: secret) { renamed in
|
|
||||||
isRenaming = false
|
|
||||||
if renamed {
|
|
||||||
renamedSecret(secret)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,8 @@ struct StoreListView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func secretRenamed(secret: AnySecret) {
|
private func secretRenamed(secret: AnySecret) {
|
||||||
|
// Toggle so name updates in list.
|
||||||
|
activeSecret = nil
|
||||||
activeSecret = secret
|
activeSecret = secret
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,7 +58,7 @@ struct StoreListView: View {
|
|||||||
extension StoreListView {
|
extension StoreListView {
|
||||||
|
|
||||||
private var nextDefaultSecret: AnySecret? {
|
private var nextDefaultSecret: AnySecret? {
|
||||||
return storeList.stores.first(where: { !$0.secrets.isEmpty })?.secrets.first
|
return storeList.allSecrets.first
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user