/* * Keyman is copyright (C) SIL Global. MIT License. * * Created by Shawn Schantz on 2026-06-01 * * Class for tracking overall state of the installation. * Includes functions for mapping to a Dictionary to store in the UserDefaults. */ import Foundation import KeymanSettings public class InstallationState { // keys to the fields we are storing in the UserDefaults KMInstallationState dictionary let kVersionKey = "version" let kDateRestartRequestedKey = "dateRestartRequested" let kRepairKey = "isRepair" let kDisplayedInstallComplete = "displayedInstallComplete" public let keymanVersion: String public var dateRestartRequested: Date? // indicates whether we are repairing a previous installation or doing a full installation public let isRepair: Bool // indicates whether the installation complete message has been displayed to the user public var hasDisplayedInstallComplete: Bool // this list of tasks that make up this installation public var tasks: Set /** * The installation is complete if all its tasks are complete. * Returns true if the tasks list is empty, but it should never be empty. */ public var isComplete: Bool { return tasks.allSatisfy(\.isComplete) } /** * The installation is new if this is not a repair and no tasks are complete. * Returns true if the tasks list is empty, but it should never be empty. */ public var isNew: Bool { return !isRepair && tasks.allSatisfy{ !$0.isComplete } } /** * The installation has at least one task complete and at least one remaining to complete. * It does not matter if this is a repair or not. */ public var isInProgress: Bool { return tasks.contains(where: { !$0.isComplete }) && tasks.contains(where: { $0.isComplete}) } init(version: String, dateRestartRequested: Date? = nil, isRepair: Bool = false, hasDisplayedInstallComplete: Bool = false, tasks: Set) { self.keymanVersion = version self.dateRestartRequested = dateRestartRequested self.isRepair = isRepair self.hasDisplayedInstallComplete = hasDisplayedInstallComplete self.tasks = tasks } /** * create a new InstallationState with an existing InstallationState and a task to mark as completed */ public static func createCopyWithCompletedTask (from state: InstallationState, with completedTask: InstallationTaskType) -> InstallationState { var newTaskList = state.tasks let completedTask = InstallationTask(task: completedTask, completed: true) newTaskList.update(with: completedTask) return InstallationState(version: state.keymanVersion, dateRestartRequested: state.dateRestartRequested, isRepair: state.isRepair, hasDisplayedInstallComplete: state.hasDisplayedInstallComplete, tasks: newTaskList) } /** * create a new InstallationState with an existing InstallationState and a task to mark as completed */ public static func createCopy (from state: InstallationState) -> InstallationState { let newTaskList = state.tasks return InstallationState(version: state.keymanVersion, dateRestartRequested: state.dateRestartRequested, isRepair: state.isRepair, hasDisplayedInstallComplete: state.hasDisplayedInstallComplete, tasks: newTaskList) } /** * initialize using the dictionary from UserDefaults */ init?(from dictionary: Dictionary) { self.keymanVersion = dictionary[kVersionKey] as? String ?? "" self.dateRestartRequested = dictionary[kDateRestartRequestedKey] as? Date self.isRepair = dictionary[kRepairKey] as? Bool ?? false self.hasDisplayedInstallComplete = dictionary[kDisplayedInstallComplete] as? Bool ?? false var installationTasks = Set() // for every task flag found in dictionary, insert a task in the tasks array if let taskFlag = dictionary[InstallationTaskType.prepareNewInstall.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .prepareNewInstall, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.prepareNewRepair.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .prepareNewRepair, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.enableInputMethod.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .enableInputMethod, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.requestAccess.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .requestAccess, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.confirmAccess.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .confirmAccess, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.requestRestart.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .requestRestart, completed: taskFlag)) } if let taskFlag = dictionary[InstallationTaskType.confirmRestart.rawValue] as? Bool { installationTasks.insert(InstallationTask(task: .confirmRestart, completed: taskFlag)) } self.tasks = installationTasks } /** * create a dictionary for storing the current InstallationState state in the UserDefaults */ public func toUserDefaultsDictionary() -> Dictionary { var dictionary: Dictionary = [:] dictionary[kVersionKey] = keymanVersion if let dateRestartRequested = self.dateRestartRequested { dictionary[kDateRestartRequestedKey] = dateRestartRequested } dictionary[kRepairKey] = self.isRepair dictionary[kDisplayedInstallComplete] = self.hasDisplayedInstallComplete for task in self.tasks { dictionary[task.taskType.rawValue] = task.isComplete } return dictionary } }