Switch toolbar items to viewbuilders

This commit is contained in:
Max Goedjen 2022-12-22 17:20:03 -05:00
parent acdf0baf3a
commit d429d090cf
No known key found for this signature in database
1 changed files with 157 additions and 107 deletions

View File

@ -9,6 +9,8 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
@Binding var showingCreation: Bool @Binding var showingCreation: Bool
@Binding var runningSetup: Bool @Binding var runningSetup: Bool
@Binding var hasRunSetup: Bool @Binding var hasRunSetup: Bool
@State var showingAgentInfo = false
@Environment(\.colorScheme) var colorScheme
@EnvironmentObject private var storeList: SecretStoreList @EnvironmentObject private var storeList: SecretStoreList
@EnvironmentObject private var updater: UpdaterType @EnvironmentObject private var updater: UpdaterType
@ -27,10 +29,10 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
} }
.frame(minWidth: 640, minHeight: 320) .frame(minWidth: 640, minHeight: 320)
.toolbar { .toolbar {
updateNotice toolbarItem(updateNoticeView, id: "update")
setupNotice toolbarItem(runningOrRunSetupView, id: "setup")
appPathNotice toolbarItem(appPathNoticeView, id: "appPath")
newItem toolbarItem(newItemView, id: "new")
} }
.sheet(isPresented: $runningSetup) { .sheet(isPresented: $runningSetup) {
SetupView(visible: $runningSetup, setupComplete: $hasRunSetup) SetupView(visible: $runningSetup, setupComplete: $hasRunSetup)
@ -41,121 +43,144 @@ struct ContentView<UpdaterType: UpdaterProtocol, AgentStatusCheckerType: AgentSt
extension ContentView { extension ContentView {
var updateNotice: ToolbarItem<Void, AnyView> {
guard let update = updater.update else { func toolbarItem(_ view: some View, id: String) -> ToolbarItem<String, some View> {
return ToolbarItem { AnyView(EmptyView()) } 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
} }
let color: Color }
let text: String
var updateNoticeContent: (String, Color)? {
guard let update = updater.update else { return nil }
if update.critical { if update.critical {
text = "Critical Security Update Required" return ("Critical Security Update Required", .red)
color = .red
} else { } else {
if updater.testBuild { if updater.testBuild {
text = "Test Build" return ("Test Build", .blue)
color = .blue
} else { } else {
text = "Update Available" return ("Update Available", .orange)
color = .orange
} }
} }
return ToolbarItem {
AnyView(
Button(action: {
selectedUpdate = update
}, label: {
Text(text)
.font(.headline)
.foregroundColor(.white)
})
.background(color)
.cornerRadius(5)
.popover(item: $selectedUpdate, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { update in
UpdateDetailView(update: update)
}
)
}
} }
var newItem: ToolbarItem<Void, AnyView> { @ViewBuilder
guard storeList.modifiableStore?.isAvailable ?? false else { var updateNoticeView: some View {
return ToolbarItem { AnyView(EmptyView()) } if let update = updater.update, let (text, color) = updateNoticeContent {
} Button(action: {
return ToolbarItem { selectedUpdate = update
AnyView( }, label: {
Button(action: { Text(text)
showingCreation = true
}, label: {
Image(systemName: "plus")
})
.sheet(isPresented: $showingCreation) {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable, showing: $showingCreation)
}
}
)
}
}
var setupNotice: ToolbarItem<Void, AnyView> {
return ToolbarItem {
AnyView(
Group {
if (runningSetup || !hasRunSetup || !agentStatusChecker.running) && !agentStatusChecker.developmentBuild {
Button(action: {
runningSetup = true
}, label: {
Group {
if hasRunSetup && !agentStatusChecker.running {
Text("Secret Agent Is Not Running")
} else {
Text("Setup Secretive")
}
}
.font(.headline)
.foregroundColor(.white)
})
.background(Color.orange)
.cornerRadius(5)
} else {
EmptyView()
}
}
)
}
}
var appPathNotice: ToolbarItem<Void, AnyView> {
let controller = ApplicationDirectoryController()
guard !controller.isInApplicationsDirectory else {
return ToolbarItem { AnyView(EmptyView()) }
}
return ToolbarItem {
AnyView(
Button(action: {
showingAppPathNotice = true
}, label: {
Group {
Text("Secretive Is Not in Applications Folder")
}
.font(.headline) .font(.headline)
.foregroundColor(.white) .foregroundColor(.white)
}) })
.background(Color.orange) .background(color)
.cornerRadius(5) .cornerRadius(5)
.popover(isPresented: $showingAppPathNotice, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { .popover(item: $selectedUpdate, attachmentAnchor: .point(.bottom), arrowEdge: .bottom) { update in
VStack { UpdateDetailView(update: update)
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.") @ViewBuilder
.frame(maxWidth: 300) var newItemView: some View {
} if storeList.modifiableStore?.isAvailable ?? false {
.padding() Button(action: {
showingCreation = true
}, label: {
Image(systemName: "plus")
})
.sheet(isPresented: $showingCreation) {
if let modifiable = storeList.modifiableStore {
CreateSecretView(store: modifiable, showing: $showingCreation)
} }
) }
}
}
@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")
}
}
.font(.headline)
.foregroundColor(.white)
})
.background(Color.orange)
.cornerRadius(5)
}
@ViewBuilder
var runningNoticeView: some View {
Button(action: {
showingAgentInfo = true
}, label: {
HStack {
Text("Agent is Running")
.font(.headline)
Circle()
.frame(width: 10, height: 10)
.foregroundColor(Color.green)
}
})
.background((colorScheme == .dark ? Color.white : Color.black).opacity(0.05))
.cornerRadius(5)
.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")
}
.font(.headline)
.foregroundColor(.white)
})
.background(Color.orange)
.cornerRadius(5)
.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()
}
} }
} }
@ -198,3 +223,28 @@ struct ContentView_Previews: PreviewProvider {
} }
#endif #endif
struct ToolbarButton: ButtonStyle {
private let lightColor: Color
private let darkColor: Color
@Environment(\.colorScheme) var colorScheme
init(color: Color) {
self.lightColor = color
self.darkColor = color
}
init(lightColor: Color, darkColor: Color) {
self.lightColor = lightColor
self.darkColor = darkColor
}
func makeBody(configuration: Configuration) -> some View {
configuration.label
// .buttonStyle(.bordered)
.padding()
.background(colorScheme == .light ? lightColor : darkColor)
.foregroundColor(.white)
}
}