mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-26 17:47:40 +00:00
feat(mac): Responding to code review
This commit is contained in:
parent
47aa28b042
commit
cfb1bedebc
6 changed files with 53 additions and 58 deletions
|
|
@ -32,7 +32,7 @@ struct ConfigApp: App {
|
|||
.environmentObject(installation)
|
||||
}
|
||||
Window("Installation", id: "install") {
|
||||
ParentInstallView()
|
||||
MainInstallView()
|
||||
.environmentObject(installation)
|
||||
}
|
||||
.windowResizability(.contentSize)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,19 @@ struct GrantAccessibiltyPermissionView: View {
|
|||
let namespace: Namespace.ID
|
||||
let onContinue: () -> Void
|
||||
|
||||
/**
|
||||
* The flow of this view depends on the following @State variables.
|
||||
* Once the user presses "Open Settings" in order to toggle the security permission they will be allowed to continue.
|
||||
* When they press "Continue," a loading symbol will run until the view receives a notification of whether the access is
|
||||
* granted or not. If not granted, an error message will appear. If granted, the user will be moved to the next screen.
|
||||
*/
|
||||
|
||||
// Tracks if the user clicked "Open Settings" (Enables the "Continue" button)
|
||||
@State var openSettingsButtonPressed: Bool = false
|
||||
// Tracks if the app is currently running the background permission check
|
||||
@State var checkingPermission: Bool = false
|
||||
@State var advancementRequestedAndPermissionNotGranted: Bool = false
|
||||
// Tracks if the user clicked "Continue" but permission is still missing
|
||||
@State var permissionNotGrantedAfterPrompt: Bool = false
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
|
|
@ -58,12 +68,13 @@ struct GrantAccessibiltyPermissionView: View {
|
|||
|
||||
if checkingPermission {
|
||||
HStack {
|
||||
// Shows spinner AKA ProgressView()
|
||||
ProgressView()
|
||||
.controlSize(.small)
|
||||
|
||||
Text("Checking...")
|
||||
}
|
||||
} else if advancementRequestedAndPermissionNotGranted {
|
||||
} else if permissionNotGrantedAfterPrompt {
|
||||
Text("Access has not been granted.")
|
||||
.foregroundStyle(Color.red)
|
||||
.padding(7)
|
||||
|
|
@ -89,7 +100,9 @@ struct GrantAccessibiltyPermissionView: View {
|
|||
.tint(.blue)
|
||||
.clipShape(Capsule())
|
||||
.matchedGeometryEffect(id: "actionButton", in: namespace)
|
||||
|
||||
Button {
|
||||
// Trigger the system task to check for accessibility permission
|
||||
checkingPermission = true
|
||||
installation.executeCurrentInstallationTask()
|
||||
} label: {
|
||||
|
|
@ -101,16 +114,18 @@ struct GrantAccessibiltyPermissionView: View {
|
|||
.clipShape(Capsule())
|
||||
}
|
||||
}
|
||||
// Triggered when the system confirms accessibility has been granted.
|
||||
.onReceive( NotificationCenter.default.publisher(for: .accessibilityGranted)) { notification in
|
||||
withAnimation(.smooth) {
|
||||
advancementRequestedAndPermissionNotGranted = true
|
||||
onContinue()
|
||||
permissionNotGrantedAfterPrompt = false
|
||||
onContinue() // Moves the user to the next screen
|
||||
}
|
||||
}
|
||||
// Triggered when the system confirms accessibility has not been granted.
|
||||
.onReceive( NotificationCenter.default.publisher(for: .accessibilityNotGranted)) { notification in
|
||||
withAnimation(.smooth) {
|
||||
checkingPermission = false
|
||||
advancementRequestedAndPermissionNotGranted = true
|
||||
checkingPermission = false // Stops showing the loading spinner
|
||||
permissionNotGrantedAfterPrompt = true // Shows the red error text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ import AppKit
|
|||
internal import UniformTypeIdentifiers
|
||||
|
||||
struct InitialRepairView: View {
|
||||
|
||||
@EnvironmentObject var installation: InstallationContainer
|
||||
|
||||
let namespace: Namespace.ID
|
||||
let onContinue: () -> Void
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +0,0 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by Eli Schantz on 2026-07-21
|
||||
*
|
||||
* The @State variable currentPage is of type InstallPage.
|
||||
* These pages are used to easily connect each installation phase with a SwiftUI view in ParentInstallView.
|
||||
*/
|
||||
|
||||
enum InstallPage: String, CaseIterable {
|
||||
case loading
|
||||
case initialInstall
|
||||
case initialRepair
|
||||
case completed
|
||||
case enableInputMethod
|
||||
case allowSecurityPermission
|
||||
case rerunInstaller
|
||||
}
|
||||
|
|
@ -8,11 +8,33 @@
|
|||
|
||||
import SwiftUI
|
||||
|
||||
struct ParentInstallView: View {
|
||||
enum InstallPage: String, CaseIterable {
|
||||
case loading
|
||||
case initialInstall
|
||||
case initialRepair
|
||||
case completed
|
||||
case enableInputMethod
|
||||
case allowSecurityPermission
|
||||
case rerunInstaller
|
||||
case restartMac
|
||||
}
|
||||
|
||||
struct MainInstallView: View {
|
||||
@EnvironmentObject var installation: InstallationContainer
|
||||
/**
|
||||
* A namespace is created here and passed to child views.
|
||||
* Any subviews with the same string id and this namespace
|
||||
* will animate smoothly when changing positions or states.
|
||||
*/
|
||||
@Namespace var animation
|
||||
@State public var currentPage: InstallPage = .loading
|
||||
|
||||
/**
|
||||
* chooseCurrentPage() will update the @State var currentPage according to the current task.
|
||||
* If there is a task involved with the installationPhase, it will display the page associated with that task.
|
||||
* If there is not a task involved with the installationPhase, it will display the page associated with that phase.
|
||||
*/
|
||||
|
||||
func chooseCurrentPage() {
|
||||
if installation.installationPhase.hasTasks {
|
||||
switch installation.currentTask()?.taskType {
|
||||
|
|
@ -21,6 +43,7 @@ struct ParentInstallView: View {
|
|||
case .enableInputMethod: currentPage = .enableInputMethod
|
||||
case .requestAccess: currentPage = .allowSecurityPermission
|
||||
case .confirmAccess: currentPage = .allowSecurityPermission
|
||||
case .requestRestart: currentPage = .restartMac
|
||||
default: currentPage = .completed
|
||||
}
|
||||
} else {
|
||||
|
|
@ -40,7 +63,9 @@ struct ParentInstallView: View {
|
|||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
|
||||
VStack {
|
||||
// The switch statement below updates the view this VStack contains whenever currentPage changes value
|
||||
switch currentPage {
|
||||
case .loading: ProgressView()
|
||||
case .initialInstall: InitialInstallView(namespace: animation,onContinue: {
|
||||
|
|
@ -55,11 +80,13 @@ struct ParentInstallView: View {
|
|||
case .enableInputMethod: EnableInputMethodView(namespace: animation, onContinue: chooseCurrentPage)
|
||||
case .allowSecurityPermission: GrantAccessibiltyPermissionView(namespace: animation, onContinue: chooseCurrentPage)
|
||||
case .rerunInstaller: RerunInstallerView(namespace: animation)
|
||||
case .restartMac: RestartComputerView(namespace: animation)
|
||||
}
|
||||
}
|
||||
// While the installer is evaluating the Keyman installation, the loading screen will be shown
|
||||
.onAppear {
|
||||
print("LOL ", installation.installationPhase)
|
||||
print("LOL ", installation.currentTask()?.taskType ?? "no task available")
|
||||
print("From MainInstallView onAppear: ", installation.installationPhase)
|
||||
print("From MainInstallView onAppear: ", installation.currentTask()?.taskType ?? "no task available")
|
||||
|
||||
if installation.installationPhase == .evaluatingInstallation {
|
||||
currentPage = .loading
|
||||
|
|
@ -70,8 +97,6 @@ struct ParentInstallView: View {
|
|||
await MainActor.run {
|
||||
withAnimation(.smooth) {
|
||||
chooseCurrentPage()
|
||||
print("LOOL ", installation.installationPhase)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,20 +11,6 @@ import SwiftUI
|
|||
import AppKit
|
||||
internal import UniformTypeIdentifiers
|
||||
|
||||
private func chooseAndOpenKeymanInstaller() {
|
||||
let panel = NSOpenPanel()
|
||||
panel.message = "Open the Keyman .pkg file."
|
||||
panel.allowedContentTypes = [UTType(filenameExtension: "pkg")!]
|
||||
panel.directoryURL = FileManager.default.urls(
|
||||
for: .downloadsDirectory,
|
||||
in: .userDomainMask
|
||||
).first
|
||||
|
||||
if panel.runModal() == .OK, let url = panel.url {
|
||||
NSWorkspace.shared.open(url)
|
||||
}
|
||||
}
|
||||
|
||||
struct RerunInstallerView: View {
|
||||
@EnvironmentObject var installation: InstallationContainer
|
||||
let namespace: Namespace.ID
|
||||
|
|
@ -59,17 +45,6 @@ struct RerunInstallerView: View {
|
|||
Text("Run Keyman installer")
|
||||
.font(.title2)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Button {
|
||||
chooseAndOpenKeymanInstaller()
|
||||
} label: {
|
||||
Text("Open Installer")
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 4)
|
||||
}
|
||||
.buttonStyle(.borderedProminent)
|
||||
.tint(.blue)
|
||||
.clipShape(Capsule())
|
||||
.matchedGeometryEffect(id: "actionButton", in: namespace)
|
||||
NavigationButton(action: .dismiss)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue