mirror of
https://github.com/maxgoedjen/secretive.git
synced 2025-08-19 11:40:56 +00:00
WIP
This commit is contained in:
parent
4882d7cde5
commit
276ca02b39
@ -1,6 +1,9 @@
|
||||
{
|
||||
"sourceLanguage" : "en",
|
||||
"strings" : {
|
||||
"Advanced" : {
|
||||
|
||||
},
|
||||
"agent_not_running_notice_title" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@ -1644,72 +1647,72 @@
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nom:"
|
||||
"value" : "Nom"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Name:"
|
||||
"value" : "Name"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Name:"
|
||||
"value" : "Name"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nimi:"
|
||||
"value" : "Nimi"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nom :"
|
||||
"value" : "Nom"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nome:"
|
||||
"value" : "Nome"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "名前:"
|
||||
"value" : "名前"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "이름:"
|
||||
"value" : "이름"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nazwa:"
|
||||
"value" : "Nazwa"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Nome:"
|
||||
"value" : "Nome"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Название:"
|
||||
"value" : "Название"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "名称"
|
||||
}
|
||||
}
|
||||
@ -2260,72 +2263,72 @@
|
||||
"ca" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirma el nom:"
|
||||
"value" : "Confirma el nom"
|
||||
}
|
||||
},
|
||||
"de" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Name bestätigen:"
|
||||
"value" : "Name bestätigen"
|
||||
}
|
||||
},
|
||||
"en" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirm Name:"
|
||||
"value" : "Confirm Name"
|
||||
}
|
||||
},
|
||||
"fi" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Vahvista nimi:"
|
||||
"value" : "Vahvista nimi"
|
||||
}
|
||||
},
|
||||
"fr" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirmer le nom :"
|
||||
"value" : "Confirmer le nom"
|
||||
}
|
||||
},
|
||||
"it" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Conferma nome:"
|
||||
"value" : "Conferma nome"
|
||||
}
|
||||
},
|
||||
"ja" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "名前の確認:"
|
||||
"value" : "名前の確認"
|
||||
}
|
||||
},
|
||||
"ko" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "확인 이름:"
|
||||
"value" : "확인 이름"
|
||||
}
|
||||
},
|
||||
"pl" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Powtórz nazwę:"
|
||||
"value" : "Powtórz nazwę"
|
||||
}
|
||||
},
|
||||
"pt-BR" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Confirmar Nome:"
|
||||
"value" : "Confirmar Nome"
|
||||
}
|
||||
},
|
||||
"ru" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "Подтвердить название:"
|
||||
"value" : "Подтвердить название"
|
||||
}
|
||||
},
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"state" : "needs_review",
|
||||
"value" : "确认名称"
|
||||
}
|
||||
}
|
||||
@ -2946,6 +2949,12 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Key Attribution" : {
|
||||
|
||||
},
|
||||
"Key Type" : {
|
||||
|
||||
},
|
||||
"no_secure_storage_description" : {
|
||||
"extractionState" : "manual",
|
||||
@ -5323,6 +5332,9 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"test@example.com" : {
|
||||
|
||||
},
|
||||
"unnamed_secret" : {
|
||||
"extractionState" : "manual",
|
||||
|
@ -30,7 +30,7 @@ public struct Attributes: Sendable, Codable {
|
||||
}
|
||||
|
||||
/// The option specified
|
||||
public enum AuthenticationRequirement: String, Hashable, Sendable, Codable {
|
||||
public enum AuthenticationRequirement: String, Hashable, Sendable, Codable, Identifiable {
|
||||
|
||||
/// Authentication is not required for usage.
|
||||
case notRequired
|
||||
@ -49,4 +49,8 @@ public enum AuthenticationRequirement: String, Hashable, Sendable, Codable {
|
||||
public var required: Bool {
|
||||
self == .presenceRequired || self == .biometryCurrent
|
||||
}
|
||||
|
||||
public var id: AuthenticationRequirement {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ extension EnvironmentValues {
|
||||
// This is injected through .environment modifier below instead of @Entry for performance reasons (basially, restrictions around init/mainactor causing delay in loading secrets/"empty screen" blip).
|
||||
@MainActor fileprivate static let _secretStoreList: SecretStoreList = {
|
||||
let list = SecretStoreList()
|
||||
list.add(store: SecureEnclave.Store())
|
||||
list.add(store: SecureEnclave.CryptoKitStore())
|
||||
list.add(store: SmartCard.Store())
|
||||
return list
|
||||
}()
|
||||
|
@ -7,244 +7,74 @@ struct CreateSecretView<StoreType: SecretStoreModifiable>: View {
|
||||
@Binding var showing: Bool
|
||||
|
||||
@State private var name = ""
|
||||
@State private var requiresAuthentication = true
|
||||
@State private var keyAttribution = ""
|
||||
@State private var authenticationRequirement: AuthenticationRequirement = .presenceRequired
|
||||
@State private var keyType: KeyType?
|
||||
@State var advanced = false
|
||||
|
||||
private var authenticationOptions: [AuthenticationRequirement] {
|
||||
[.presenceRequired, .notRequired]
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
HStack {
|
||||
VStack {
|
||||
HStack {
|
||||
Text(.createSecretTitle)
|
||||
.font(.largeTitle)
|
||||
Spacer()
|
||||
VStack(alignment: .trailing) {
|
||||
Form {
|
||||
Section {
|
||||
TextField(String(localized: .createSecretNameLabel), text: $name, prompt: Text(.createSecretNamePlaceholder))
|
||||
Picker(.createSecretRequireAuthenticationTitle, selection: $authenticationRequirement) {
|
||||
ForEach(authenticationOptions) { option in
|
||||
Text(String(describing: option))
|
||||
.tag(option)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Text(.createSecretNameLabel)
|
||||
TextField(String(localized: .createSecretNamePlaceholder), text: $name)
|
||||
.focusable()
|
||||
}
|
||||
if advanced {
|
||||
Section {
|
||||
Picker("Key Type", selection: $keyType) {
|
||||
ForEach(store.supportedKeyTypes, id: \.self) { option in
|
||||
Text(String(describing: option))
|
||||
.tag(option)
|
||||
}
|
||||
}
|
||||
TextField("Key Attribution", text: $keyAttribution, prompt: Text("test@example.com"))
|
||||
}
|
||||
ThumbnailPickerView(items: [
|
||||
ThumbnailPickerView.Item(value: true, name: .createSecretRequireAuthenticationTitle, description: .createSecretRequireAuthenticationDescription, thumbnail: AuthenticationView()),
|
||||
ThumbnailPickerView.Item(value: false, name: .createSecretNotifyTitle,
|
||||
description: .createSecretNotifyDescription,
|
||||
thumbnail: NotificationView())
|
||||
], selection: $requiresAuthentication)
|
||||
}
|
||||
}
|
||||
HStack {
|
||||
Toggle("Advanced", isOn: $advanced)
|
||||
.toggleStyle(.button)
|
||||
Spacer()
|
||||
Button(.createSecretCancelButton) {
|
||||
Button(.createSecretCancelButton, role: .cancel) {
|
||||
showing = false
|
||||
}
|
||||
.keyboardShortcut(.cancelAction)
|
||||
Button(.createSecretCreateButton, action: save)
|
||||
.disabled(name.isEmpty)
|
||||
.keyboardShortcut(.defaultAction)
|
||||
}
|
||||
}.padding()
|
||||
.padding()
|
||||
}
|
||||
.onAppear {
|
||||
keyType = store.supportedKeyTypes.first
|
||||
}
|
||||
.formStyle(.grouped)
|
||||
}
|
||||
|
||||
func save() {
|
||||
let attribution = keyAttribution.isEmpty ? nil : keyAttribution
|
||||
Task {
|
||||
try! await store.create(name: name, attributes: .init(keyType: .init(algorithm: .ecdsa, size: 256), authentication: .presenceRequired, publicKeyAttribution: nil))
|
||||
try! await store.create(
|
||||
name: name,
|
||||
attributes: .init(
|
||||
keyType: keyType!,
|
||||
authentication: authenticationRequirement,
|
||||
publicKeyAttribution: attribution
|
||||
)
|
||||
)
|
||||
showing = false
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ThumbnailPickerView<ValueType: Hashable>: View {
|
||||
|
||||
private let items: [Item<ValueType>]
|
||||
@Binding var selection: ValueType
|
||||
|
||||
init(items: [ThumbnailPickerView<ValueType>.Item<ValueType>], selection: Binding<ValueType>) {
|
||||
self.items = items
|
||||
_selection = selection
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
HStack(alignment: .top) {
|
||||
ForEach(items) { item in
|
||||
VStack(alignment: .leading, spacing: 15) {
|
||||
item.thumbnail
|
||||
.frame(height: 200)
|
||||
.overlay(RoundedRectangle(cornerRadius: 10)
|
||||
.stroke(lineWidth: item.value == selection ? 15 : 0))
|
||||
.clipShape(RoundedRectangle(cornerRadius: 10, style: .continuous))
|
||||
.foregroundColor(.accentColor)
|
||||
VStack(alignment: .leading, spacing: 5) {
|
||||
Text(item.name)
|
||||
.bold()
|
||||
Text(item.description)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
}
|
||||
}
|
||||
.frame(width: 250)
|
||||
.onTapGesture {
|
||||
withAnimation(.spring()) {
|
||||
selection = item.value
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true))
|
||||
}
|
||||
|
||||
extension ThumbnailPickerView {
|
||||
|
||||
struct Item<InnerValueType: Hashable>: Identifiable {
|
||||
let id = UUID()
|
||||
let value: InnerValueType
|
||||
let name: LocalizedStringResource
|
||||
let description: LocalizedStringResource
|
||||
let thumbnail: AnyView
|
||||
|
||||
init<ViewType: View>(value: InnerValueType, name: LocalizedStringResource, description: LocalizedStringResource, thumbnail: ViewType) {
|
||||
self.value = value
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.thumbnail = AnyView(thumbnail)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@MainActor @Observable class SystemBackground {
|
||||
|
||||
static let shared = SystemBackground()
|
||||
var image: NSImage?
|
||||
|
||||
private init() {
|
||||
if let mainScreen = NSScreen.main, let imageURL = NSWorkspace.shared.desktopImageURL(for: mainScreen) {
|
||||
image = NSImage(contentsOf: imageURL)
|
||||
} else {
|
||||
image = nil
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct SystemBackgroundView: View {
|
||||
|
||||
let anchor: UnitPoint
|
||||
|
||||
var body: some View {
|
||||
if let image = SystemBackground.shared.image {
|
||||
Image(nsImage: image)
|
||||
.resizable()
|
||||
.scaleEffect(3, anchor: anchor)
|
||||
.clipped()
|
||||
.allowsHitTesting(false)
|
||||
} else {
|
||||
Rectangle()
|
||||
.foregroundColor(Color(.systemPurple))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct AuthenticationView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
SystemBackgroundView(anchor: .center)
|
||||
GeometryReader { geometry in
|
||||
VStack {
|
||||
Image(systemName: "touchid")
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.foregroundColor(Color(.systemRed))
|
||||
Text(verbatim: "Touch ID Prompt")
|
||||
.font(.headline)
|
||||
.foregroundColor(.primary)
|
||||
.redacted(reason: .placeholder)
|
||||
VStack {
|
||||
Text(verbatim: "Touch ID Detail prompt.Detail two.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.primary)
|
||||
Text(verbatim: "Touch ID Detail prompt.Detail two.")
|
||||
.font(.caption2)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
.redacted(reason: .placeholder)
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: geometry.size.width, height: 20, alignment: .center)
|
||||
.foregroundColor(.accentColor)
|
||||
RoundedRectangle(cornerRadius: 5)
|
||||
.frame(width: geometry.size.width, height: 20, alignment: .center)
|
||||
.foregroundColor(Color(.unemphasizedSelectedContentBackgroundColor))
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.frame(width: 150)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(.ultraThickMaterial)
|
||||
)
|
||||
.padding()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct NotificationView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
SystemBackgroundView(anchor: .topTrailing)
|
||||
VStack {
|
||||
Rectangle()
|
||||
.background(Color.clear)
|
||||
.foregroundStyle(.thinMaterial)
|
||||
.frame(height: 35)
|
||||
VStack {
|
||||
HStack {
|
||||
Spacer()
|
||||
HStack {
|
||||
Image(nsImage: NSApplication.shared.applicationIconImage)
|
||||
.resizable()
|
||||
.frame(width: 64, height: 64)
|
||||
.foregroundColor(.primary)
|
||||
VStack(alignment: .leading) {
|
||||
Text(verbatim: "Secretive")
|
||||
.font(.title)
|
||||
.foregroundColor(.primary)
|
||||
Text(verbatim: "Secretive wants to sign")
|
||||
.font(.body)
|
||||
.foregroundColor(.primary)
|
||||
}
|
||||
}.padding()
|
||||
.redacted(reason: .placeholder)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 15)
|
||||
.foregroundStyle(.ultraThickMaterial)
|
||||
)
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
|
||||
struct CreateSecretView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
Group {
|
||||
CreateSecretView(store: Preview.StoreModifiable(), showing: .constant(true))
|
||||
AuthenticationView().environment(\.colorScheme, .dark)
|
||||
AuthenticationView().environment(\.colorScheme, .light)
|
||||
NotificationView().environment(\.colorScheme, .dark)
|
||||
NotificationView().environment(\.colorScheme, .light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
Loading…
Reference in New Issue
Block a user