diff --git a/Yeti.xcodeproj/project.pbxproj b/Yeti.xcodeproj/project.pbxproj index e64e139..be9ccdf 100644 --- a/Yeti.xcodeproj/project.pbxproj +++ b/Yeti.xcodeproj/project.pbxproj @@ -359,6 +359,7 @@ ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = NO; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; @@ -415,6 +416,7 @@ MTL_FAST_MATH = YES; SDKROOT = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_EMIT_LOC_STRINGS = NO; VALIDATE_PRODUCT = YES; }; name = Release; diff --git a/Yeti/Assets.xcassets/AccentColor.colorset/Contents.json b/Yeti/Assets/Assets.xcassets/AccentColor.colorset/Contents.json similarity index 100% rename from Yeti/Assets.xcassets/AccentColor.colorset/Contents.json rename to Yeti/Assets/Assets.xcassets/AccentColor.colorset/Contents.json diff --git a/Yeti/Assets.xcassets/AppIcon.appiconset/Contents.json b/Yeti/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from Yeti/Assets.xcassets/AppIcon.appiconset/Contents.json rename to Yeti/Assets/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/Yeti/Assets.xcassets/Contents.json b/Yeti/Assets/Assets.xcassets/Contents.json similarity index 100% rename from Yeti/Assets.xcassets/Contents.json rename to Yeti/Assets/Assets.xcassets/Contents.json diff --git a/Yeti/Assets/Localizable.xcstrings b/Yeti/Assets/Localizable.xcstrings new file mode 100644 index 0000000..6078513 --- /dev/null +++ b/Yeti/Assets/Localizable.xcstrings @@ -0,0 +1,57 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Add a key" : { + "comment" : "Button to add a key." + }, + "Add your key" : { + "comment" : "Title of view to add the user’s private key." + }, + "Approve basic actions" : { + "comment" : "Name of event signing policy that approves basic actions." + }, + "Create a key" : { + "comment" : "Button to create a key." + }, + "Done" : { + "comment" : "Button to go to the next view that adds the user's entered private key." + }, + "Got it!" : { + "comment" : "Button to go to the next view that adds the user’s entered private key." + }, + "Hooo-raaaayyy!" : { + "comment" : "Title of view that confirms the user’s selection of the signing policy." + }, + "Manually approve each app" : { + "comment" : "Name of event signing policy that requires manual approval to sign each event." + }, + "Next" : { + "comment" : "Button to go to the next view that adds the user’s entered private key." + }, + "nsec / private key" : { + "comment" : "Prompt asking user to enter in a Nostr private key." + }, + "Recommended for most people. This policy will minimize the number of interruptions during your app usage." : { + "comment" : "Description of event signing policy that approves basic actions." + }, + "Recommended for privacy-minded people who would like control over each app.\nChoosing this policy will prompt you to set a preference every time you try a new app." : { + "comment" : "Description of event signing policy that requires manual approval to sign each event." + }, + "Select a signing policy" : { + "comment" : "Title of view to select a signing policy." + }, + "Yeti: Nostr Helper" : { + "comment" : "Application title." + }, + "You’re all set! I’ll take care of most of the approval for you.\nIf I see an event I don’t recognize, I’ll ask you to review it." : { + "comment" : "Description of what the user should expect after selecting the basic signing policy." + }, + "You’re set for now. You’ll need to come back here with every new app and approve some nostr events." : { + "comment" : "Description of what the user should expect after selecting the manual signing policy." + }, + "Your private key is stored locally. Only you can see it." : { + "comment" : "Footer text explaining that the private key is stored locally and only the user can see it." + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/Yeti/ContentView.swift b/Yeti/ContentView.swift deleted file mode 100644 index a4e32da..0000000 --- a/Yeti/ContentView.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// ContentView.swift -// Yeti -// -// Created by Terry Yiu on 1/19/25. -// - -import SwiftUI -import SwiftData - -struct ContentView: View { - @Environment(\.modelContext) private var modelContext - @Query private var items: [Item] - - var body: some View { - NavigationSplitView { - List { - ForEach(items) { item in - NavigationLink { - Text("Item at \(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard))") - } label: { - Text(item.timestamp, format: Date.FormatStyle(date: .numeric, time: .standard)) - } - } - .onDelete(perform: deleteItems) - } - .toolbar { - ToolbarItem(placement: .navigationBarTrailing) { - EditButton() - } - ToolbarItem { - Button(action: addItem) { - Label("Add Item", systemImage: "plus") - } - } - } - } detail: { - Text("Select an item") - } - } - - private func addItem() { - withAnimation { - let newItem = Item(timestamp: Date()) - modelContext.insert(newItem) - } - } - - private func deleteItems(offsets: IndexSet) { - withAnimation { - for index in offsets { - modelContext.delete(items[index]) - } - } - } -} - -#Preview { - ContentView() - .modelContainer(for: Item.self, inMemory: true) -} diff --git a/Yeti/Views/AddKeyView.swift b/Yeti/Views/AddKeyView.swift new file mode 100644 index 0000000..53624b6 --- /dev/null +++ b/Yeti/Views/AddKeyView.swift @@ -0,0 +1,53 @@ +// +// AddKeyView.swift +// Yeti +// +// Created by Terry Yiu on 1/19/25. +// + +import SwiftUI + +struct AddKeyView: View { + @State private var key: String = "" + + var body: some View { + NavigationStack { + Form { + Text("Add your key", comment: "Title of view to add the user’s private key.") + .font(.title) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .listRowInsets(EdgeInsets()) + .background(Color(UIColor.systemGroupedBackground)) + + Section( + content: { + SecureField( + String( + localized: "nsec / private key", + comment: "Prompt asking user to enter in a Nostr private key." + ), + text: $key + ) + }, + footer: { + Text( + "Your private key is stored locally. Only you can see it.", + comment: +""" +Footer text explaining that the private key is stored locally and only the user can see it. +""" + ) + } + ) + + NavigationLink(destination: SigningPolicySelectionView()) { + Text("Next", comment: "Button to go to the next view that adds the user’s entered private key.") + } + } + } + } +} + +#Preview { + AddKeyView() +} diff --git a/Yeti/Views/ContentView.swift b/Yeti/Views/ContentView.swift new file mode 100644 index 0000000..fffc537 --- /dev/null +++ b/Yeti/Views/ContentView.swift @@ -0,0 +1,46 @@ +// +// ContentView.swift +// Yeti +// +// Created by Terry Yiu on 1/19/25. +// + +import SwiftUI +import SwiftData + +struct ContentView: View { + @Environment(\.modelContext) private var modelContext + + var body: some View { + NavigationStack { + Text("Yeti: Nostr Helper", comment: "Application title.") + + HStack { + NavigationLink( + destination: { + AddKeyView() + }, + label: { + Text("Add a key", comment: "Button to add a key.") + } + ) + .buttonStyle(.bordered) + + NavigationLink( + destination: { + AddKeyView() + }, + label: { + Text("Create a key", comment: "Button to create a key.") + } + ) + .buttonStyle(.borderedProminent) + } + } + } +} + +#Preview { + ContentView() + .modelContainer(for: Item.self, inMemory: true) +} diff --git a/Yeti/Views/SigningPolicyConfirmationView.swift b/Yeti/Views/SigningPolicyConfirmationView.swift new file mode 100644 index 0000000..747b88a --- /dev/null +++ b/Yeti/Views/SigningPolicyConfirmationView.swift @@ -0,0 +1,41 @@ +// +// SigningPolicyConfirmationView.swift +// Yeti +// +// Created by Terry Yiu on 1/20/25. +// + +import SwiftUI + +struct SigningPolicyConfirmationView: View { + let signingPolicy: SigningPolicy + + var body: some View { + VStack { + Text("Hooo-raaaayyy!", comment: "Title of view that confirms the user’s selection of the signing policy.") + .font(.title) + + switch signingPolicy { + case .basic: + Text( +""" +You’re all set! I’ll take care of most of the approval for you. +If I see an event I don’t recognize, I’ll ask you to review it. +""", + comment: "Description of what the user should expect after selecting the basic signing policy." + ) + case .manual: + Text( +""" +You’re set for now. You’ll need to come back here with every new app and approve some nostr events. +""", + comment: "Description of what the user should expect after selecting the manual signing policy." + ) + } + } + } +} + +#Preview { + SigningPolicyConfirmationView(signingPolicy: .basic) +} diff --git a/Yeti/Views/SigningPolicySelectionView.swift b/Yeti/Views/SigningPolicySelectionView.swift new file mode 100644 index 0000000..9cc90e7 --- /dev/null +++ b/Yeti/Views/SigningPolicySelectionView.swift @@ -0,0 +1,95 @@ +// +// SigningPolicySelectionView.swift +// Yeti +// +// Created by Terry Yiu on 1/20/25. +// + +import SwiftUI + +struct SigningPolicySelectionView: View { + @State var signingPolicy: SigningPolicy = .basic + + var body: some View { + NavigationStack { + Form { + Text("Select a signing policy", comment: "Title of view to select a signing policy.") + .font(.title) + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + .listRowInsets(EdgeInsets()) + .background(Color(UIColor.systemGroupedBackground)) + + Section( + content: { + Picker( + selection: $signingPolicy, + content: { + ForEach(SigningPolicy.allCases, id: \.self) { signingPolicy in + Text(signingPolicy.name) + .tag(signingPolicy) + } + }, + label: { + EmptyView() + } + ) + .pickerStyle(.inline) + }, + footer: { + Text(signingPolicy.description) + } + ) + + NavigationLink(destination: SigningPolicyConfirmationView(signingPolicy: signingPolicy)) { + Text("Done", comment: "Button to go to the next view that adds the user’s entered private key.") + } + } + } + } +} + +enum SigningPolicy: CaseIterable { + case basic + case manual + + var name: String { + switch self { + case .basic: + return String( + localized: "Approve basic actions", + comment: "Name of event signing policy that approves basic actions." + ) + case .manual: + return String( + localized: "Manually approve each app", + comment: "Name of event signing policy that requires manual approval to sign each event." + ) + } + } + + var description: String { + switch self { + case .basic: + return String( + localized: +""" +Recommended for most people. This policy will minimize the number of interruptions during your app usage. +""", + comment: "Description of event signing policy that approves basic actions." + ) + case .manual: + return String( + localized: +""" +Recommended for privacy-minded people who would like control over each app. +Choosing this policy will prompt you to set a preference every time you try a new app. +""", + comment: "Description of event signing policy that requires manual approval to sign each event." + ) + } + } +} + +#Preview { + SigningPolicySelectionView() +}