secretive/Sources/Secretive/Views/ContentView.swift

224 lines
7.7 KiB
Swift
Raw Normal View History

2020-02-19 03:36:41 +00:00
import SwiftUI
2020-02-19 04:52:00 +00:00
import SecretKit
import SecureEnclaveSecretKit
import SmartCardSecretKit
2020-03-22 01:43:26 +00:00
import Brief
2020-02-19 04:52:00 +00:00
struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentStatusCheckerProtocol>: View {
2020-09-22 06:12:50 +00:00
@Binding var showingCreation: Bool
@Binding var runningSetup: Bool
@Binding var hasRunSetup: Bool
2022-12-22 22:20:03 +00:00
@State var showingAgentInfo = false
@Environment(\.colorScheme) var colorScheme
2020-09-22 06:12:50 +00:00
@EnvironmentObject private var storeList: SecretStoreList
@EnvironmentObject private var updater: UpdaterType
@EnvironmentObject private var agentStatusChecker: AgentStatusCheckerType
@State private var selectedUpdate: Release?
@State private var showingAppPathNotice = false
2020-02-19 03:36:41 +00:00
var body: some View {
2020-03-15 08:01:40 +00:00
VStack {
2020-03-21 03:20:20 +00:00
if storeList.anyAvailable {
2020-09-22 06:12:50 +00:00
StoreListView(showingCreation: $showingCreation)
2020-03-21 03:20:20 +00:00
} else {
NoStoresView()
}
2020-09-22 06:12:50 +00:00
}
.frame(minWidth: 640, minHeight: 320)
.toolbar {
2022-12-22 22:20:03 +00:00
toolbarItem(updateNoticeView, id: "update")
toolbarItem(runningOrRunSetupView, id: "setup")
toolbarItem(appPathNoticeView, id: "appPath")
toolbarItem(newItemView, id: "new")
2020-09-22 06:12:50 +00:00
}
.sheet(isPresented: $runningSetup) {
SetupView(visible: $runningSetup, setupComplete: $hasRunSetup)
}
2020-02-19 03:36:41 +00:00
}
2020-03-15 08:01:40 +00:00
2020-09-22 06:12:50 +00:00
}
extension ContentView {
2022-12-22 22:20:03 +00:00
func toolbarItem(_ view: some View, id: String) -> ToolbarItem<String, some View> {
ToolbarItem(id: id) { view }
}
var needsSetup: Bool {
(runningSetup || !hasRunSetup || !agentStatusChecker.running) && !agentStatusChecker.developmentBuild
}
/// Item either showing a "everything's good, here's more info" or "something's wrong, re-run setup" message
/// These two are mutually exclusive
@ViewBuilder
var runningOrRunSetupView: some View {
if needsSetup {
setupNoticeView
} else {
runningNoticeView
2020-09-22 06:12:50 +00:00
}
2022-12-22 22:20:03 +00:00
}
var updateNoticeContent: (String, Color)? {
guard let update = updater.update else { return nil }
2020-03-15 08:01:40 +00:00
if update.critical {
2022-12-22 22:20:03 +00:00
return ("Critical Security Update Required", .red)
2020-03-15 08:01:40 +00:00
} else {
if updater.testBuild {
2022-12-22 22:20:03 +00:00
return ("Test Build", .blue)
} else {
2022-12-22 22:20:03 +00:00
return ("Update Available", .orange)
}
2020-09-22 06:12:50 +00:00
}
2020-03-15 08:01:40 +00:00
}
2022-12-22 22:20:03 +00:00
@ViewBuilder
var updateNoticeView: some View {
if let update = updater.update, let (text, color) = updateNoticeContent {
Button(action: {
selectedUpdate = update
}, label: {
Text(text)
.font(.headline)
.foregroundColor(.white)
})
2022-12-22 22:55:53 +00:00
.buttonStyle(ToolbarButtonStyle(color: color))
2022-12-22 22:20:03 +00:00
.popover(item: $selectedUpdate, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { update in
UpdateDetailView(update: update)
}
2020-09-22 06:12:50 +00:00
}
2022-12-22 22:20:03 +00:00
}
2022-12-22 22:20:03 +00:00
@ViewBuilder
var newItemView: some View {
if storeList.modifiableStore?.isAvailable ?? false {
Button(action: {
showingCreation = true
}, label: {
Image(systemName: "plus")
})
.sheet(isPresented: $showingCreation) {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable, showing: $showingCreation)
}
}
}
}
2022-12-22 22:20:03 +00:00
@ViewBuilder
var setupNoticeView: some View {
Button(action: {
runningSetup = true
}, label: {
Group {
if hasRunSetup && !agentStatusChecker.running {
Text("Secret Agent Is Not Running")
} else {
Text("Setup Secretive")
2020-09-22 06:12:50 +00:00
}
2022-12-22 22:20:03 +00:00
}
.font(.headline)
.foregroundColor(.white)
})
2022-12-22 22:55:53 +00:00
.buttonStyle(ToolbarButtonStyle(color: .orange))
2020-03-04 07:14:38 +00:00
}
2020-03-15 21:03:57 +00:00
2022-12-22 22:20:03 +00:00
@ViewBuilder
var runningNoticeView: some View {
Button(action: {
showingAgentInfo = true
}, label: {
HStack {
Text("Agent is Running")
2020-09-22 06:12:50 +00:00
.font(.headline)
2022-12-22 22:55:53 +00:00
.foregroundColor(colorScheme == .light ? Color(white: 0.3) : .white)
2022-12-22 22:20:03 +00:00
Circle()
.frame(width: 10, height: 10)
.foregroundColor(Color.green)
}
})
2022-12-22 22:55:53 +00:00
.buttonStyle(ToolbarButtonStyle(lightColor: .black.opacity(0.05), darkColor: .white.opacity(0.05)))
2022-12-22 22:20:03 +00:00
.popover(isPresented: $showingAgentInfo, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
VStack {
Text("SecretAgent is Running")
.font(.title)
.padding(5)
Text("SecretAgent is a process that runs in the background to sign requests, so you don't need to keep Secretive open all the time.\n\n**You can close Secretive, and everything will still keep working.**")
.frame(width: 300)
}
.padding()
}
}
@ViewBuilder
var appPathNoticeView: some View {
if !ApplicationDirectoryController().isInApplicationsDirectory {
Button(action: {
showingAppPathNotice = true
}, label: {
Group {
Text("Secretive Is Not in Applications Folder")
}
2022-12-22 22:20:03 +00:00
.font(.headline)
.foregroundColor(.white)
})
2022-12-22 22:55:53 +00:00
.buttonStyle(ToolbarButtonStyle(color: .orange))
2022-12-22 22:20:03 +00:00
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) {
VStack {
Image(systemName: "exclamationmark.triangle")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 64)
Text("Secretive needs to be in your Applications folder to work properly. Please move it and relaunch.")
.frame(maxWidth: 300)
}
.padding()
}
2020-03-15 21:03:57 +00:00
}
}
2020-03-11 08:53:31 +00:00
}
2020-03-12 06:07:22 +00:00
#if DEBUG
struct ContentView_Previews: PreviewProvider {
2020-09-22 06:12:50 +00:00
private static let storeList: SecretStoreList = {
let list = SecretStoreList()
list.add(store: SecureEnclave.Store())
list.add(store: SmartCard.Store())
return list
}()
private static let agentStatusChecker = AgentStatusChecker()
private static let justUpdatedChecker = JustUpdatedChecker()
@State var hasRunSetup = false
@State private var showingSetup = false
@State private var showingCreation = false
static var previews: some View {
Group {
2020-09-22 06:12:50 +00:00
// Empty on modifiable and nonmodifiable
ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
.environmentObject(Preview.storeList(stores: [Preview.Store(numberOfRandomSecrets: 0)], modifiableStores: [Preview.StoreModifiable(numberOfRandomSecrets: 0)]))
.environmentObject(PreviewUpdater())
.environmentObject(agentStatusChecker)
// 5 items on modifiable and nonmodifiable
ContentView<PreviewUpdater, AgentStatusChecker>(showingCreation: .constant(false), runningSetup: .constant(false), hasRunSetup: .constant(true))
.environmentObject(Preview.storeList(stores: [Preview.Store()], modifiableStores: [Preview.StoreModifiable()]))
.environmentObject(PreviewUpdater())
.environmentObject(agentStatusChecker)
}
2020-09-22 06:12:50 +00:00
.environmentObject(agentStatusChecker)
}
}
2020-03-12 06:07:22 +00:00
#endif
2022-12-22 22:20:03 +00:00