/* * KeyboardDetailController.swift * FirstVoices app * * License: MIT * * Copyright © 2022 FirstVoices. * * Created by Shawn Schantz on 2022-01-14. * * Class responsible for the display and editing of settings for a single keyboard. * */ import UIKit import KeymanEngine let keyboardsSection = 0 let languageSettingsSection = 2 let dictionarySection = 1 let keyboardInfoSection = 3 let keyboardsHeader = "Available Keyboards" let languageSettingsHeader = "Dictionary Settings" let dictionaryHeader = "Downloadable Dictionaries" let keyboardInfoHeader = "Keyboard Information" let keyboardsRowCount = 1 let languageSettingsRowCount = 2 let keyboardInfoRowCount = 1 let keyboardsRow = 0 let predictionsRow = 0 let correctionsRow = 1 let versionLabel = "Version" let predictionLabel = "Suggest Predictions" let correctionLabel = "Suggest Corrections" let noDictionaryMessage = "No dictionaries available" let attributeCellIdentifier = "attributeCell" let switchCellIdentifier = "switchCell" let labelCellIdentifier = "labelCell" class KeyboardDetailController: UITableViewController { private var keyboardRepo = KeyboardRepository.shared private var settingsRepo = KeyboardSettingsRepository.shared private var lexicalModelRepo = LexicalModelRepository.shared private var keyboardState: KeyboardState? = nil private var delegate: RefreshKeyboardCheckmark? = nil private var lexicalModels: [FVLexicalModel] = [] override func viewDidLoad() { super.viewDidLoad() self.title = keyboardState?.name self.navigationController?.toolbar.isHidden = false // get lexical models to display in dictionary section if let languageTag = self.keyboardState?.languageTag { lexicalModels = lexicalModelRepo.getAvailableLexicalModels(languageTag: languageTag) } } /* * Used to display the values for the keyboard that was tapped, causing the segue to the detail view. */ func configure(delegate: RefreshKeyboardCheckmark, keyboard: KeyboardState) { self.delegate = delegate self.keyboardState = keyboard } override func numberOfSections(in tableView: UITableView) -> Int { return 4 } override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { var header = "" switch section { case keyboardsSection: header = keyboardsHeader case languageSettingsSection: header = languageSettingsHeader case dictionarySection: header = dictionaryHeader case keyboardInfoSection: header = keyboardInfoHeader default: header = "Undefined" } return header } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { var numberOfRows = 0; switch section { case keyboardsSection: numberOfRows = keyboardsRowCount case languageSettingsSection: numberOfRows = languageSettingsRowCount case dictionarySection: numberOfRows = max(1, self.lexicalModels.count) case keyboardInfoSection: numberOfRows = keyboardInfoRowCount default: numberOfRows = 0 } return numberOfRows } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { var tableCell: UITableViewCell? = nil switch indexPath.section { case keyboardsSection: tableCell = configureKeyboardCell() case languageSettingsSection: if indexPath.row == predictionsRow { tableCell = configurePredictionsSwitchCell() } else if indexPath.row == correctionsRow { tableCell = configureCorrectionsSwitchCell() } case dictionarySection: if self.lexicalModels.isEmpty { tableCell = configureNoDictionaryCell() } else { tableCell = configureDictionaryCell(index: indexPath.row) } case keyboardInfoSection: tableCell = configureVersionCell() default: tableCell = tableView.dequeueReusableCell(withIdentifier: "labelCell") tableCell?.textLabel!.text = "n/a" } return tableCell! } func configureKeyboardCell() -> UITableViewCell { let switchCell = tableView.dequeueReusableCell(withIdentifier: switchCellIdentifier) as! KeyboardDetailCell let keyboardAction: Callback = { (on) in var updatedKeyboardState = false // call Keyman Engine to install or remove the keyboard if on { updatedKeyboardState = self.keyboardRepo.installKeyboard(keyboard: self.keyboardState!.definition) } else { updatedKeyboardState = self.keyboardRepo.removeKeyboard(keyboard: self.keyboardState!.definition) } if (updatedKeyboardState) { // update state and write to local settings storage self.keyboardState!.isActive = on self.settingsRepo.saveKeyboardState(state: self.keyboardState!) // update the on/off state of dependent switches self.updateDictionarySettingsState(animated: true) self.delegate?.refreshCheckmark() } else { // install or remove failed, set switch back to previous state let keyboardsIndex = IndexPath(row: keyboardsRow, section: keyboardsSection) self.updateCellSwitchState(index: keyboardsIndex, on: self.keyboardState!.isActive, animated: true, available: true) } } switchCell.configure(label: (self.keyboardState?.name)!, on: self.keyboardState!.isActive, available: true, callback: keyboardAction) return switchCell } func reportError(message: String) { let alertController = UIAlertController(title: title, message: message, preferredStyle: UIAlertController.Style.alert) alertController.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel, handler: nil)) self.present(alertController, animated: true, completion: nil) print(message) // TODO: send the error message + call stack through to us // some reporting mechanism } func configurePredictionsSwitchCell() -> UITableViewCell { let switchCell = tableView.dequeueReusableCell(withIdentifier: switchCellIdentifier) as! KeyboardDetailCell let predictionAction: Callback = { (on) in if self.lexicalModelRepo.applyPredictionSettings(languageId: self.keyboardState!.languageTag, modelId: self.keyboardState!.lexicalModelId!, on: on) { self.keyboardState!.updatePredictions(on: on) self.settingsRepo.saveKeyboardState(state: self.keyboardState!) self.updateDictionarySettingsState(animated: true) } } switchCell.configure(label: predictionLabel, on: self.keyboardState!.suggestPredictions, available: self.keyboardState!.canSuggestPredictions(), callback: predictionAction) return switchCell } func configureCorrectionsSwitchCell() -> UITableViewCell { let switchCell = tableView.dequeueReusableCell(withIdentifier: switchCellIdentifier) as! KeyboardDetailCell let correctionAction: Callback = { (on) in if (self.lexicalModelRepo.applyCorrectionSettings(languageId: self.keyboardState!.languageTag, modelId: self.keyboardState!.lexicalModelId!, on: on)) { self.keyboardState?.suggestCorrections = on self.settingsRepo.saveKeyboardState(state: self.keyboardState!) } } switchCell.configure(label: correctionLabel, on: self.keyboardState!.suggestCorrections, available: self.keyboardState!.canSuggestCorrections(), callback: correctionAction) return switchCell } func configureNoDictionaryCell() -> UITableViewCell { let labelCell = tableView.dequeueReusableCell(withIdentifier: labelCellIdentifier) labelCell?.textLabel!.text = noDictionaryMessage return labelCell! } func configureDictionaryCell(index: Int) -> UITableViewCell { let switchCell = tableView.dequeueReusableCell(withIdentifier: switchCellIdentifier) as! KeyboardDetailCell if (self.lexicalModels.isEmpty) { print("error, invalid cell: there are no lexical models available for this langauge") return configureNoDictionaryCell() } let thisDictionary = self.lexicalModels[index] let savedModel = (self.keyboardState?.lexicalModelName)! let modelEnabled = thisDictionary.name == savedModel let dictionaryAction: Callback = { (on) in let lexicalModelName = thisDictionary.name let lexicalModelId = thisDictionary.id if on { let message = "As you type in your language, the FirstVoices dictionary will provide suggestions.\n\nWould you like to install this dictionary?" let alert = UIAlertController(title: "\(lexicalModelName)", message: message, preferredStyle: .alert) let installAction = UIAlertAction(title: NSLocalizedString("Install", comment: "Default action"), style: .default, handler: { _ in let toolbar = self.navigationController?.toolbar as? DownloadStatusToolbar toolbar?.displayStatus(true) ResourceDownloadManager.shared.downloadLexicalModelsForLanguageIfExists(languageID: self.keyboardState!.languageTag) { package, error in if error != nil { print("lexical model download failed: \(error!)") } toolbar?.hideStatus() let installed = self.lexicalModelRepo.installLexicalModel(package: package!, keyboardState: self.keyboardState!, modelId: lexicalModelId) if (installed) { self.keyboardState?.selectDictionary(lexicalModel: thisDictionary) self.settingsRepo.saveKeyboardState(state: self.keyboardState!) self.updateDictionarySettingsState(animated: true) } } }) alert.addAction(installAction) let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { (action) in switchCell.detailSwitch.isOn = false } alert.addAction(cancelAction) self.present(alert, animated: true, completion: nil) } else { if self.lexicalModelRepo.disableLexicalModel(keyboardState: self.keyboardState!, modelId: lexicalModelId) { self.keyboardState?.clearDictionary() self.settingsRepo.saveKeyboardState(state: self.keyboardState!) self.updateDictionarySettingsState(animated: true) } } } switchCell.configure(label: thisDictionary.name, on: modelEnabled, available: self.keyboardState!.canSelectDictionary(), callback: dictionaryAction) return switchCell } func configureVersionCell() -> UITableViewCell { let versionCell = tableView.dequeueReusableCell(withIdentifier: attributeCellIdentifier) versionCell!.textLabel!.text = versionLabel let versionText = self.keyboardState?.version versionCell!.detailTextLabel!.text = versionText return versionCell! } func updateCellSwitchAvailability(index: IndexPath, available: Bool) { if let cell:KeyboardDetailCell = self.tableView.cellForRow(at: index) as? KeyboardDetailCell { cell.updateSwitchAvailability(available: available) } } func updateCellSwitchState(index: IndexPath, on: Bool, animated: Bool, available: Bool) { if let cell:KeyboardDetailCell = self.tableView.cellForRow(at: index) as? KeyboardDetailCell { cell.updateSwitchState(on: on, animated: animated, available: available) } } func updateDictionarySettingsState(animated: Bool) { let keyboardsIndex = IndexPath(row: keyboardsRow, section: keyboardsSection) self.updateCellSwitchState(index: keyboardsIndex, on: self.keyboardState!.isActive, animated: animated, available: true) for rowNumber in 0...max(0, lexicalModels.count-1) { let dictionaryIndex = IndexPath(row: rowNumber, section: dictionarySection) self.updateCellSwitchState(index: dictionaryIndex, on: self.keyboardState!.isDictionarySelected(), animated: animated, available: self.keyboardState!.canSelectDictionary()) } let predictionIndex = IndexPath(row: predictionsRow, section: languageSettingsSection) self.updateCellSwitchState(index: predictionIndex, on: self.keyboardState!.suggestPredictions, animated: animated, available: self.keyboardState!.canSuggestPredictions()) let correctionIndex = IndexPath(row: correctionsRow, section: languageSettingsSection) self.updateCellSwitchState(index: correctionIndex, on: self.keyboardState!.suggestCorrections, animated: animated, available: self.keyboardState!.canSuggestCorrections()) } }