secretive/Sources/Secretive/Views/SetupView.swift

296 lines
8.6 KiB
Swift
Raw Normal View History

2020-03-04 07:14:38 +00:00
import SwiftUI
struct SetupView: View {
2020-09-22 06:12:50 +00:00
@State var stepIndex = 0
@Binding var visible: Bool
@Binding var setupComplete: Bool
2020-03-04 07:14:38 +00:00
var body: some View {
2020-09-22 06:12:50 +00:00
GeometryReader { proxy in
VStack {
StepView(numberOfSteps: 3, currentStep: stepIndex, width: proxy.size.width)
GeometryReader { _ in
HStack(spacing: 0) {
SecretAgentSetupView(buttonAction: advance)
.frame(width: proxy.size.width)
SSHAgentSetupView(buttonAction: advance)
.frame(width: proxy.size.width)
UpdaterExplainerView {
visible = false
setupComplete = true
}
.frame(width: proxy.size.width)
}
2022-01-18 21:24:57 +00:00
.offset(x: -proxy.size.width * Double(stepIndex), y: 0)
2020-03-21 04:14:51 +00:00
}
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
}
2024-01-17 04:08:48 +00:00
.frame(minWidth: 500, idealWidth: 500, minHeight: 500, idealHeight: 500)
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
func advance() {
withAnimation(.spring()) {
stepIndex += 1
}
}
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
struct StepView: View {
let numberOfSteps: Int
let currentStep: Int
// Ideally we'd have a geometry reader inside this view doing this for us, but that crashes on 11.0b7
2022-01-18 21:24:57 +00:00
let width: Double
2020-09-22 06:12:50 +00:00
2020-03-04 07:14:38 +00:00
var body: some View {
2020-09-22 06:12:50 +00:00
ZStack(alignment: .leading) {
Rectangle()
.foregroundColor(.blue)
.frame(height: 5)
Rectangle()
.foregroundColor(.green)
2022-01-18 21:24:57 +00:00
.frame(width: max(0, ((width - (Constants.padding * 2)) / Double(numberOfSteps - 1)) * Double(currentStep) - (Constants.circleWidth / 2)), height: 5)
2020-03-04 07:14:38 +00:00
HStack {
ForEach(0..<numberOfSteps, id: \.self) { index in
2020-09-22 06:12:50 +00:00
ZStack {
if currentStep > index {
Circle()
.foregroundColor(.green)
.frame(width: Constants.circleWidth, height: Constants.circleWidth)
Text("setup_step_complete_symbol")
2020-09-22 06:12:50 +00:00
.foregroundColor(.white)
.bold()
} else {
Circle()
.foregroundColor(.blue)
.frame(width: Constants.circleWidth, height: Constants.circleWidth)
if currentStep == index {
Circle()
.strokeBorder(Color.white, lineWidth: 3)
.frame(width: Constants.circleWidth, height: Constants.circleWidth)
}
Text(String(describing: index + 1))
.foregroundColor(.white)
.bold()
}
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
if index < numberOfSteps - 1 {
Spacer(minLength: 30)
2020-03-04 07:14:38 +00:00
}
}
}
2020-09-22 06:12:50 +00:00
}.padding(Constants.padding)
}
}
extension StepView {
enum Constants {
2022-01-18 21:24:57 +00:00
static let padding: Double = 15
static let circleWidth: Double = 30
2020-09-22 06:12:50 +00:00
}
}
struct SetupStepView<Content> : View where Content : View {
let title: LocalizedStringKey
2020-09-22 06:12:50 +00:00
let image: Image
let bodyText: LocalizedStringKey
let buttonTitle: LocalizedStringKey
2020-09-22 06:12:50 +00:00
let buttonAction: () -> Void
let content: Content
init(title: LocalizedStringKey, image: Image, bodyText: LocalizedStringKey, buttonTitle: LocalizedStringKey, buttonAction: @escaping () -> Void = {}, @ViewBuilder content: () -> Content) {
2020-09-22 06:12:50 +00:00
self.title = title
self.image = image
self.bodyText = bodyText
self.buttonTitle = buttonTitle
self.buttonAction = buttonAction
self.content = content()
}
var body: some View {
VStack {
Text(title)
.font(.title)
Spacer()
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 64)
Spacer()
Text(bodyText)
.multilineTextAlignment(.center)
Spacer()
content
Spacer()
Button(buttonTitle) {
buttonAction()
}
}.padding()
}
}
struct SecretAgentSetupView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: "setup_agent_title",
image: Image(nsImage: NSApplication.shared.applicationIconImage),
bodyText: "setup_agent_description",
buttonTitle: "setup_agent_install_button",
2020-09-22 06:12:50 +00:00
buttonAction: install) {
Text("setup_agent_activity_monitor_description")
2020-09-22 06:12:50 +00:00
.multilineTextAlignment(.center)
2020-03-04 07:14:38 +00:00
}
}
2020-09-22 06:12:50 +00:00
func install() {
LaunchAgentController().install()
2020-09-22 06:12:50 +00:00
buttonAction()
}
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
struct SSHAgentSetupView: View {
let buttonAction: () -> Void
private static let controller = ShellConfigurationController()
@State private var selectedShellInstruction: ShellConfigInstruction = controller.shellInstructions.first!
2020-03-04 07:14:38 +00:00
var body: some View {
SetupStepView(title: "setup_ssh_title",
2020-09-22 06:12:50 +00:00
image: Image(systemName: "terminal"),
bodyText: "setup_ssh_description",
buttonTitle: "setup_ssh_added_manually_button",
2020-09-22 06:12:50 +00:00
buttonAction: buttonAction) {
Link("setup_third_party_faq_link", destination: URL(string: "https://github.com/maxgoedjen/secretive/blob/main/APP_CONFIG.md")!)
2020-09-22 06:12:50 +00:00
Picker(selection: $selectedShellInstruction, label: EmptyView()) {
ForEach(SSHAgentSetupView.controller.shellInstructions) { instruction in
Text(instruction.shell)
.tag(instruction)
.padding()
}
}.pickerStyle(SegmentedPickerStyle())
CopyableView(title: "setup_ssh_add_to_config_button_\(selectedShellInstruction.shellConfigPath)", image: Image(systemName: "greaterthan.square"), text: selectedShellInstruction.text)
Button("setup_ssh_add_for_me_button") {
2020-09-22 06:12:50 +00:00
let controller = ShellConfigurationController()
if controller.addToShell(shellInstructions: selectedShellInstruction) {
buttonAction()
2020-03-21 04:07:58 +00:00
}
}
2020-03-04 07:14:38 +00:00
}
2020-03-21 04:07:58 +00:00
}
2020-09-22 06:12:50 +00:00
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
class Delegate: NSObject, NSOpenSavePanelDelegate {
private let name: String
init(name: String) {
self.name = name
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
func panel(_ sender: Any, shouldEnable url: URL) -> Bool {
return url.lastPathComponent == name
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
}
struct UpdaterExplainerView: View {
let buttonAction: () -> Void
var body: some View {
SetupStepView(title: "setup_updates_title",
2020-09-22 06:12:50 +00:00
image: Image(systemName: "dot.radiowaves.left.and.right"),
bodyText: "setup_updates_description",
buttonTitle: "setup_updates_ok",
2020-09-22 06:12:50 +00:00
buttonAction: buttonAction) {
Link("setup_updates_readmore", destination: SetupView.Constants.updaterFAQURL)
2020-09-22 06:12:50 +00:00
}
}
2020-03-04 07:14:38 +00:00
}
extension SetupView {
2020-09-22 06:12:50 +00:00
2020-03-04 07:14:38 +00:00
enum Constants {
2020-09-22 06:12:50 +00:00
static let updaterFAQURL = URL(string: "https://github.com/maxgoedjen/secretive/blob/main/FAQ.md#whats-this-network-request-to-github")!
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
}
struct ShellConfigInstruction: Identifiable, Hashable {
var shell: String
var shellConfigDirectory: String
var shellConfigFilename: String
var text: String
var id: String {
shell
}
var shellConfigPath: String {
return (shellConfigDirectory as NSString).appendingPathComponent(shellConfigFilename)
}
2020-03-04 07:14:38 +00:00
}
2020-03-12 06:07:22 +00:00
#if DEBUG
2020-03-04 07:14:38 +00:00
struct SetupView_Previews: PreviewProvider {
2020-09-22 06:12:50 +00:00
static var previews: some View {
Group {
SetupView(visible: .constant(true), setupComplete: .constant(false))
}
}
}
struct SecretAgentSetupView_Previews: PreviewProvider {
static var previews: some View {
Group {
SecretAgentSetupView(buttonAction: {})
}
}
}
struct SSHAgentSetupView_Previews: PreviewProvider {
static var previews: some View {
Group {
SSHAgentSetupView(buttonAction: {})
}
}
}
struct UpdaterExplainerView_Previews: PreviewProvider {
2020-03-04 07:14:38 +00:00
static var previews: some View {
2020-09-22 06:12:50 +00:00
Group {
UpdaterExplainerView(buttonAction: {})
}
2020-03-04 07:14:38 +00:00
}
2020-09-22 06:12:50 +00:00
2020-03-04 07:14:38 +00:00
}
2020-03-12 06:07:22 +00:00
#endif