From acdf0baf3a71b739c435f835bdf51f451beabb5d Mon Sep 17 00:00:00 2001 From: Max Goedjen Date: Sun, 18 Dec 2022 16:19:54 -0800 Subject: [PATCH] Create Secret Screen Redesign (#370) --- .../xcshareddata/xcschemes/Secretive.xcscheme | 1 + Sources/Secretive/Views/ContentView.swift | 2 +- .../Secretive/Views/CreateSecretView.swift | 237 +++++++++++++++++- 3 files changed, 226 insertions(+), 14 deletions(-) diff --git a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme index 7f8d2cb..757e085 100644 --- a/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme +++ b/Sources/Secretive.xcodeproj/xcshareddata/xcschemes/Secretive.xcscheme @@ -75,6 +75,7 @@ ignoresPersistentStateOnLaunch = "NO" debugDocumentVersioning = "YES" debugServiceExtension = "internal" + enableGPUValidationMode = "1" allowLocationSimulation = "YES"> diff --git a/Sources/Secretive/Views/ContentView.swift b/Sources/Secretive/Views/ContentView.swift index c14e48e..2699062 100644 --- a/Sources/Secretive/Views/ContentView.swift +++ b/Sources/Secretive/Views/ContentView.swift @@ -88,7 +88,7 @@ extension ContentView { }, label: { Image(systemName: "plus") }) - .popover(isPresented: $showingCreation, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { + .sheet(isPresented: $showingCreation) { if let modifiable = storeList.modifiableStore { CreateSecretView(store: modifiable, showing: $showingCreation) } diff --git a/Sources/Secretive/Views/CreateSecretView.swift b/Sources/Secretive/Views/CreateSecretView.swift index 318226d..428e0ca 100644 --- a/Sources/Secretive/Views/CreateSecretView.swift +++ b/Sources/Secretive/Views/CreateSecretView.swift @@ -12,28 +12,35 @@ struct CreateSecretView: View { var body: some View { VStack { HStack { - Image(nsImage: NSApplication.shared.applicationIconImage) - .resizable() - .frame(width: 64, height: 64) - .padding() VStack { HStack { - Text("Create a New Secret").bold() + Text("Create a New Secret") + .font(.largeTitle) Spacer() } HStack { Text("Name:") - TextField("Shhhhh", text: $name).focusable() + TextField("Shhhhh", text: $name) + .focusable() } - HStack { - VStack(spacing: 20) { - Picker("", selection: $requiresAuthentication) { - Text("Requires Authentication (Biometrics or Password) before each use").tag(true) - Text("Authentication not required when Mac is unlocked").tag(false) + if #available(macOS 12.0, *) { + ThumbnailPickerView(items: [ + ThumbnailPickerView.Item(value: true, name: "Require Authentication", description: "You will be required to authenticate using Touch ID, Apple Watch, or password before each use.", thumbnail: AuthenticationView()), + ThumbnailPickerView.Item(value: false, name: "Notify", + description: "No authentication is required while your Mac is unlocked, but you will be notified when a secret is used.", + thumbnail: NotificationView()) + ], selection: $requiresAuthentication) + } else { + HStack { + VStack(spacing: 20) { + Picker("", selection: $requiresAuthentication) { + Text("Requires Authentication (Biometrics or Password) before each use").tag(true) + Text("Authentication not required when Mac is unlocked").tag(false) + } + .pickerStyle(RadioGroupPickerStyle()) + Spacer(minLength: 10) } - .pickerStyle(RadioGroupPickerStyle()) } - Spacer() } } } @@ -54,4 +61,208 @@ struct CreateSecretView: View { try! store.create(name: name, requiresAuthentication: requiresAuthentication) showing = false } + } + +struct ThumbnailPickerView: View { + + private let items: [Item] + @Binding var selection: ValueType + + init(items: [ThumbnailPickerView.Item], selection: Binding) { + 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) + } + } + +} + +extension ThumbnailPickerView { + + struct Item: Identifiable { + let id = UUID() + let value: ValueType + let name: String + let description: String + let thumbnail: AnyView + + init(value: ValueType, name: String, description: String, thumbnail: ViewType) { + self.value = value + self.name = name + self.description = description + self.thumbnail = AnyView(thumbnail) + } + } + +} + +@MainActor class SystemBackground: ObservableObject { + + static let shared = SystemBackground() + @Published var image: NSImage? + + private init() { + if let mainScreen = NSScreen.main, let imageURL = NSWorkspace.shared.desktopImageURL(for: mainScreen) { + image = NSImage(contentsOf: imageURL) + } else { + image = nil + } + } + +} + +@available(macOS 12.0, *) +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)) + } + } +} + +@available(macOS 12.0, *) +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("Touch ID Prompt") + .font(.headline) + .foregroundColor(.primary) + .redacted(reason: .placeholder) + VStack { + Text("Touch ID Detail prompt.Detail two.") + .font(.caption2) + .foregroundColor(.primary) + Text("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() + + } + } + +} + +@available(macOS 12.0, *) +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("Secretive") + .font(.title) + .foregroundColor(.primary) + Text("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)) + if #available(macOS 12.0, *) { + AuthenticationView().environment(\.colorScheme, .dark) + AuthenticationView().environment(\.colorScheme, .light) + NotificationView().environment(\.colorScheme, .dark) + NotificationView().environment(\.colorScheme, .light) + } else { + // Fallback on earlier versions + } + } + } +} + +#endif