diff --git a/mac/Config/Config/ConfigView.swift b/mac/Config/Config/ConfigView.swift index d8c40528194..2763e1eb2ab 100644 --- a/mac/Config/Config/ConfigView.swift +++ b/mac/Config/Config/ConfigView.swift @@ -19,7 +19,8 @@ struct ConfigView: View { Image(systemName: "keyboard") .imageScale(.large) .foregroundColor(.accentColor) - Text("keyboard count = \(settings.installedPackages.count)") + Text("multiple keyboard package count = \(settings.multiKeyboardPackages.count)") + Text("single keyboard package count = \(settings.singleKeyboardPackages.count)") Button("debug") { settings.debug() } @@ -46,17 +47,39 @@ struct ConfigView: View { ScrollView { VStack(alignment: .leading, spacing: 6) { - ForEach(Array(settings.installedPackages.enumerated()), id: \.offset) { index, package in + ForEach(Array(settings.singleKeyboardPackages.enumerated()), id: \.offset) { index, package in VStack { HStack(alignment: .center, spacing: 10) { + VStack(spacing: 16) { + Text("Scan to visit website:") + .font(.headline) + + if let qrImage = package.generateSharePackageQRCode(size: 200) { + Image(nsImage: qrImage) + .interpolation(.none) // important: keeps the QR edges sharp + .resizable() + .frame(width: 200, height: 200) + .background(Color.white) // ensures good scanning contrast + } else { + Text("Failed to generate QR Code") + .foregroundColor(.red) + } + } + .padding() Text(package.packageName) .font(.headline) Text(package.packageVersion) .font(.subheadline) // Example of Icon-Only Button Spacer() + if let nsImage = package.graphicImage { + Image(nsImage: nsImage) + .resizable() // Allows resizing + .scaledToFit() // Maintains original aspect ratio + .frame(maxWidth: 140, maxHeight: 250) // Controls the bounds + } Button(action: { - settings.removePackage(at: index) + settings.removeInstalledPackage(with: package.id) }) { Label("remove", systemImage: "trash.fill") } diff --git a/mac/Config/Installation/InstallationCheck.swift b/mac/Config/Installation/InstallationCheck.swift index 9f07b58df86..04d118b3128 100644 --- a/mac/Config/Installation/InstallationCheck.swift +++ b/mac/Config/Installation/InstallationCheck.swift @@ -37,7 +37,7 @@ public class InstallationCheck { self.inputMethodVersion = "unknown" } - self.configurationVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" + self.configurationVersion = ConfigAppUtil.configAppVersion() self.isInputMethodCurrent = InstallationCheck.isVersionCurrent(inputMethodVersion: self.inputMethodVersion, configurationVersion: self.configurationVersion) self.installationState = self.loadState() diff --git a/mac/KeymanSettings/Package.swift b/mac/KeymanSettings/Package.swift index 94a0570f843..ff9c162e558 100644 --- a/mac/KeymanSettings/Package.swift +++ b/mac/KeymanSettings/Package.swift @@ -25,16 +25,19 @@ let package = Package( .target( name: "KeymanSettings", dependencies: [ - .product(name: "ZIPFoundation", package: "ZIPFoundation") + .product(name: "ZIPFoundation", package: "ZIPFoundation") ], - path: "Sources" + path: "Sources", + resources: [ + .process("Resources") + ] ), .testTarget( name: "KeymanSettingsTests", dependencies: ["KeymanSettings"], path: "Tests", resources: [ - .copy("Resources/amharic.kmp.json") // The test data files, copy files without modifying them + .copy("Resources/amharic.kmp.json") // The test data files, copy files without modifying them ] ), ] diff --git a/mac/KeymanSettings/Sources/KeymanSettings/ConfigAppUtil.swift b/mac/KeymanSettings/Sources/KeymanSettings/ConfigAppUtil.swift new file mode 100644 index 00000000000..194c71d91a1 --- /dev/null +++ b/mac/KeymanSettings/Sources/KeymanSettings/ConfigAppUtil.swift @@ -0,0 +1,20 @@ +/* + * Keyman is copyright (C) SIL Global. MIT License. + * + * Created by Shawn Schantz on 2026-07-13 + * + * Used for getting application metadata from the Config application + */ + +import Foundation + +public struct ConfigAppUtil { + /** + * returns the short version string from the bundle of the Config app + */ + static public func configAppVersion() -> String { + return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown" + } + + +} diff --git a/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift b/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift index af5b4c47973..be75cb30654 100644 --- a/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift +++ b/mac/KeymanSettings/Sources/KeymanSettings/SettingsContainer.swift @@ -40,10 +40,23 @@ public enum SettingsError: Error { @MainActor // run on the main actor since data is published directly to the UI public class SettingsContainer : ObservableObject { - // packages are loaded from disk, each package may contain one or more keyboard - @Published public var installedPackages: [KeymanPackage] + // installed packages are loaded from disk, each package may contain one or more keyboard + fileprivate var installedPackages: [KeymanPackage] { + didSet { + self.updatePackageArrays() + } + } + + // Maintain two arrays for use by configuration views. + // One is for packages with single keyboards and one for packages with multiple keyboards. + // Each array is sorted alphabetically by package name. + // These arrays are updated by a property observer on installedPackages. + // (Consider installedPackages as the source of truth and these arrays for presentation purposes.) + @Published public private(set) var singleKeyboardPackages: [KeymanPackage] + @Published public private(set) var multiKeyboardPackages: [KeymanPackage] + // when a new package is downloaded, it is tracked here - public var packageDownload: PackageDownload? = nil + public private(set) var packageDownload: PackageDownload? = nil fileprivate let packageRepository: PackageRepo fileprivate let defaultsRepository: DefaultsRepo @@ -53,6 +66,11 @@ public class SettingsContainer : ObservableObject { fileprivate var selectedKeyboard: String public init() { + // initialize arrays before loading packages + self.singleKeyboardPackages = [] + self.multiKeyboardPackages = [] + self.installedPackages = [] + // create the package repository, gaining access to the app group container directory do { try self.packageRepository = PackageRepository() @@ -72,11 +90,12 @@ public class SettingsContainer : ObservableObject { } catch { fatalError("Unable to access defaults in group container: \(error.localizedDescription).") } - + self.selectedKeyboard = self.defaultsRepository.readSelectedKeyboard() - - // first load all the installed packages from disk - self.installedPackages = [] + + // load all the installed packages from disk + // though we are in the initializer, didset will be called to update + // the two dependent package arrays because of the call to this helper method self.loadPackages() // next, apply the settings to the packages @@ -95,6 +114,8 @@ public class SettingsContainer : ObservableObject { self.packageRepository = packageRepo self.selectedKeyboard = self.defaultsRepository.readSelectedKeyboard() + self.singleKeyboardPackages = [] + self.multiKeyboardPackages = [] self.installedPackages = [] } @@ -114,7 +135,7 @@ public class SettingsContainer : ObservableObject { name: .packageReplaced, object: nil ) } - + /** * called for `newPackageInstalled` notification */ @@ -133,6 +154,27 @@ public class SettingsContainer : ObservableObject { self.packageDownload = nil } + /** + * Whenever the installedPackages array changes, recreate the two subarrays + */ + public func updatePackageArrays() { + self.singleKeyboardPackages = [] + self.multiKeyboardPackages = [] + + // divide the installedPackages array into two separate arrays based on whether they have one or more keyboards + let partitionedPackages = self.installedPackages.reduce(into: (single: [KeymanPackage](), multiple: [KeymanPackage]())) { result, element in + if element.keyboards.count == 1 { + result.single.append(element) + } else if element.keyboards.count > 1 { + result.multiple.append(element) + } + } + + // sort subarrays alphabetically, without regard to case, using the 'packageName' property + self.singleKeyboardPackages = partitionedPackages.single.sorted { $0.packageName.caseInsensitiveCompare($1.packageName) == .orderedAscending } + self.multiKeyboardPackages = partitionedPackages.multiple.sorted { $0.packageName.caseInsensitiveCompare($1.packageName) == .orderedAscending } + } + /** * Called when user approves the downgrade of package */ @@ -257,9 +299,9 @@ public class SettingsContainer : ObservableObject { /** * find the installed package with the specified UUID */ - public func findPackage(with packageId: UUID) -> KeymanPackage? { - guard let package = self.installedPackages.first(where: { $0.id == packageId }) else { - print ("Error: could not find package with ID: \(packageId)") + public func findInstalledPackage(with id: UUID) -> KeymanPackage? { + guard let package = self.installedPackages.first(where: { $0.id == id }) else { + print ("Error: could not find package with UUID: \(id)") return nil } @@ -269,7 +311,7 @@ public class SettingsContainer : ObservableObject { /** * find the installed package with the specified package name */ - public func findPackage(with packageName: String) -> KeymanPackage? { + public func findInstalledPackage(with packageName: String) -> KeymanPackage? { guard let package = self.installedPackages.first(where: { $0.packageName == packageName }) else { print ("Error: could not find package with name: \(packageName)") return nil @@ -279,11 +321,21 @@ public class SettingsContainer : ObservableObject { } /** - * remove/uninstall the package at the specified index + * remove/uninstall the package with the specified UUID */ - public func removePackage(at index: Int) { - let package = self.installedPackages[index] + public func removeInstalledPackage(with id: UUID) { + if let package = findInstalledPackage(with: id) { + self.removeInstalledPackage(package: package) + } else { + print("could not find package with id: \(id)") + } + } + + /** + * remove the installed package + */ + func removeInstalledPackage(package: KeymanPackage) { // will removing this package cause the removal of any enabled keyboards? let removingEnabledKeyboards = !package.getEnabledKeyboardsKeys().isEmpty @@ -291,20 +343,22 @@ public class SettingsContainer : ObservableObject { self.packageRepository.deletePackage(package: package) // remove package from installed packages list - _ = self.installedPackages.remove(at: index) + if let index = self.installedPackages.firstIndex(where: { $0.packageName == package.packageName }) { + self.installedPackages.remove(at: index) + } // if we removed any enabled keyboards, then update settings if removingEnabledKeyboards { self.saveKeyboardState() } } - + /** * returns true if the keyboard is enabled * when enabled, the keyboard appears in the Keyman sub menu in the mac */ public func isKeyboardEnabled(packageId: UUID, keyboardKey: String) -> Bool { - guard let package = self.findPackage(with: packageId) else { + guard let package = self.findInstalledPackage(with: packageId) else { print ("Could not read keyboard state for package: \(packageId) and keyboard: \(keyboardKey)") return false } @@ -317,7 +371,7 @@ public class SettingsContainer : ObservableObject { * enable or disable the keyboard */ public func setKeyboardEnabled(packageId: UUID, keyboardKey: String, enabled: Bool) { - guard let package = self.findPackage(with: packageId) else { + guard let package = self.findInstalledPackage(with: packageId) else { print ("Could not read keyboard state for package: \(packageId) and keyboard: \(keyboardKey)") return } diff --git a/mac/KeymanSettings/Sources/Model/Keyboard.swift b/mac/KeymanSettings/Sources/Model/Keyboard.swift index c26f32011f8..d67e2e14d6b 100644 --- a/mac/KeymanSettings/Sources/Model/Keyboard.swift +++ b/mac/KeymanSettings/Sources/Model/Keyboard.swift @@ -16,6 +16,8 @@ public class Keyboard: Identifiable, Hashable, Equatable { public var id = UUID() public var enabled: Bool public var name: String + public let oskFont: String? + public let displayFont: String? public var keyboardId: String // the directory we are reading the keyboard from public var keyboardDirectoryUrl: URL @@ -30,19 +32,21 @@ public class Keyboard: Identifiable, Hashable, Equatable { public init(keyboardSource: KeyboardSource, directoryUrl: URL) { self.enabled = true self.name = keyboardSource.name + self.oskFont = keyboardSource.oskFont + self.displayFont = keyboardSource.displayFont self.keyboardId = keyboardSource.id self.keyboardDirectoryUrl = directoryUrl self.kmxFileUrl = Keyboard.deriveKmxFileUrl(from: self.keyboardDirectoryUrl, keyboardId: self.keyboardId) self.keyboardKey = Keyboard.deriveKeyboardSettingsKey(from: self.keyboardDirectoryUrl, keyboardId: self.keyboardId) - -// print("keyboard created for: \(keyboardSource.id) \r with kmxFileUrl: \(self.kmxFileUrl) \r and settingsKey: \(self.keyboardKey)") } /** * initializer that does not rely on package source -- provided to create unit test data */ - public init(name: String, keyboardId: String, keyboardDirectoryUrl: URL, enabled: Bool) { + public init(name: String, oskFont: String? = nil, displayFont: String? = nil, keyboardId: String, keyboardDirectoryUrl: URL, enabled: Bool) { self.name = name + self.oskFont = oskFont + self.displayFont = displayFont self.keyboardId = keyboardId self.keyboardDirectoryUrl = keyboardDirectoryUrl self.kmxFileUrl = Keyboard.deriveKmxFileUrl(from: self.keyboardDirectoryUrl, keyboardId: self.keyboardId) diff --git a/mac/KeymanSettings/Sources/Model/KeymanPackage.swift b/mac/KeymanSettings/Sources/Model/KeymanPackage.swift index ec370856c1b..569202d32e7 100644 --- a/mac/KeymanSettings/Sources/Model/KeymanPackage.swift +++ b/mac/KeymanSettings/Sources/Model/KeymanPackage.swift @@ -9,12 +9,17 @@ import Foundation import AppKit +import Cocoa +import CoreImage +import CoreImage.CIFilterBuiltins public class KeymanPackage: Identifiable, Hashable, Equatable { static let defaultImage: NSImage? = { var image: NSImage? = nil - if let imageUrl = Bundle.main.url(forResource: "SideImage", withExtension: "bmp") { - image = NSImage(contentsOf: imageUrl) + if let imageUrl = Bundle.module.url(forResource: "SideImage", withExtension: "bmp") { + image = NSImage(contentsOf: imageUrl) + } else { + print("Error: Could not find SideImage.bmp in the module bundle.") } return image }() @@ -24,14 +29,23 @@ public class KeymanPackage: Identifiable, Hashable, Equatable { // the URL of the directory in which the package is contained public let sourceDirectoryUrl: URL // the URL of the kmp.json file for the package - + public let jsonFileUrl: URL + // the URL for downloading the package from keyman.com + public let sharePackageUrl: URL? + public let keyboards: [Keyboard] + public let fonts: [String] public let packageName: String public let packageVersion: String + public let author: String? + public let websiteUrl: URL? public let copyright: String? - public let jsonFileUrl: URL + // the URL of the readme file within the package public let readmeFileUrl: URL? + // the URL of the help file within the package, named 'welcomeFile' in kmp.json + public let helpFileUrl: URL? + // the URL of the graphic file within the package public let graphicFileUrl: URL? public let graphicImage: NSImage? @@ -42,6 +56,12 @@ public class KeymanPackage: Identifiable, Hashable, Equatable { self.id = UUID() self.packageName = packageSource.info.name.description self.packageVersion = packageSource.info.version.description + self.author = packageSource.info.author?.description + if let websiteUrlString = packageSource.info.website?.url { + self.websiteUrl = URL(string: websiteUrlString) + } else { + self.websiteUrl = nil + } self.copyright = packageSource.info.copyright?.description self.sourceDirectoryUrl = packageSource.directoryUrl! self.jsonFileUrl = packageSource.kmpJsonFileUrl! @@ -52,17 +72,29 @@ public class KeymanPackage: Identifiable, Hashable, Equatable { } else { self.readmeFileUrl = nil } - + + if let helpFilename = packageSource.helpFilename { + let fileUrl = sourceDirectoryUrl.appendingPathComponent(helpFilename) + self.helpFileUrl = fileUrl + } else { + self.helpFileUrl = nil + } + self.graphicFileUrl = KeymanPackage.buildGraphicFileUrl(source: packageSource) self.graphicImage = KeymanPackage.loadImage(imageUrl: self.graphicFileUrl) - self.keyboards = KeymanPackage.buildKeyboardsArray(packageSource: packageSource) + self.sharePackageUrl = KeymanPackage.buildSharePackageUrl(packageUrl: self.sourceDirectoryUrl) + + let keyboardsArray = KeymanPackage.buildKeyboardsArray(packageSource: packageSource) + self.keyboards = keyboardsArray + + self.fonts = KeymanPackage.buildFontNamesArray(keyboards: keyboardsArray) } /** * build an array of Keyboard objects using the array of KeyboardSource object created from the kmp.json */ - private static func buildKeyboardsArray (packageSource: PackageSource) -> [Keyboard] { + private static func buildKeyboardsArray(packageSource: PackageSource) -> [Keyboard] { var keyboardsArray = [Keyboard]() if let keyboards = packageSource.keyboards, let directoryUrl = packageSource.directoryUrl { @@ -74,21 +106,42 @@ public class KeymanPackage: Identifiable, Hashable, Equatable { return keyboardsArray } - + + /** + * build an array of all the fonts used by the specified array of keyboards + */ + private static func buildFontNamesArray(keyboards: [Keyboard]) -> [String] { + var fontNames: Set = [] + for keyboard in keyboards { + if let oskFontName = keyboard.oskFont { + fontNames.insert(oskFontName) + } + if let displayFontName = keyboard.displayFont { + fontNames.insert(displayFontName) + } + } + return fontNames.sorted() + } + /** * initializer that does not rely on package source -- provided to create unit test data */ - public init(sourceDirectoryUrl: URL, keyboards: [Keyboard], packageName: String, packageVersion: String, copyright: String? = nil, jsonFileUrl: URL, readmeFileUrl: URL? = nil, graphicFileUrl: URL? = nil, graphicImage: NSImage? = nil) { + public init(sourceDirectoryUrl: URL, sharePackageUrl: URL? = nil, keyboards: [Keyboard], packageName: String, packageVersion: String, author: String? = nil, website: URL? = nil, copyright: String? = nil, jsonFileUrl: URL, readmeFileUrl: URL? = nil, helpFileUrl: URL? = nil, graphicFileUrl: URL? = nil, graphicImage: NSImage? = nil) { self.id = UUID() self.sourceDirectoryUrl = sourceDirectoryUrl + self.sharePackageUrl = sharePackageUrl self.keyboards = keyboards self.packageName = packageName self.packageVersion = packageVersion + self.author = author + self.websiteUrl = website self.copyright = copyright self.jsonFileUrl = jsonFileUrl self.readmeFileUrl = readmeFileUrl + self.helpFileUrl = helpFileUrl self.graphicFileUrl = graphicFileUrl self.graphicImage = graphicImage + self.fonts = [] } /** @@ -167,6 +220,44 @@ public class KeymanPackage: Identifiable, Hashable, Equatable { return packageImage } + /** + * build the URL where the keyboard can be installed from the Keyman website + */ + static func buildSharePackageUrl(packageUrl: URL) -> URL? { + return URL(string: "https://\(KeymanPaths.keymanDomain)/go/keyboard/\(packageUrl.lastPathComponent)/share") + } + + // MAC-CONFIG-TODO: cache QR code image, but must be size specific + + /** + * generate a QR code for sharing the Keyman Package URL + */ + public func generateSharePackageQRCode(size: CGFloat = 300) -> NSImage? { + guard let data = self.sharePackageUrl?.absoluteString.data(using: .utf8) else { return nil } + + // initialize the built-in Apple QR filter + let filter = CIFilter.qrCodeGenerator() + filter.message = data + filter.correctionLevel = "M" + + guard let rawQRImage = filter.outputImage else { return nil } + + // calculate scaling factor to ensure crisp rendering of QR code + let rawWidth = rawQRImage.extent.width + guard rawWidth > 0 else { return nil } + let scale = size/rawWidth + + // apply transform to scale up without blurring (use interpolation = none in SwiftUI image) + let scaledQRImage = rawQRImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) + + // convert the CIImage to a macOS NSImage + let rep = NSCIImageRep(ciImage: scaledQRImage) + let nsImage = NSImage(size: NSSize(width: size, height: size)) + nsImage.addRepresentation(rep) + + return nsImage + } + /** * provided for Hashable conformance */ diff --git a/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift b/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift index 474d17aabd7..6e982f9c0b0 100644 --- a/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift +++ b/mac/KeymanSettings/Sources/Persistence/Data/PackageSource.swift @@ -41,6 +41,13 @@ public struct PackageSource: Identifiable, Decodable, Hashable, Equatable { return nil } } + var helpFilename: String? { + if let filename = options?.welcomeFile { + return filename + } else { + return nil + } + } var graphicFilename: String? { if let filename = options?.graphicFile { return filename @@ -144,10 +151,14 @@ struct SystemInfo: Decodable { struct Options: Decodable { let readmeFile: String? let graphicFile: String? - + let licenseFile: String? + let welcomeFile: String? + enum CodingKeys: String, CodingKey { case readmeFile case graphicFile + case licenseFile + case welcomeFile } } diff --git a/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift b/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift index b5ef856f242..5244858eaa4 100644 --- a/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift +++ b/mac/KeymanSettings/Sources/Persistence/KeymanPaths.swift @@ -40,6 +40,10 @@ public struct KeymanPaths { static public let configBundleId = "com.keyman.config" static public let groupId = "group.com.keyman" + static public let keymanDomain = "keyman.com" + static public let keymanHelpDomain = "help.keyman.com" + static public let keymanApiDomain = "api.keyman.com" + // keyman file extensions static public let keymanPackageFileExtension: String = "kmp" @@ -132,6 +136,9 @@ public struct KeymanPaths { } } + /** + * build the URL to the executable inside the specified Input Method app + */ public func buildInputMethodExecutableUrl(fileName:String) -> URL? { if let inputMethodUrl = self.buildInputMethodPathUrl(fileName: fileName) { let executableName = inputMethodUrl.deletingPathExtension().lastPathComponent diff --git a/mac/KeymanSettings/Sources/Resources/SideImage.bmp b/mac/KeymanSettings/Sources/Resources/SideImage.bmp new file mode 100644 index 00000000000..ac4aa2dded3 Binary files /dev/null and b/mac/KeymanSettings/Sources/Resources/SideImage.bmp differ diff --git a/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift b/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift index 4efd174fc95..68a87d5bada 100644 --- a/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift +++ b/mac/KeymanSettings/Tests/KeymanSettingsTests/KeymanSettingsTests.swift @@ -22,11 +22,12 @@ import Foundation let packageRepo = PackageRepoStub() let settingsContainer = SettingsContainer(defaultsRepo: defaultsRepo, packageRepo: packageRepo) - #expect(settingsContainer.installedPackages.isEmpty) - + #expect(settingsContainer.multiKeyboardPackages.isEmpty) + #expect(settingsContainer.singleKeyboardPackages.isEmpty) + settingsContainer.loadPackages() - #expect(settingsContainer.findPackage(with: packageRepo.testPackageId) != nil) + #expect(settingsContainer.findInstalledPackage(with: packageRepo.testPackageId) != nil) #expect(defaultsRepo.readEnabledKeyboards().contains(moabiteKeyboardKey)) } @@ -37,8 +38,8 @@ import Foundation settingsContainer.loadPackages() // verify that we can find that test package and cannot find the null package - #expect(settingsContainer.findPackage(with: packageRepo.testPackageId) != nil) - #expect(settingsContainer.findPackage(with: packageRepo.nullPackageId) == nil) + #expect(settingsContainer.findInstalledPackage(with: packageRepo.testPackageId) != nil) + #expect(settingsContainer.findInstalledPackage(with: packageRepo.nullPackageId) == nil) } @Test("Check remove package") @MainActor func testRemovePackage() async throws { @@ -48,9 +49,9 @@ import Foundation settingsContainer.loadPackages() // verify that we cannot find the test package after removing it - #expect(settingsContainer.findPackage(with: packageRepo.testPackageId) != nil) - settingsContainer.removePackage(at: 0) - #expect(settingsContainer.findPackage(with: packageRepo.testPackageId) == nil) + #expect(settingsContainer.findInstalledPackage(with: packageRepo.testPackageId) != nil) + settingsContainer.removeMultipleKeyboardPackage(at: 0) + #expect(settingsContainer.findInstalledPackage(with: packageRepo.testPackageId) == nil) } /** @@ -78,7 +79,7 @@ import Foundation let settingsContainer = SettingsContainer(defaultsRepo: defaultsRepo, packageRepo: packageRepo) settingsContainer.loadPackages() - let package = try #require(settingsContainer.findPackage(with: packageRepo.testPackageId)) + let package = try #require(settingsContainer.findInstalledPackage(with: packageRepo.testPackageId)) // the hittite keyboard is initialized as disabled #expect(!package.isKeyboardEnabled(keyboardKey: hittiteKeyboardKey)) @@ -171,13 +172,13 @@ import Foundation @Test("Check Keyman 19 container data directory") func testKeyman19ContainerDataDirectory() async throws { #expect(true) - let containerDirectory = try #require(KeymanPaths().keyman19ContainerDirectory) + let containerDirectory = try KeymanPaths().keyman19ContainerDirectory #expect(containerDirectory.absoluteString.hasSuffix("Group%20Containers/group.com.keyman/")) } @Test("Check Keyman 19 packages directory") func testKeyman19KeyboardsDirectory() async throws { #expect(true) - let keyboardsDirectory = try #require(KeymanPaths().keyman19PackagesDirectory) + let keyboardsDirectory = try KeymanPaths().keyman19PackagesDirectory #expect(keyboardsDirectory.absoluteString.hasSuffix("Group%20Containers/group.com.keyman/Library/Application%20Support/Keyman-Packages/")) } }