diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 1.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 1.png new file mode 100644 index 00000000000..ab4f1cb881b Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 2.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 2.png new file mode 100644 index 00000000000..ab4f1cb881b Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark.png new file mode 100644 index 00000000000..ab4f1cb881b Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionDark.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 1.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 1.png new file mode 100644 index 00000000000..0cdfe4ada68 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 2.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 2.png new file mode 100644 index 00000000000..0cdfe4ada68 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight.png b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight.png new file mode 100644 index 00000000000..0cdfe4ada68 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/AccessibilityPermissionLight.png differ diff --git a/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/Contents.json b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/Contents.json new file mode 100644 index 00000000000..4272aa3879f --- /dev/null +++ b/mac/Config/Config/Assets.xcassets/AccessibilityPermission.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "AccessibilityPermissionLight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "AccessibilityPermissionDark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "AccessibilityPermissionLight 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "AccessibilityPermissionDark 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "AccessibilityPermissionLight 2.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "AccessibilityPermissionDark 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/Contents.json b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/Contents.json new file mode 100644 index 00000000000..803796eafb0 --- /dev/null +++ b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "EnableKeymanLight.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "EnableKeymanDark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "EnableKeymanLight 2.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "EnableKeymanDark 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "EnableKeymanLight 1.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "EnableKeymanDark 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 1.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 1.png new file mode 100644 index 00000000000..6b272d3b19e Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 2.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 2.png new file mode 100644 index 00000000000..6b272d3b19e Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark.png new file mode 100644 index 00000000000..6b272d3b19e Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanDark.png differ diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 1.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 1.png new file mode 100644 index 00000000000..6eb470fb7eb Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 2.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 2.png new file mode 100644 index 00000000000..6eb470fb7eb Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight.png b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight.png new file mode 100644 index 00000000000..6eb470fb7eb Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/EnableKeyman.imageset/EnableKeymanLight.png differ diff --git a/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/Contents.json b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/Contents.json new file mode 100644 index 00000000000..18495eb1064 --- /dev/null +++ b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "InstallerIcon.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "InstallerIcon 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "InstallerIcon 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 1.png b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 1.png new file mode 100644 index 00000000000..0637a896d74 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 2.png b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 2.png new file mode 100644 index 00000000000..0637a896d74 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon.png b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon.png new file mode 100644 index 00000000000..0637a896d74 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/InstallerIcon.imageset/InstallerIcon.png differ diff --git a/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Contents.json b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Contents.json new file mode 100644 index 00000000000..f9d3e9953d1 --- /dev/null +++ b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "Keyman-256.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Keyman-256 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Keyman-256 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 1.png b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 1.png new file mode 100644 index 00000000000..8aebcb40337 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 2.png b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 2.png new file mode 100644 index 00000000000..8aebcb40337 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256.png b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256.png new file mode 100644 index 00000000000..8aebcb40337 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/Keyman-256.imageset/Keyman-256.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Contents.json b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Contents.json new file mode 100644 index 00000000000..5d8fa4a6b38 --- /dev/null +++ b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Contents.json @@ -0,0 +1,56 @@ +{ + "images" : [ + { + "filename" : "keyman logo BIG (2) 3.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Keyman_Logo_BIG_Dark.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "keyman logo BIG (2) 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Keyman_Logo_BIG_Dark 1.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "keyman logo BIG (2) 2.png", + "idiom" : "universal", + "scale" : "3x" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "filename" : "Keyman_Logo_BIG_Dark 2.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 1.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 1.png new file mode 100644 index 00000000000..67ddbb7bea1 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 2.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 2.png new file mode 100644 index 00000000000..67ddbb7bea1 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark.png new file mode 100644 index 00000000000..67ddbb7bea1 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/Keyman_Logo_BIG_Dark.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 1.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 1.png new file mode 100644 index 00000000000..d7243fbab63 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 1.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 2.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 2.png new file mode 100644 index 00000000000..d7243fbab63 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 2.png differ diff --git a/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 3.png b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 3.png new file mode 100644 index 00000000000..d7243fbab63 Binary files /dev/null and b/mac/Config/Config/Assets.xcassets/KeymanLogo-BIG.imageset/keyman logo BIG (2) 3.png differ diff --git a/mac/Config/Config/ConfigApp.swift b/mac/Config/Config/ConfigApp.swift index e8ed3eb54fb..0bf80a3b68d 100644 --- a/mac/Config/Config/ConfigApp.swift +++ b/mac/Config/Config/ConfigApp.swift @@ -16,12 +16,15 @@ struct ConfigApp: App { @Environment(\.openWindow) private var openWindow var body: some Scene { + + Window("Configuration", id: "config") { ConfigView() .environmentObject(settings) .task { if !installation.isInstallationComplete() { openWindow(id: "install") + openWindow(id: "new install") } } } @@ -29,5 +32,14 @@ struct ConfigApp: App { InstallView() .environmentObject(installation) } + + + Window("New Installation", id: "new install") { + ParentInstallView() + .environmentObject(installation) + } + .defaultSize(width: 500, height: 400) + .windowResizability(.contentSize) + } } diff --git a/mac/Config/Config/InstallationViews/CompletedInstallView.swift b/mac/Config/Config/InstallationViews/CompletedInstallView.swift new file mode 100644 index 00000000000..9d1a460ab0a --- /dev/null +++ b/mac/Config/Config/InstallationViews/CompletedInstallView.swift @@ -0,0 +1,46 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-01 + * + * View used for notifying the user that installation is complete. + */ + +import SwiftUI + +struct CompletedInstallView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + + var versionText: String { + if let version = installation.installationState?.keymanVersion { + return "Version: \(version) " + } else { + return "No version to display" + } + } + + var body: some View { + + VStack { + + Spacer() + + KeymanLogo(namespace: namespace) + Text(versionText) + .foregroundStyle(.secondary) + + Spacer() + + GradientDivider(namespace: namespace) + .padding(.bottom, 8) + HStack { + Text("Installation complete") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + NavigationButton(action: .dismiss) + } + } + } +} + diff --git a/mac/Config/Config/InstallationViews/EnableInputMethodView.swift b/mac/Config/Config/InstallationViews/EnableInputMethodView.swift new file mode 100644 index 00000000000..a855ffffa9e --- /dev/null +++ b/mac/Config/Config/InstallationViews/EnableInputMethodView.swift @@ -0,0 +1,61 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-01 + * + * View used for directing the user to enable the Keyman input method. + */ + +import SwiftUI + +struct EnableInputMethodView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + let onContinue: () -> Void + @State var enableButtonPressed : Bool = false + + var body: some View { + VStack { + Text("Enable Keyman") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + GradientDivider(namespace: namespace) + + Spacer() + + Image("EnableKeyman") + .interpolation(.high) + .resizable() + .scaledToFit() + .frame(height: 150) + .padding(.horizontal, 100) + .padding(.bottom, 8) + Text("To use Keyman, enable the Keyman input method in System Settings.") + .multilineTextAlignment(.center) + + Spacer() + + HStack { + Text("Enable input method") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + Button { + enableButtonPressed = true + installation.executeNextInstallationTask() + } label: { + Text("Enable") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + .matchedGeometryEffect(id: "actionButton", in: namespace) + NavigationButton(action: .advance, onContinue: onContinue) + .disabled(!enableButtonPressed) + } + } + } +} diff --git a/mac/Config/Config/InstallationViews/GradientDivider.swift b/mac/Config/Config/InstallationViews/GradientDivider.swift new file mode 100644 index 00000000000..bf7fb5c4df5 --- /dev/null +++ b/mac/Config/Config/InstallationViews/GradientDivider.swift @@ -0,0 +1,30 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-01 + * + * View used for creating a thin divider with a Keyman color gradient. + */ + +import SwiftUI + +struct GradientDivider: View { + let namespace: Namespace.ID + var id: String = "divider" + + var body: some View { + Rectangle() + .fill(LinearGradient( + colors: [ + Color(.keymanBlue), + Color(.keymanOrange), + Color(.keymanRed) + ], + startPoint: .leading, + endPoint: .trailing + )) + .frame(height: 1) + .opacity(0.5) + .matchedGeometryEffect(id: id, in: namespace) + } +} diff --git a/mac/Config/Config/InstallationViews/GrantAccessibiltyPermissionView.swift b/mac/Config/Config/InstallationViews/GrantAccessibiltyPermissionView.swift new file mode 100644 index 00000000000..f95d57dc85c --- /dev/null +++ b/mac/Config/Config/InstallationViews/GrantAccessibiltyPermissionView.swift @@ -0,0 +1,133 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-03 + * + * View used for directing the user to grant Keyman accessibility permission. + */ + +import SwiftUI +import AppKit + +func openAccessibilitySettings() { + if let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Accessibility") { + NSWorkspace.shared.open(url) + } +} + +struct GrantAccessibiltyPermissionView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + let onContinue: () -> Void + + @State var openSettingsButtonPressed: Bool = false + @State var checkingPermission: Bool = false + @State var advancementRequestedAndPermissionNotGranted: Bool = false + + var body: some View { + VStack { + Text("Grant Accessibilty Permission") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + GradientDivider(namespace: namespace) + .padding(.bottom, 8) + if checkingPermission { + HStack { + ProgressView() + .controlSize(.small) + + Text("Checking...") + } + } + + Spacer() + + if advancementRequestedAndPermissionNotGranted == true { + Text("Access has not been granted.") + .foregroundStyle(Color.red) + .padding(7) + .background { + Capsule() + .fill(.quaternary.opacity(0.6)) + } + } + + Spacer() + + Image("AccessibilityPermission") + .interpolation(.high) + .resizable() + .scaledToFit() + .frame(height: 130) + .padding(.bottom, 8) + Text("Ensure Keyman.app is toggled to provide it with necessary control in System Settings > Privacy & Security > Accessibility.") + .multilineTextAlignment(.center) + + Spacer() + + HStack { + + Text("Grant accessibility control") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + Button { + if installation.currentTask()?.taskType == .requestAccess { + openSettingsButtonPressed = true + installation.executeNextInstallationTask() + } else { + openAccessibilitySettings() + } + } label: { + Text("Open Settings") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + .matchedGeometryEffect(id: "actionButton", in: namespace) + Button { + checkingPermission = true + installation.executeNextInstallationTask() + + } label: { + Text("Continue") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + .disabled(!openSettingsButtonPressed) + .clipShape(Capsule()) + } + } + .onReceive( NotificationCenter.default.publisher(for: .accessibilityGranted)) { notification in + withAnimation(.smooth) { + advancementRequestedAndPermissionNotGranted = true + onContinue() + } + } + .onReceive( NotificationCenter.default.publisher(for: .accessibilityNotGranted)) { notification in + withAnimation(.smooth) { + checkingPermission = false + advancementRequestedAndPermissionNotGranted = true + } + } + } +} + +struct GrantAccessibiltyPermissionView_Previews: PreviewProvider { + struct PreviewHost: View { + @Namespace private var namespace + private let installation = InstallationContainer() + + var body: some View { + GrantAccessibiltyPermissionView(namespace: namespace, onContinue: {}) + .environmentObject(installation) + } + } + + static var previews: some View { + PreviewHost() + } +} diff --git a/mac/Config/Config/InstallationViews/InitialRepairView.swift b/mac/Config/Config/InstallationViews/InitialRepairView.swift new file mode 100644 index 00000000000..6b376b51312 --- /dev/null +++ b/mac/Config/Config/InstallationViews/InitialRepairView.swift @@ -0,0 +1,63 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-27 + * + * View used for providing an opening screen for the repair phase. + */ + +import SwiftUI + +import SwiftUI +import AppKit +internal import UniformTypeIdentifiers + +struct InitialRepairView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + + var body: some View { + VStack { + Label("Repairs Required", systemImage: "hand.raised.fill") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + GradientDivider(namespace: namespace) + + Spacer() + + Image(systemName: "hammer.circle.fill") + .font(.system(size: 100)) + .padding(.bottom, 16) + Text("One or more Keyman components or permissions require your attention. Complete the following steps to restore your Keyman installation.") + .multilineTextAlignment(.center) + + Spacer() + + HStack { + Text("Resolve Issues") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + NavigationButton(action: .advance) + } + } + } +} + +private struct InitialRepairViewPreview: View { + @Namespace private var namespace + private let installation = InstallationContainer() + + var body: some View { + InitialRepairView(namespace: namespace) + .environmentObject(installation) + } +} + +#Preview { + InitialRepairViewPreview() +} diff --git a/mac/Config/Config/InstallationViews/InstallPage.swift b/mac/Config/Config/InstallationViews/InstallPage.swift new file mode 100644 index 00000000000..bc58ce9b185 --- /dev/null +++ b/mac/Config/Config/InstallationViews/InstallPage.swift @@ -0,0 +1,18 @@ +/* + * 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 initial + case completed + case enableInputMethod + case allowSecurityPermission + case rerunInstaller + case restartComputer +} diff --git a/mac/Config/Config/InstallationViews/KeymanLogo.swift b/mac/Config/Config/InstallationViews/KeymanLogo.swift new file mode 100644 index 00000000000..ddbf634e013 --- /dev/null +++ b/mac/Config/Config/InstallationViews/KeymanLogo.swift @@ -0,0 +1,23 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-02 + * + * View used to display the large Keyman logo. + */ + +import SwiftUI + +struct KeymanLogo: View { + let namespace: Namespace.ID + + var body: some View { + Image("KeymanLogo-BIG") + .interpolation(.high) + .resizable() + .scaledToFit() + .frame(height: 60) + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace, anchor: .top) + } +} diff --git a/mac/Config/Config/InstallationViews/NavigationButton.swift b/mac/Config/Config/InstallationViews/NavigationButton.swift new file mode 100644 index 00000000000..ae6632d7b4c --- /dev/null +++ b/mac/Config/Config/InstallationViews/NavigationButton.swift @@ -0,0 +1,48 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-02 + * + * View used to display a simple continue or close button. + */ + +import SwiftUI +import Combine + +enum ButtonAction { + case advance + case dismiss +} + +struct NavigationButton: View { + @Environment(\.dismiss) private var dismiss + @EnvironmentObject var installation: InstallationContainer + + var action: ButtonAction = .advance + var onContinue: () -> Void = {} + + var body: some View { + Button { + switch action { + case .advance: + withAnimation(.smooth) { + onContinue() + } + case .dismiss: + dismiss() + } + } label: { + switch action { + case .advance: + Text("Continue") + .padding(.horizontal, 16) + .padding(.vertical, 4) + case .dismiss: + Text("Close") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + } + .clipShape(Capsule()) + } +} diff --git a/mac/Config/Config/InstallationViews/NewInstallView.swift b/mac/Config/Config/InstallationViews/NewInstallView.swift new file mode 100644 index 00000000000..51f5c5d0d11 --- /dev/null +++ b/mac/Config/Config/InstallationViews/NewInstallView.swift @@ -0,0 +1,53 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-01 + * + * View used for providing an opening screen after the installation package has been run + */ + +import SwiftUI +import Foundation + + +struct NewInstallView: View { + + @EnvironmentObject var installation: InstallationContainer + @Environment(\.dismiss) private var dismiss + let namespace: Namespace.ID + let onContinue: () -> Void + var versionText: String { + if let version = installation.installationState?.keymanVersion { + return "Version: \(version) " + } else { + return "No version to display" + } + } + + var body: some View { + + VStack { + + Spacer() + + KeymanLogo(namespace: namespace) + + + Text(versionText) + .foregroundStyle(.secondary) + + Spacer() + + GradientDivider(namespace: namespace) + .padding(.bottom, 8) + + HStack { + Text("Proceed to continue with installation") + .font(.title2) + .multilineTextAlignment(.center) + .frame(maxWidth: .infinity, alignment: .leading) + NavigationButton(action: .advance, onContinue: onContinue) + } + } + } +} diff --git a/mac/Config/Config/InstallationViews/ParentInstallView.swift b/mac/Config/Config/InstallationViews/ParentInstallView.swift new file mode 100644 index 00000000000..da3a05e9c5d --- /dev/null +++ b/mac/Config/Config/InstallationViews/ParentInstallView.swift @@ -0,0 +1,85 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-06-29 + * + * Main view used for configuring Keyman + */ + +import SwiftUI + +struct ParentInstallView: View { + @EnvironmentObject var installation: InstallationContainer + @Namespace var animation + @State public var currentPage: InstallPage = .loading + + func chooseCurrentPage() { + if installation.installationPhase.hasTasks { + switch installation.currentTask()?.taskType { + case .prepareNewInstall: currentPage = .initial + case .enableInputMethod: currentPage = .enableInputMethod + case .requestAccess: currentPage = .allowSecurityPermission + case .confirmAccess: print("Access granted") + case .requestRestart: currentPage = .restartComputer + default: currentPage = .completed + } + } else { + switch installation.installationPhase { + case .evaluatingInstallation: currentPage = .loading + case .inputMethodMissing, .inputMethodOutdated: currentPage = .rerunInstaller + case .installationComplete: + if installation.getHasDisplayedInstallationComplete() { + currentPage = .completed + installation.setHasDisplayedInstallationComplete() + } + default: currentPage = .completed + } + } + print("currentPage is now \(currentPage)") + } + + var body: some View { + ZStack { + switch currentPage { + case .loading: ProgressView() + case .initial: NewInstallView(namespace: animation,onContinue: { + installation.executeNextInstallationTask() + chooseCurrentPage() + }) + case .completed: CompletedInstallView(namespace: animation) + case .enableInputMethod: EnableInputMethodView(namespace: animation, onContinue: chooseCurrentPage) + case .allowSecurityPermission: GrantAccessibiltyPermissionView(namespace: animation, onContinue: chooseCurrentPage) + case .rerunInstaller: RerunInstallerView(namespace: animation) + case .restartComputer: RestartComputerView(namespace: animation) + } + } + .onAppear { + if installation.installationPhase == .evaluatingInstallation { + currentPage = .loading + Task { + while installation.installationPhase == .evaluatingInstallation { + try? await Task.sleep(for: .milliseconds(100)) + } + await MainActor.run { + withAnimation(.smooth) { + chooseCurrentPage() + } + } + } + } else { + chooseCurrentPage() + } + } + .onReceive( NotificationCenter.default.publisher(for: .installationRepairStarted)) { notification in + } + .padding() + .frame(minWidth: 600) + .frame(minHeight: 400) + } +} + +#Preview { + let installation = InstallationContainer() + ParentInstallView() + .environmentObject(installation) +} diff --git a/mac/Config/Config/InstallationViews/RerunInstallerView.swift b/mac/Config/Config/InstallationViews/RerunInstallerView.swift new file mode 100644 index 00000000000..a452b115f28 --- /dev/null +++ b/mac/Config/Config/InstallationViews/RerunInstallerView.swift @@ -0,0 +1,59 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-21 + * + * View used for directing the user to rerun the installer. + * This is in the case that the input method needs to be restored. + */ + +import SwiftUI +import AppKit +internal import UniformTypeIdentifiers + +struct RerunInstallerView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + + var body: some View { + VStack { + Text("Missing Keyman Components") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + GradientDivider(namespace: namespace) + + Spacer() + + Image("InstallerIcon") + .interpolation(.high) + .resizable() + .scaledToFit() + .frame(height: 130) + .padding(.bottom, 8) + Text("Your Keyman input method is either missing or outdated. Run the Keyman installer to install a new version.") + .multilineTextAlignment(.center) + + Spacer() + + HStack { + Text("Run Keyman installer") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + Button { + installation.executeNextInstallationTask() + } label: { + Text("Open Installer") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + .matchedGeometryEffect(id: "actionButton", in: namespace) + NavigationButton(action: .dismiss) + } + } + } +} diff --git a/mac/Config/Config/InstallationViews/RestartComputerView.swift b/mac/Config/Config/InstallationViews/RestartComputerView.swift new file mode 100644 index 00000000000..c997bee309e --- /dev/null +++ b/mac/Config/Config/InstallationViews/RestartComputerView.swift @@ -0,0 +1,47 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-21 + * + * View used for directing the user to restart their mac. + */ + +import SwiftUI + +struct RestartComputerView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + + var body: some View { + VStack { + Text("Restart Computer") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + + Spacer() + + Image(systemName: "restart.circle.fill") + .font(.system(size: 100)) + .padding(16) + Text("Restart your Mac to complete the installation. After restarting, open Keyman Configuration again if it doesn't launch automatically.") + .multilineTextAlignment(.center) + .padding(.bottom, 8) + + Spacer() + + GradientDivider(namespace: namespace) + .padding(.bottom, 8) + HStack { + Text("Finish installation") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + NavigationButton(action: .dismiss) + } + } + .onAppear { + installation.executeNextInstallationTask() + } + } +} diff --git a/mac/Copy of keyman logo BIG 600.png b/mac/Copy of keyman logo BIG 600.png new file mode 100644 index 00000000000..8fab27b6a46 Binary files /dev/null and b/mac/Copy of keyman logo BIG 600.png differ