From aae25543e084c07977b683650bd2057b3914bd5d Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Sat, 25 Jan 2025 10:19:07 -0500 Subject: [PATCH] Add some support for signer requests --- Yeti.xcodeproj/project.pbxproj | 2 + Yeti/Models/NostrClientModel.swift | 8 +- Yeti/Models/ProfileSettingsModel.swift | 8 ++ Yeti/Models/SignEventPermissionModel.swift | 6 +- Yeti/Models/SignerRequestModel.swift | 68 +++++++++ Yeti/Models/SigningPolicy.swift | 6 +- Yeti/Resources/Localizable.xcstrings | 18 +++ Yeti/SceneDelegate.swift | 117 +++++++++------ Yeti/Views/ContentView.swift | 8 +- Yeti/Views/HistoryView.swift | 31 +++- Yeti/Views/HomeView.swift | 63 +++++++- Yeti/Views/LoggedInView.swift | 8 +- Yeti/Views/NostrClientView.swift | 134 +++++++++--------- Yeti/Views/PermissionsView.swift | 43 +++--- .../Views/SigningPolicyConfirmationView.swift | 2 +- Yeti/YetiApp.swift | 3 +- 16 files changed, 372 insertions(+), 153 deletions(-) create mode 100644 Yeti/Models/SignerRequestModel.swift diff --git a/Yeti.xcodeproj/project.pbxproj b/Yeti.xcodeproj/project.pbxproj index 938f4e6..d89af68 100644 --- a/Yeti.xcodeproj/project.pbxproj +++ b/Yeti.xcodeproj/project.pbxproj @@ -436,6 +436,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\""; + DEVELOPMENT_TEAM = S99A5B637C; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; @@ -466,6 +467,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Yeti/Preview Content\""; + DEVELOPMENT_TEAM = S99A5B637C; ENABLE_PREVIEWS = YES; ENABLE_USER_SCRIPT_SANDBOXING = NO; GENERATE_INFOPLIST_FILE = YES; diff --git a/Yeti/Models/NostrClientModel.swift b/Yeti/Models/NostrClientModel.swift index 38cfc45..5868853 100644 --- a/Yeti/Models/NostrClientModel.swift +++ b/Yeti/Models/NostrClientModel.swift @@ -10,12 +10,10 @@ import SwiftData @Model final class NostrClientModel { - @Attribute(.unique) var id: String + @Attribute(.unique) var name: String @Relationship(deleteRule: .cascade) var signEventPermissions: [SignEventPermissionModel] = [] - var signingPolicy: SigningPolicy? - var readPublicKeyPermission: Bool = false var nip04EncryptPermission: Bool = false var nip44EncryptPermission: Bool = false @@ -24,7 +22,7 @@ final class NostrClientModel { var getRelaysPermission: Bool = false var decryptZapEventPermission: Bool = false - init(id: String) { - self.id = id + init(name: String) { + self.name = name } } diff --git a/Yeti/Models/ProfileSettingsModel.swift b/Yeti/Models/ProfileSettingsModel.swift index 7e0287a..aea9892 100644 --- a/Yeti/Models/ProfileSettingsModel.swift +++ b/Yeti/Models/ProfileSettingsModel.swift @@ -19,3 +19,11 @@ final class ProfileSettingsModel { self.publicKey = publicKey } } + +extension ProfileSettingsModel { + static func predicateByPublicKey(_ publicKey: String) -> Predicate { + #Predicate { profileSettingsModel in + profileSettingsModel.publicKey == publicKey + } + } +} diff --git a/Yeti/Models/SignEventPermissionModel.swift b/Yeti/Models/SignEventPermissionModel.swift index 54342ab..5a3b838 100644 --- a/Yeti/Models/SignEventPermissionModel.swift +++ b/Yeti/Models/SignEventPermissionModel.swift @@ -11,10 +11,10 @@ import SwiftData @Model final class SignEventPermissionModel { var kind: Int - var allowed: Bool + var approved: Bool - init(kind: Int, allowed: Bool) { + init(kind: Int, approved: Bool) { self.kind = kind - self.allowed = allowed + self.approved = approved } } diff --git a/Yeti/Models/SignerRequestModel.swift b/Yeti/Models/SignerRequestModel.swift new file mode 100644 index 0000000..83b3844 --- /dev/null +++ b/Yeti/Models/SignerRequestModel.swift @@ -0,0 +1,68 @@ +// +// SignerRequestModel.swift +// Yeti +// +// Created by Terry Yiu on 1/22/25. +// + +import Foundation +import SwiftData + +@Model +final class SignerRequestModel { + var type: NostrSignerType + var returnType: NostrSignerReturnType + var compressionType: NostrSignerCompressionType + var createdAt: Date + var callbackURL: String? + var pubkey: String? + var sourceApplication: String? + var targetApplication: String? + var approved: Bool? + var decidedAt: Date? + + init( + type: NostrSignerType, + returnType: NostrSignerReturnType, + compressionType: NostrSignerCompressionType, + createdAt: Date, + callbackURL: String? = nil, + pubkey: String? = nil, + sourceApplication: String? = nil, + targetApplication: String? = nil, + approved: Bool? = nil, + decidedAt: Date? = nil + ) { + self.type = type + self.returnType = returnType + self.compressionType = compressionType + self.createdAt = createdAt + self.callbackURL = callbackURL + self.pubkey = pubkey + self.sourceApplication = sourceApplication + self.targetApplication = targetApplication + self.approved = approved + self.decidedAt = decidedAt + } +} + +enum NostrSignerType: String, Codable, CaseIterable { + case getPublicKey = "get_public_key" + case signEvent = "sign_event" + case nip04Encrypt = "nip04_encrypt" + case nip44Encrypt = "nip44_encrypt" + case nip04Decrypt = "nip04_decrypt" + case nip44Decrypt = "nip44_decrypt" + case getRelays = "get_relays" + case decryptZapEvent = "decrypt_zap_event" +} + +enum NostrSignerReturnType: String, Codable { + case signature + case event +} + +enum NostrSignerCompressionType: String, Codable { + case none + case gzip +} diff --git a/Yeti/Models/SigningPolicy.swift b/Yeti/Models/SigningPolicy.swift index 718734d..7153727 100644 --- a/Yeti/Models/SigningPolicy.swift +++ b/Yeti/Models/SigningPolicy.swift @@ -7,9 +7,9 @@ import Foundation -enum SigningPolicy: Int, Codable, CaseIterable { - case basic = 0 - case manual = 1 +enum SigningPolicy: Codable, CaseIterable { + case basic + case manual var name: String { switch self { diff --git a/Yeti/Resources/Localizable.xcstrings b/Yeti/Resources/Localizable.xcstrings index 7ff95f0..ecc5958 100644 --- a/Yeti/Resources/Localizable.xcstrings +++ b/Yeti/Resources/Localizable.xcstrings @@ -7,9 +7,15 @@ "Add your key" : { "comment" : "Title of view to add the user’s private key." }, + "Allow" : { + "comment" : "Button to allow a permission request." + }, "Approve basic actions" : { "comment" : "Name of event signing policy that approves basic actions." }, + "Approved: %@" : { + "comment" : "Text indicating that a signer request is either approved or not approved." + }, "Create a key" : { "comment" : "Button to create a key." }, @@ -22,6 +28,9 @@ "Decrypt zap events" : { "comment" : "Permission toggle to allow Nostr client to decrypt zap events." }, + "Deny" : { + "comment" : "Button to deny a permission request." + }, "Done" : { "comment" : "Button to go to the next view that adds the user’s entered private key." }, @@ -49,9 +58,15 @@ "Next" : { "comment" : "Button to go to the next view that adds the user’s entered private key." }, + "Nothing to approve yet" : { + "comment" : "Text on Home tab to indicate that there are no permission requests to review for approval." + }, "nsec / private key" : { "comment" : "Prompt asking user to enter in a Nostr private key." }, + "Pending" : { + "comment" : "Text indicating that a signer request is pending approval." + }, "Permissions" : { "comment" : "Title for Permissions tab" }, @@ -76,6 +91,9 @@ "Sign kind %@ events" : { "comment" : "Permission toggle to allow Nostr client to sign events of a specific kind.." }, + "Why not explore your favorite Nostr app a bit?" : { + "comment" : "Text on Home tab to suggest what the user could do instead\nsince there are no permission requests to review for approval." + }, "Yeti: Nostr Helper" : { "comment" : "Application title." }, diff --git a/Yeti/SceneDelegate.swift b/Yeti/SceneDelegate.swift index 7c923df..e1bffac 100644 --- a/Yeti/SceneDelegate.swift +++ b/Yeti/SceneDelegate.swift @@ -6,9 +6,29 @@ // import Foundation +import SwiftData import UIKit class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject { + private static let nostrSignerURLScheme = "nostrsigner" + + private let modelContainer: ModelContainer + + override init() { + let schema = Schema([ + GeneralSettingsModel.self, + ProfileSettingsModel.self, + SignerRequestModel.self + ]) + let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false) + + do { + modelContainer = try ModelContainer(for: schema, configurations: [modelConfiguration]) + } catch { + fatalError("Could not create ModelContainer: \(error)") + } + } + func scene( _ scene: UIScene, willConnectTo session: UISceneSession, @@ -26,32 +46,29 @@ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject { } private func scene(_ scene: UIScene, openURLContext: UIOpenURLContext) { - let sendingAppID = openURLContext.options.sourceApplication - let url = openURLContext.url + var generalSettingsModelDescriptor = FetchDescriptor() + generalSettingsModelDescriptor.fetchLimit = 1 - guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true), - components.scheme == "nostrsigner", - let queryItems = components.queryItems else { - print("Invalid URL or path missing") + guard let generalSettingsModel = try? modelContainer.mainContext.fetch(generalSettingsModelDescriptor).first, + let activePublicKey = generalSettingsModel.activePublicKey + else { return } - print("source application = \(sendingAppID ?? "Unknown")") - print("url = \(url)") + let sourceApplication = openURLContext.options.sourceApplication + let url = openURLContext.url - if let path = components.path { - print("path = \(path)") + guard let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true), + components.scheme == SceneDelegate.nostrSignerURLScheme, + let queryItems = components.queryItems else { + print("Invalid URL or path missing. \(url.absoluteString)") + return } - if let host = components.host { - print("host = \(host)") - } - - print("queryItems = \(queryItems)") let params = queryItems.reduce(into: [:]) { $0[$1.name] = $1.value } let compressionType: NostrSignerCompressionType - if let rawCompressionType = params["compressionType"] { + if let rawCompressionType = params[NostrSignerURLQueryItemName.compressionType.rawValue] { if let maybeCompressionType = NostrSignerCompressionType(rawValue: rawCompressionType) { compressionType = maybeCompressionType } else { @@ -61,44 +78,52 @@ class SceneDelegate: NSObject, UIWindowSceneDelegate, ObservableObject { compressionType = .none } - guard let rawType = params["type"], + guard let rawType = params[NostrSignerURLQueryItemName.type.rawValue], let type = NostrSignerType(rawValue: rawType), - let rawReturnType = params["returnType"], - let returnType = NostrSignerReturnType(rawValue: rawReturnType) else { + let rawReturnType = params[NostrSignerURLQueryItemName.returnType.rawValue], + let returnType = NostrSignerReturnType(rawValue: rawReturnType) + else { return } - print("type = \(type)") - print("returnType = \(returnType)") - print("compressionType = \(compressionType)") + let callbackURLString = params[NostrSignerURLQueryItemName.callbackURL.rawValue] + let targetApplication = targetApplication(callbackURLString) - if let pubkey = params["pubkey"] { - print("pubkey = \(pubkey)") - } + let pubkey = params[NostrSignerURLQueryItemName.pubkey.rawValue] - if let callbackURL = params["callbackUrl"] { - print("callbackURL = \(callbackURL)") + let signerRequestModel = SignerRequestModel( + type: type, + returnType: returnType, + compressionType: compressionType, + createdAt: Date.now, + callbackURL: callbackURLString, + pubkey: pubkey, + sourceApplication: sourceApplication, + targetApplication: targetApplication + ) + + modelContainer.mainContext.insert(signerRequestModel) + } + + private func targetApplication(_ callbackURLString: String?) -> String? { + if let callbackURLString, let callbackURL = URL(string: callbackURLString) { + if let scheme = callbackURL.scheme { + let lowercasedScheme = scheme.lowercased() + if lowercasedScheme == "http" || lowercasedScheme == "https" { + return callbackURL.host() + } else { + return lowercasedScheme + } + } } + return nil } } -enum NostrSignerType: String, CaseIterable { - case getPublicKey = "get_public_key" - case signEvent = "sign_event" - case nip04Encrypt = "nip04_encrypt" - case nip44Encrypt = "nip44_encrypt" - case nip04Decrypt = "nip04_decrypt" - case nip44Decrypt = "nip44_decrypt" - case getRelays = "get_relays" - case decryptZapEvent = "decrypt_zap_event" -} - -enum NostrSignerReturnType: String, CaseIterable { - case signature - case event -} - -enum NostrSignerCompressionType: String, CaseIterable { - case none - case gzip +private enum NostrSignerURLQueryItemName: String { + case callbackURL = "callbackUrl" + case compressionType + case pubkey + case returnType + case type } diff --git a/Yeti/Views/ContentView.swift b/Yeti/Views/ContentView.swift index 07b0c42..1fe512f 100644 --- a/Yeti/Views/ContentView.swift +++ b/Yeti/Views/ContentView.swift @@ -14,10 +14,10 @@ struct ContentView: View { var body: some View { Group { - if generalSettingsModels.first!.activePublicKey == nil { - OnboardingView() + if let activePublicKey = generalSettingsModels.first?.activePublicKey { + LoggedInView(publicKey: activePublicKey) } else { - LoggedInView() + OnboardingView() } } } @@ -25,5 +25,5 @@ struct ContentView: View { #Preview { ContentView() - .modelContainer(for: ProfileSettingsModel.self, inMemory: true) + .modelContainer(for: GeneralSettingsModel.self, inMemory: true) } diff --git a/Yeti/Views/HistoryView.swift b/Yeti/Views/HistoryView.swift index ad5438b..d1fe296 100644 --- a/Yeti/Views/HistoryView.swift +++ b/Yeti/Views/HistoryView.swift @@ -5,11 +5,40 @@ // Created by Terry Yiu on 1/20/25. // +import SwiftData import SwiftUI struct HistoryView: View { + @Query(sort: \SignerRequestModel.createdAt, order: .reverse) private var signerRequestModels: [SignerRequestModel] + var body: some View { - EmptyView() + List { + ForEach(signerRequestModels, id: \.self) { signerRequestModel in + Section { + VStack { + Text(signerRequestModel.type.rawValue) + Text(signerRequestModel.returnType.rawValue) + Text(signerRequestModel.compressionType.rawValue) + Text(signerRequestModel.createdAt.description) + Text(signerRequestModel.callbackURL ?? "Unknown callbackURL") + Text(signerRequestModel.pubkey ?? "Unknown pubkey") + Text(signerRequestModel.sourceApplication ?? "Unknown sourceApplication") + Text(signerRequestModel.targetApplication ?? "Unknown targetApplication") + + switch signerRequestModel.approved { + case .none: + Text("Pending", comment: "Text indicating that a signer request is pending approval.") + case .some(let approved): + Text( + "Approved: \(approved.description)", + comment: "Text indicating that a signer request is either approved or not approved.") + } + + Text(signerRequestModel.decidedAt?.description ?? "Unknown decisionTimestamp") + } + } + } + } } } diff --git a/Yeti/Views/HomeView.swift b/Yeti/Views/HomeView.swift index e972a70..cb101bb 100644 --- a/Yeti/Views/HomeView.swift +++ b/Yeti/Views/HomeView.swift @@ -5,11 +5,72 @@ // Created by Terry Yiu on 1/20/25. // +import SwiftData import SwiftUI struct HomeView: View { + @Query( + filter: #Predicate { signerRequestModel in + signerRequestModel.approved == nil + }, + sort: \SignerRequestModel.createdAt, order: .reverse + ) + private var signerRequestModels: [SignerRequestModel] + var body: some View { - EmptyView() + if let signerRequestModel = signerRequestModels.first { + Section { + VStack { + Text(signerRequestModel.type.rawValue) + Text(signerRequestModel.returnType.rawValue) + Text(signerRequestModel.compressionType.rawValue) + Text(signerRequestModel.createdAt.description) + Text(signerRequestModel.callbackURL ?? "Unknown callbackURL") + Text(signerRequestModel.pubkey ?? "Unknown pubkey") + Text(signerRequestModel.sourceApplication ?? "Unknown sourceApplication") + Text(signerRequestModel.targetApplication ?? "Unknown targetApplication") + + HStack { + Button( + String(localized: "Allow", comment: "Button to allow a permission request."), + systemImage: "checkmark" + ) { + signerRequestModel.approved = true + } + .buttonStyle(.borderedProminent) + + Button( + String(localized: "Deny", comment: "Button to deny a permission request."), + systemImage: "xmark" + ) { + signerRequestModel.approved = false + } + .buttonStyle(.borderedProminent) + } + } + } + } else { + VStack { + Text( + "Nothing to approve yet", + comment: +""" +Text on Home tab to indicate that there are no permission requests to review for approval. +""" + ) + .font(.headline) + + Text( + "Why not explore your favorite Nostr app a bit?", + comment: +""" +Text on Home tab to suggest what the user could do instead +since there are no permission requests to review for approval. +""" + ) + .font(.caption) + } + } } } diff --git a/Yeti/Views/LoggedInView.swift b/Yeti/Views/LoggedInView.swift index 8040a4b..2cd465f 100644 --- a/Yeti/Views/LoggedInView.swift +++ b/Yeti/Views/LoggedInView.swift @@ -5,9 +5,13 @@ // Created by Terry Yiu on 1/20/25. // +import NostrSDK +import SwiftData import SwiftUI struct LoggedInView: View { + let publicKey: String + @State var selectedTab: YetiTab = .home var body: some View { @@ -18,7 +22,7 @@ struct LoggedInView: View { case .home: HomeView() case .permissions: - PermissionsView() + PermissionsView(publicKey: publicKey) case .history: HistoryView() case .settings: @@ -64,5 +68,5 @@ enum YetiTab: CustomStringConvertible, CaseIterable { } #Preview { - LoggedInView() + LoggedInView(publicKey: Keypair()!.publicKey.hex) } diff --git a/Yeti/Views/NostrClientView.swift b/Yeti/Views/NostrClientView.swift index c330830..20ecfdd 100644 --- a/Yeti/Views/NostrClientView.swift +++ b/Yeti/Views/NostrClientView.swift @@ -11,82 +11,82 @@ import SwiftUI struct NostrClientView: View { @Query private var nostrClientModels: [NostrClientModel] - init(nostrClientModelId: String) { - self._nostrClientModels = Query(filter: #Predicate { - $0.id == nostrClientModelId - }) + init(nostrClientModelName: String) { + self._nostrClientModels = Query(filter: NostrClientModel.predicateByName(nostrClientModelName)) } - var nostrClientModel: NostrClientModel { - nostrClientModels.first! + var nostrClientModel: NostrClientModel? { + nostrClientModels.first } var body: some View { VStack { - let bindableNostrClientModel = Bindable(nostrClientModel) + if let nostrClientModel { + let bindableNostrClientModel = Bindable(nostrClientModel) - Text(bindableNostrClientModel.id) - .font(.headline) + Text(bindableNostrClientModel.name.wrappedValue) + .font(.headline) - List { - Toggle( - String( - localized: "Read your profile", - comment: "Permission toggle to allow Nostr client to read your profile." - ), - isOn: bindableNostrClientModel.readPublicKeyPermission - ) - Toggle( - String( - localized: "Get relays", - comment: "Permission toggle to allow Nostr client to get relays." - ), - isOn: bindableNostrClientModel.getRelaysPermission - ) - Toggle( - String( - localized: "Encrypt DMs", - comment: "Permission toggle to allow Nostr client to encrypt direct messages." - ), - isOn: bindableNostrClientModel.nip44EncryptPermission - ) - Toggle( - String( - localized: "Decrypt DMs", - comment: "Permission toggle to allow Nostr client to decrypt direct messages." - ), - isOn: bindableNostrClientModel.nip44DecryptPermission - ) - Toggle( - String( - localized: "Encrypt legacy DMs", - comment: "Permission toggle to allow Nostr client to encrypt legacy direct messages." - ), - isOn: bindableNostrClientModel.nip04EncryptPermission - ) - Toggle( - String( - localized: "Decrypt legacy DMs", - comment: "Permission toggle to allow Nostr client to decrypt legacy direct messages." - ), - isOn: bindableNostrClientModel.nip04DecryptPermission - ) - Toggle( - String( - localized: "Decrypt zap events", - comment: "Permission toggle to allow Nostr client to decrypt zap events." - ), - isOn: bindableNostrClientModel.decryptZapEventPermission - ) - - ForEach(bindableNostrClientModel.signEventPermissions, id: \.self) { signEventPermissionModel in + List { Toggle( String( - localized: "Sign kind \(signEventPermissionModel.kind.wrappedValue.description) events", - comment: "Permission toggle to allow Nostr client to sign events of a specific kind.." + localized: "Read your profile", + comment: "Permission toggle to allow Nostr client to read your profile." ), - isOn: signEventPermissionModel.allowed + isOn: bindableNostrClientModel.readPublicKeyPermission ) + Toggle( + String( + localized: "Get relays", + comment: "Permission toggle to allow Nostr client to get relays." + ), + isOn: bindableNostrClientModel.getRelaysPermission + ) + Toggle( + String( + localized: "Encrypt DMs", + comment: "Permission toggle to allow Nostr client to encrypt direct messages." + ), + isOn: bindableNostrClientModel.nip44EncryptPermission + ) + Toggle( + String( + localized: "Decrypt DMs", + comment: "Permission toggle to allow Nostr client to decrypt direct messages." + ), + isOn: bindableNostrClientModel.nip44DecryptPermission + ) + Toggle( + String( + localized: "Encrypt legacy DMs", + comment: "Permission toggle to allow Nostr client to encrypt legacy direct messages." + ), + isOn: bindableNostrClientModel.nip04EncryptPermission + ) + Toggle( + String( + localized: "Decrypt legacy DMs", + comment: "Permission toggle to allow Nostr client to decrypt legacy direct messages." + ), + isOn: bindableNostrClientModel.nip04DecryptPermission + ) + Toggle( + String( + localized: "Decrypt zap events", + comment: "Permission toggle to allow Nostr client to decrypt zap events." + ), + isOn: bindableNostrClientModel.decryptZapEventPermission + ) + + ForEach(bindableNostrClientModel.signEventPermissions, id: \.self) { signEventPermissionModel in + Toggle( + String( + localized: "Sign kind \(signEventPermissionModel.kind.wrappedValue.description) events", + comment: "Permission toggle to allow Nostr client to sign events of a specific kind.." + ), + isOn: signEventPermissionModel.approved + ) + } } } } @@ -94,13 +94,13 @@ struct NostrClientView: View { } extension NostrClientModel { - static func predicateById(_ id: String) -> Predicate { + static func predicateByName(_ name: String) -> Predicate { #Predicate { nostrClientModel in - nostrClientModel.id == id + nostrClientModel.name == name } } } #Preview { - NostrClientView(nostrClientModelId: UUID().uuidString) + NostrClientView(nostrClientModelName: UUID().uuidString) } diff --git a/Yeti/Views/PermissionsView.swift b/Yeti/Views/PermissionsView.swift index 398ba80..786dc35 100644 --- a/Yeti/Views/PermissionsView.swift +++ b/Yeti/Views/PermissionsView.swift @@ -5,41 +5,46 @@ // Created by Terry Yiu on 1/20/25. // +import NostrSDK import SwiftData import SwiftUI struct PermissionsView: View { - @Environment(\.modelContext) private var modelContext - @Query(sort: \NostrClientModel.id) var nostrClientModels: [NostrClientModel] + @Query var profileSettingsModels: [ProfileSettingsModel] + + init(publicKey: String) { + var descriptor = FetchDescriptor( + predicate: #Predicate { profileSettingsModel in + profileSettingsModel.publicKey == publicKey + } + ) + descriptor.fetchLimit = 1 + + self._profileSettingsModels = Query(descriptor) + } + + var profileSettingsModel: ProfileSettingsModel? { + profileSettingsModels.first + } + + var nostrClientModels: [NostrClientModel] { + profileSettingsModel?.nostrClientModels ?? [] + } var body: some View { NavigationStack { List { ForEach(nostrClientModels, id: \.self) { nostrClientModel in NavigationLink( - nostrClientModel.id, - destination: NostrClientView(nostrClientModelId: nostrClientModel.id) + nostrClientModel.name, + destination: NostrClientView(nostrClientModelName: nostrClientModel.name) ) } } } - // TODO Remove this dummy code - .onAppear(perform: addNostrClientModel) - } - - // TODO Remove this dummy code - private func addNostrClientModel() { - withAnimation { - let newNostrClientModel = NostrClientModel(id: Date().timeIntervalSince1970.rounded().description) - newNostrClientModel.signEventPermissions = [ - SignEventPermissionModel(kind: 1, allowed: true), - SignEventPermissionModel(kind: 31923, allowed: true) - ] - modelContext.insert(newNostrClientModel) - } } } #Preview { - PermissionsView() + PermissionsView(publicKey: Keypair()!.publicKey.hex) } diff --git a/Yeti/Views/SigningPolicyConfirmationView.swift b/Yeti/Views/SigningPolicyConfirmationView.swift index 676e127..92df7a8 100644 --- a/Yeti/Views/SigningPolicyConfirmationView.swift +++ b/Yeti/Views/SigningPolicyConfirmationView.swift @@ -49,7 +49,7 @@ You’re set for now. You’ll need to come back here with every new app and app profileSettingsModel.signingPolicy = signingPolicy modelContext.insert(profileSettingsModel) - generalSettingsModels.first!.activePublicKey = keypair.publicKey.hex + generalSettingsModels.first?.activePublicKey = keypair.publicKey.hex }) .buttonStyle(.borderedProminent) } diff --git a/Yeti/YetiApp.swift b/Yeti/YetiApp.swift index 8d9cf75..a10413c 100644 --- a/Yeti/YetiApp.swift +++ b/Yeti/YetiApp.swift @@ -17,7 +17,8 @@ struct YetiApp: App { init() { let schema = Schema([ GeneralSettingsModel.self, - ProfileSettingsModel.self + ProfileSettingsModel.self, + SignerRequestModel.self ]) let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)