mirror of
https://github.com/maxgoedjen/secretive.git
synced 2026-09-27 18:48:00 +02:00
Pending request view (#821)
* Splitting out auth context stuff in preparation for batch * Add uncommitted authhandler rename * Split out auth/nonauth paths * Messy auth batching infra WIP * Restrict connectionAcceptedNotifications to creating filehandle * WIP * WIP * WIP * WIP * WIP * Reenable multilineinfoview * WIP * WIP * Fixing up tests * WIP * Return empty bind response on parse throw * JSON project * JSON Project * WIP * WIP * Almost done * Tests * Cleanup test calls * Fixme * Cleanup * Localized strings
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import Foundation
|
||||
import SecretKit
|
||||
import LocalAuthentication
|
||||
|
||||
enum Preview {}
|
||||
|
||||
@@ -38,17 +39,10 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
||||
nil
|
||||
}
|
||||
|
||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||
}
|
||||
|
||||
func reloadSecrets() {
|
||||
}
|
||||
|
||||
@@ -82,16 +76,10 @@ extension Preview {
|
||||
self.init(secrets: new)
|
||||
}
|
||||
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?) throws -> Data {
|
||||
func sign(data: Data, with secret: Preview.Secret, for provenance: SigningRequestProvenance, target: SigningRequestTarget?, context: LAContext?) async throws -> Data {
|
||||
return data
|
||||
}
|
||||
|
||||
func existingPersistedAuthenticationContext(secret: Preview.Secret) -> PersistedAuthenticationContext? {
|
||||
nil
|
||||
}
|
||||
|
||||
func persistAuthentication(secret: Preview.Secret, forDuration duration: TimeInterval) throws {
|
||||
}
|
||||
|
||||
func reloadSecrets() {
|
||||
}
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
struct PrimaryButtonModifier: ViewModifier {
|
||||
|
||||
@Environment(\.colorScheme) var colorScheme
|
||||
@Environment(\.isEnabled) var isEnabled
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
// Tinted glass prominent is really hard to read on 26.0.
|
||||
if #available(macOS 26.0, *), colorScheme == .dark, isEnabled {
|
||||
content.buttonStyle(.glassProminent)
|
||||
} else {
|
||||
content.buttonStyle(.borderedProminent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func primaryButton() -> some View {
|
||||
modifier(PrimaryButtonModifier())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ToolbarCircleButtonModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
content
|
||||
.glassEffect(.regular.tint(.white.opacity(0.1)), in: .circle)
|
||||
} else {
|
||||
content
|
||||
.buttonStyle(.borderless)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func toolbarCircleButton() -> some View {
|
||||
modifier(ToolbarCircleButtonModifier())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct NormalButtonModifier: ViewModifier {
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
content.buttonStyle(.glass)
|
||||
} else {
|
||||
content.buttonStyle(.bordered)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func normalButton() -> some View {
|
||||
modifier(NormalButtonModifier())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct DangerButtonModifier: 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)
|
||||
.tint(.red)
|
||||
.foregroundStyle(.white)
|
||||
} else {
|
||||
content.buttonStyle(.borderedProminent)
|
||||
.tint(.red)
|
||||
.foregroundStyle(.white)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
func danger() -> some View {
|
||||
modifier(DangerButtonModifier())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -28,6 +28,13 @@ struct SecretDetailView<SecretType: Secret>: View {
|
||||
image: Image(systemName: "touchid"),
|
||||
text: keyWriter.openSSHMD5Fingerprint(secret: secret)
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 20)
|
||||
CopyableView(
|
||||
title: .secretDetailPublicKeyLabel,
|
||||
image: Image(systemName: "key"),
|
||||
text: keyWriter.openSSHString(secret: secret)
|
||||
)
|
||||
Spacer()
|
||||
.frame(height: 20)
|
||||
CopyableView(
|
||||
@@ -39,18 +46,14 @@ struct SecretDetailView<SecretType: Secret>: View {
|
||||
if !certificates.isEmpty {
|
||||
Spacer()
|
||||
.frame(height: 20)
|
||||
MultilineInfoView(
|
||||
title: .secretDetailCertificatePathLabel,
|
||||
image: Image(
|
||||
systemName: "checkmark.seal.text.page"
|
||||
),
|
||||
items: certificates.map({ certificate in
|
||||
MultilineInfoView.Item(
|
||||
text: certificate.name,
|
||||
action: (Image(systemName: "chevron.forward"), { navigateToCertificate?(certificate) })
|
||||
)
|
||||
})
|
||||
)
|
||||
MultilineInfoView(title: .secretDetailCertificatePathLabel, image: Image(systemName: "checkmark.seal.text.page")) {
|
||||
ForEach(certificates) { certificate in
|
||||
Text(certificate.name)
|
||||
.multilineItemAction(image: Image(systemName: "chevron.forward")) {
|
||||
navigateToCertificate?(certificate)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ struct StoreListView: View {
|
||||
}
|
||||
}
|
||||
if !certificateStore.certificates.isEmpty {
|
||||
Section("Certificates") {
|
||||
Section(.certificatesSectionTitle) {
|
||||
ForEach(certificateStore.certificates) { certificate in
|
||||
CertificateListItemView(
|
||||
certificate: certificate,
|
||||
|
||||
@@ -186,7 +186,7 @@ fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
}
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
return base.mix(with: .accentColor, by: 0)
|
||||
case .hovering:
|
||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
||||
case .clicking, .dragging:
|
||||
|
||||
@@ -1,170 +0,0 @@
|
||||
import SwiftUI
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
struct MultilineInfoView: View {
|
||||
|
||||
struct Item {
|
||||
let text: String
|
||||
let action: (Image, () -> Void)?
|
||||
}
|
||||
|
||||
var title: LocalizedStringResource
|
||||
var image: Image
|
||||
var items: [Item]
|
||||
|
||||
init(title: LocalizedStringResource, image: Image, items: [Item]) {
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.items = items
|
||||
}
|
||||
|
||||
init(title: LocalizedStringResource, image: Image, items: [String]) {
|
||||
self.title = title
|
||||
self.image = image
|
||||
self.items = items.map({ Item(text: $0, action: nil) })
|
||||
}
|
||||
|
||||
@State private var interactionState: InteractionState = .normal
|
||||
@State private var interactionStateIndex: Int?
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
HStack {
|
||||
image
|
||||
.renderingMode(.template)
|
||||
.imageScale(.large)
|
||||
.foregroundColor(primaryTextColor)
|
||||
Text(title)
|
||||
.font(.headline)
|
||||
.foregroundColor(primaryTextColor)
|
||||
Spacer()
|
||||
}
|
||||
.safeAreaPadding(20)
|
||||
ForEach(Array(items.enumerated()), id: \.offset) { item in
|
||||
Divider()
|
||||
.ignoresSafeArea()
|
||||
.opacity(item.offset == 0 ? 1 : 0.75)
|
||||
HStack {
|
||||
Text(item.element.text)
|
||||
Spacer()
|
||||
if let (image, _) = item.element.action {
|
||||
image
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.safeAreaPadding(20)
|
||||
.onHover { hovering in
|
||||
withAnimation {
|
||||
guard item.element.action != nil else { return }
|
||||
interactionState = hovering ? .hovering : .normal
|
||||
interactionStateIndex = item.offset
|
||||
}
|
||||
}
|
||||
.gesture(
|
||||
TapGesture()
|
||||
.onEnded {
|
||||
item.element.action?.1()
|
||||
withAnimation {
|
||||
interactionState = .normal
|
||||
interactionStateIndex = nil
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
}
|
||||
._background(interactionState: .normal)
|
||||
.frame(minWidth: 150, maxWidth: .infinity)
|
||||
}
|
||||
|
||||
var primaryTextColor: Color {
|
||||
switch interactionState {
|
||||
case .normal, .hovering:
|
||||
return Color(.textColor)
|
||||
}
|
||||
}
|
||||
|
||||
var secondaryTextColor: Color {
|
||||
switch interactionState {
|
||||
case .normal, .hovering:
|
||||
return Color(.secondaryLabelColor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate enum InteractionState {
|
||||
case normal, hovering
|
||||
}
|
||||
|
||||
extension View {
|
||||
|
||||
fileprivate func _background(interactionState: InteractionState, cornerRadius: Double = 15) -> some View {
|
||||
modifier(BackgroundViewModifier(interactionState: interactionState, cornerRadius: cornerRadius))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fileprivate struct BackgroundViewModifier: ViewModifier {
|
||||
|
||||
@Environment(\.colorScheme) private var colorScheme
|
||||
@Environment(\.appearsActive) private var appearsActive
|
||||
|
||||
let interactionState: InteractionState
|
||||
let cornerRadius: Double
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
if #available(macOS 26.0, *) {
|
||||
content
|
||||
.contentShape(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.glassEffect(.regular.tint(backgroundColor(interactionState: interactionState)), in: RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.mask(RoundedRectangle(cornerRadius: cornerRadius))
|
||||
.shadow(color: .black.opacity(0.1), radius: 5)
|
||||
} else {
|
||||
content
|
||||
.background(backgroundColor(interactionState: interactionState))
|
||||
.cornerRadius(10)
|
||||
}
|
||||
}
|
||||
|
||||
func backgroundColor(interactionState: InteractionState) -> Color {
|
||||
guard appearsActive else { return Color.clear }
|
||||
if #available(macOS 26.0, *) {
|
||||
let base: Color
|
||||
if #available(macOS 27.0, *) {
|
||||
base = .clear
|
||||
} else {
|
||||
base = colorScheme == .dark ? Color(white: 0.2) : Color(white: 1)
|
||||
}
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return base
|
||||
case .hovering:
|
||||
return base.mix(with: .accentColor, by: colorScheme == .dark ? 0.2 : 0.1)
|
||||
}
|
||||
} else {
|
||||
switch interactionState {
|
||||
case .normal:
|
||||
return colorScheme == .dark ? Color(white: 0.2) : Color(white: 0.885)
|
||||
case .hovering:
|
||||
return colorScheme == .dark ? Color(white: 0.275) : Color(white: 0.82)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#Preview {
|
||||
MultilineInfoView(title: "Multiple", image: Image(systemName: "figure.wave"), items: [
|
||||
MultilineInfoView.Item(text: "hello", action: (Image(systemName: "chevron.forward"), {})),
|
||||
MultilineInfoView.Item(text: "World", action: (Image(systemName: "chevron.forward"), {})),
|
||||
])
|
||||
.padding()
|
||||
}
|
||||
|
||||
|
||||
#Preview {
|
||||
MultilineInfoView(title: "One", image: Image(systemName: "figure.wave"), items: ["Hello world."])
|
||||
.padding()
|
||||
}
|
||||
Reference in New Issue
Block a user