diff --git a/mac/Config/Config.xcodeproj/project.pbxproj b/mac/Config/Config.xcodeproj/project.pbxproj index 3b8bb555a05..3dbdd408872 100644 --- a/mac/Config/Config.xcodeproj/project.pbxproj +++ b/mac/Config/Config.xcodeproj/project.pbxproj @@ -51,6 +51,11 @@ /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ + 3A8B4F0F301263BC00A18F80 /* Resources */ = { + isa = PBXFileSystemSynchronizedRootGroup; + path = Resources; + sourceTree = ""; + }; D87D6F492FAF95400083A95E /* Installation */ = { isa = PBXFileSystemSynchronizedRootGroup; path = Installation; @@ -101,6 +106,7 @@ D88F03BD2F50ED5000C02A31 = { isa = PBXGroup; children = ( + 3A8B4F0F301263BC00A18F80 /* Resources */, D87D6F492FAF95400083A95E /* Installation */, D88F03C82F50ED5000C02A31 /* Config */, D88F03E02F50ED5100C02A31 /* ConfigUITests */, @@ -142,6 +148,7 @@ dependencies = ( ); fileSystemSynchronizedGroups = ( + 3A8B4F0F301263BC00A18F80 /* Resources */, D87D6F492FAF95400083A95E /* Installation */, D88F03C82F50ED5000C02A31 /* Config */, ); diff --git a/mac/Config/Config/About/AboutPanelView.swift b/mac/Config/Config/About/AboutPanelView.swift new file mode 100644 index 00000000000..fa28d0dac61 --- /dev/null +++ b/mac/Config/Config/About/AboutPanelView.swift @@ -0,0 +1,56 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Eli Schantz on 2026-07-23 + * + * View shown when About Keyman Configuration is selected from the menu. + */ + +import SwiftUI +import KeymanSettings + +struct AboutPanelView: View { + + var body: some View { + VStack(spacing: 20) { + HStack(alignment: .top, spacing: 28) { + Image(nsImage: NSApp.applicationIconImage) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 104, height: 104) + + VStack(alignment: .leading, spacing: 6) { + Text("Keyman Configuration") + .font(.system(size: 32, weight: .semibold)) + Text("Version \(ConfigAppUtil.configAppVersion())") + .font(.system(size: 13)) + .foregroundStyle(.secondary) + + Spacer(minLength: 14) + + Text("Copyright © SIL Global") + .font(.system(size: 11)) + .foregroundStyle(.secondary) + } + + Spacer(minLength: 0) + } + + HStack { + Spacer() + Button("License Agreement") { + if let url = Bundle.main.url(forResource: "keyman-for-mac-os-license", withExtension: "html") { + NSWorkspace.shared.open(url) + } + } + } + } + .padding(.top, 32) + .padding(.horizontal, 42) + .padding(.bottom, 20) + .frame(width: 570, height: 200) + } +} +#Preview { + AboutPanelView() +} 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 49f8d2c6493..03c2358d3a3 100644 --- a/mac/Config/Config/ConfigApp.swift +++ b/mac/Config/Config/ConfigApp.swift @@ -7,6 +7,7 @@ */ import SwiftUI +import AppKit import KeymanSettings @main @@ -22,11 +23,13 @@ struct ConfigApp: App { .task { if !installation.getHasDisplayedInstallationComplete() { openWindow(id: "install") + openWindow(id: "new install") } } .onReceive(NotificationCenter.default.publisher(for: .installationRepairStarted)) { notification in openWindow(id: "install") } } + Window("Installation", id: "install") { MainInstallView() .environmentObject(installation) @@ -41,5 +44,53 @@ struct ConfigApp: App { InstallDebugView() .environmentObject(installation) } + + Window("New Installation", id: "new install") { + ParentInstallView() + .environmentObject(installation) + } + .defaultSize(width: 500, height: 400) + .windowResizability(.contentSize) + .commands { + CommandGroup(replacing: .appInfo) { + Button { + AboutPanelPresenter.showAboutPanel() + } label: { + Label("About Keyman Configuration", systemImage: "info.circle") + } + } + } + } +} + +@MainActor +private enum AboutPanelPresenter { + private static var aboutWindow: NSWindow? + + static func showAboutPanel() { + let contentView = AboutPanelView() + + let window = aboutWindow ?? makeAboutWindow() + window.contentView = NSHostingView(rootView: contentView) + window.center() + window.makeKeyAndOrderFront(nil) + aboutWindow = window + + NSApp.activate(ignoringOtherApps: true) + } + + private static func makeAboutWindow() -> NSWindow { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 570, height: 200), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + + window.titleVisibility = .hidden + window.titlebarAppearsTransparent = true + window.isReleasedWhenClosed = false + window.backgroundColor = .windowBackgroundColor + return window } } diff --git a/mac/Config/Config/InstallerViews/CheckAccessibiltyPermissionView.swift b/mac/Config/Config/InstallerViews/CheckAccessibiltyPermissionView.swift new file mode 100644 index 00000000000..d44d6cb806a --- /dev/null +++ b/mac/Config/Config/InstallerViews/CheckAccessibiltyPermissionView.swift @@ -0,0 +1,74 @@ +// +// CheckAccessiblityPermissionView.swift +// Config +// +// Created by Eli Schantz on 7/3/26. +// + +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 CheckAccessibiltyPermissionView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + let onContinue: () -> Void + + var body: some View { + VStack { + + Text("Check Accessibilty Permission") + .font(.title) + .bold() + .frame(maxWidth: .infinity, alignment: .center) + .matchedGeometryEffect(id: "title", in: namespace) + + GradientDivider(namespace: namespace) + + 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("Check accessibility control") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + + + Button { + _ = installation.requestAccessibility() + openAccessibilitySettings() + + } label: { + Text("Open Settings") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + .matchedGeometryEffect(id: "actionButton", in: namespace) + + NavigationButton(action: .advance, onContinue: onContinue) + } + } + } +} diff --git a/mac/Config/Config/InstallerViews/CompletedInstallView.swift b/mac/Config/Config/InstallerViews/CompletedInstallView.swift new file mode 100644 index 00000000000..d4fbf82c388 --- /dev/null +++ b/mac/Config/Config/InstallerViews/CompletedInstallView.swift @@ -0,0 +1,48 @@ +// +// CompletedInstallView.swift +// Config +// +// Created by Eli Schantz on 7/1/26. +// + +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/InstallerViews/EnableInputMethodView.swift b/mac/Config/Config/InstallerViews/EnableInputMethodView.swift new file mode 100644 index 00000000000..c51e7dc00cf --- /dev/null +++ b/mac/Config/Config/InstallerViews/EnableInputMethodView.swift @@ -0,0 +1,67 @@ +// +// EnableInputMethodView.swift +// Config +// +// Created by Eli Schantz on 7/1/26. +// + +import SwiftUI + +struct EnableInputMethodView: View { + @EnvironmentObject var installation: InstallationContainer + let namespace: Namespace.ID + let onContinue: () -> Void + + 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 { + _ = installation.enableKeymanInputMethod() + + } 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) + } + } + } +} diff --git a/mac/Config/Config/InstallerViews/GradientDivider.swift b/mac/Config/Config/InstallerViews/GradientDivider.swift new file mode 100644 index 00000000000..1263f4dadb5 --- /dev/null +++ b/mac/Config/Config/InstallerViews/GradientDivider.swift @@ -0,0 +1,29 @@ +// +// GradientDivider.swift +// Config +// +// Created by Eli Schantz on 7/1/26. +// +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/InstallerViews/InitialView.swift b/mac/Config/Config/InstallerViews/InitialView.swift new file mode 100644 index 00000000000..19ff52c3626 --- /dev/null +++ b/mac/Config/Config/InstallerViews/InitialView.swift @@ -0,0 +1,53 @@ +// +// InitialView.swift +// Config +// +// Created by Eli Schantz on 7/1/26. +// + +import SwiftUI +import Foundation + + +struct InitialView: 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) + .frame(maxWidth: .infinity, alignment: .leading) + + NavigationButton(action: .advance, onContinue: onContinue) + } + } + } +} diff --git a/mac/Config/Config/InstallerViews/InstallPage.swift b/mac/Config/Config/InstallerViews/InstallPage.swift new file mode 100644 index 00000000000..04866dfe84e --- /dev/null +++ b/mac/Config/Config/InstallerViews/InstallPage.swift @@ -0,0 +1,16 @@ +// +// InstallPage.swift +// Config +// +// Created by Eli Schantz on 7/21/26. +// + +enum InstallPage: String, CaseIterable { + case loading + case initial + case completed + case enableInputMethod + case allowSecurityPermission + case rerunInstaller + case restartComputer +} diff --git a/mac/Config/Config/InstallerViews/KeymanLogo.swift b/mac/Config/Config/InstallerViews/KeymanLogo.swift new file mode 100644 index 00000000000..286f67bd649 --- /dev/null +++ b/mac/Config/Config/InstallerViews/KeymanLogo.swift @@ -0,0 +1,22 @@ +// +// KeymanLogo.swift +// Config +// +// Created by Eli Schantz on 7/2/26. +// + +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/InstallerViews/NavigationButton.swift b/mac/Config/Config/InstallerViews/NavigationButton.swift new file mode 100644 index 00000000000..2f9eb03c9af --- /dev/null +++ b/mac/Config/Config/InstallerViews/NavigationButton.swift @@ -0,0 +1,53 @@ +// +// NavigationButton.swift +// Config +// +// Created by Eli Schantz on 7/2/26. +// + +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: + installation.executeNextInstallationTask() + onContinue() + case .dismiss: + dismiss() + } + + + } label: { + + switch action { + + case .dismiss: + Text("Close") + .padding(.horizontal, 16) + .padding(.vertical, 4) + + default: + Text("Continue") + .padding(.horizontal, 16) + .padding(.vertical, 4) + } + + } + .clipShape(Capsule()) + } +} diff --git a/mac/Config/Config/InstallerViews/ParentInstallView.swift b/mac/Config/Config/InstallerViews/ParentInstallView.swift new file mode 100644 index 00000000000..42c9ad88d6d --- /dev/null +++ b/mac/Config/Config/InstallerViews/ParentInstallView.swift @@ -0,0 +1,67 @@ +// +// ParentInstallView.swift +// Config +// +// Created by Eli Schantz on 6/29/26. +// + +import SwiftUI + +struct ParentInstallView: View { + @EnvironmentObject var installation: InstallationContainer + @Namespace var animation + @State public var currentPage: InstallPage = .initial + + func chooseCurrentPage() -> Void { + if installation.installationPhase.hasTasks { + switch installation.currentTask()?.taskType { + case .prepareNewInstall: currentPage = .initial + case .enableInputMethod: + currentPage = .enableInputMethod + case .requestAccess: currentPage = + .allowSecurityPermission + case .restartMac: currentPage = .restartComputer + default: currentPage = .completed + } + } else { + switch installation.installationPhase { + case .evaluatingInstallation: currentPage = .loading + case .inputMethodMissing, .inputMethodOutdated: currentPage = .rerunInstaller + case .installationComplete: currentPage = .completed + default: currentPage = .completed + } + } + } + + var body: some View { + + ZStack { + switch currentPage { + case .loading: KeymanLogo(namespace: animation) + case .initial: InitialView(namespace: animation,onContinue: chooseCurrentPage) + case .completed: CompletedInstallView(namespace: animation) + case .enableInputMethod: EnableInputMethodView(namespace: animation, onContinue: chooseCurrentPage) + case .allowSecurityPermission: CheckAccessibiltyPermissionView(namespace: animation, onContinue: chooseCurrentPage) + case .rerunInstaller: RerunInstallerView(namespace: animation) + case .restartComputer: RestartComputerView(namespace: animation) + } + } + .onAppear { + switch installation.installationPhase { + case .evaluatingInstallation: + currentPage = .initial + default: + chooseCurrentPage() + } + } + .padding() + .frame(width: 600) + .frame(height: 400) + } +} + +#Preview { + let installation = InstallationContainer() + ParentInstallView() + .environmentObject(installation) +} diff --git a/mac/Config/Config/InstallerViews/RerunInstallerView.swift b/mac/Config/Config/InstallerViews/RerunInstallerView.swift new file mode 100644 index 00000000000..63526d27997 --- /dev/null +++ b/mac/Config/Config/InstallerViews/RerunInstallerView.swift @@ -0,0 +1,81 @@ +// +// RerunInstallerView.swift +// Config +// +// Created by Eli Schantz on 7/21/26. +// + +import SwiftUI +import AppKit +internal import UniformTypeIdentifiers + +struct RerunInstallerView: View { + let namespace: Namespace.ID + @EnvironmentObject var installation: InstallationContainer + + 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) + + .buttonStyle(.borderedProminent) + .tint(.blue) + .clipShape(Capsule()) + + 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) + } + } + } +} + +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) + } +} diff --git a/mac/Config/Config/InstallerViews/RestartComputerView.swift b/mac/Config/Config/InstallerViews/RestartComputerView.swift new file mode 100644 index 00000000000..78e1748a7c5 --- /dev/null +++ b/mac/Config/Config/InstallerViews/RestartComputerView.swift @@ -0,0 +1,61 @@ +// +// RestartComputerView.swift +// Config +// +// Created by Eli Schantz on 7/21/26. +// + +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) + + GradientDivider(namespace: namespace) + + Spacer() + + + Image(systemName: "restart.circle.fill") + .font(.system(size: 88)) + .padding(8) + + Text("A restart is required for the previous installation steps to take effect.") + .multilineTextAlignment(.center) + + Spacer() + + HStack { + + Text("Finish installation") + .font(.title2) + .frame(maxWidth: .infinity, alignment: .leading) + + + + Button { + + } label: { + Text("Restart Now") + .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/Resources/keyman-for-mac-os-license.html b/mac/Config/Resources/keyman-for-mac-os-license.html new file mode 100644 index 00000000000..feb7e7ac7e9 --- /dev/null +++ b/mac/Config/Resources/keyman-for-mac-os-license.html @@ -0,0 +1,123 @@ + + + + + + + + + + + + +
+ +

Keyman­® for macOS +End User Licence and Services Agreement

+ +

The MIT License

+

Copyright (c) 2017 SIL International

+

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

+

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

+ +

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

+ + + +
+ +

 

+ +
+ +
+ + + + 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