spiegel-keyman/oem/firstvoices/ios/FirstVoices/LexicalModelRepository.swift
2022-03-31 08:01:50 +07:00

209 lines
8.4 KiB
Swift

/*
* LexicalModelRepository.swift
* FirstVoices app
*
* License: MIT
*
* Copyright © 2022 FirstVoices.
*
* Created by Shawn Schantz on 2022-01-27.
*
* Class to call the Keyman API to query about lexical models for a given language.
*
*/
import UIKit
import KeymanEngine
class LexicalModelRepository {
private static let keymanLexicalModelApiUrl = "https://api.keyman.com/model?q=bcp47:"
// these strings match those in KeymanEngine so that we can share data across UserDefaults (hack!)
private static let userPredictionSettings = "UserPredictionEnablementSettings"
private static let userCorrectionSettings = "UserCorrectionEnablementSettings"
static let shared: LexicalModelRepository = {
let instance = LexicalModelRepository()
return instance
}()
private init() {}
func getAvailableLexicalModels(languageTag: String) -> [FVLexicalModel] {
var modelArray: [FVLexicalModel] = []
let keymanApiUrl: URL = URL.init(string: "\(LexicalModelRepository.keymanLexicalModelApiUrl)\(languageTag)")!
do {
let lexicalModelData = try Data(contentsOf: keymanApiUrl, options: NSData.ReadingOptions())
do {
// make sure this JSON is in the format we expect
if let jsonArray = try JSONSerialization.jsonObject(with: lexicalModelData, options: []) as? [[String : Any]] {
// try to read array
if !jsonArray.isEmpty {
let jsonModelMap = jsonArray.first
let name = jsonModelMap!["name"] as! String
let modelId = jsonModelMap!["id"] as! String
let packageUrl = jsonModelMap!["packageFilename"] as! String
let version = jsonModelMap!["version"] as! String
let model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
modelArray.append(model)
}
}
} catch let error as NSError {
print("Failed to load: \(error.localizedDescription)")
}
print(lexicalModelData)
} catch {
print(error)
}
// TODO: delete commented-out test code when a keyboard with multiple dictionaries is available
/*
// test code for multi dictionary support since no real data exists for this
// add two fake dictionaries for single language
if (languageTag == "ikt-latn") {
var name = "northern dialect"
var modelId = "nrc.str.sencoten"
var packageUrl = "https://keyman.com/go/package/download/model/nrc.str.sencoten?version=1.0.5&update=1"
var version = "1.01"
var model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
modelArray.append(model)
name = "southern dialect"
modelId = "nrc.str.sencoten"
packageUrl = "https://keyman.com/go/package/download/model/nrc.str.sencoten?version=1.0.5&update=1"
version = "1.03"
model = FVLexicalModel(name: name, id: modelId, packageUrl: packageUrl, languageTag: languageTag, version: version)
modelArray.append(model)
}
*/
return modelArray
}
/*
* install = 1) download lexical model and 2) turn on prediction and corrections by default
*/
func installLexicalModel(package: LexicalModelKeymanPackage, keyboardState: KeyboardState, modelId: String) -> Bool {
let added = Manager.shared.addLexicalModel(lexicalModelId: modelId, languageId: keyboardState.languageTag, from: package)
if (!added) {
return false
}
// set as preferred lexical model (same as done in Keyman switchLexicalModel())
let defaults: UserDefaults = FVShared.userDefaults()
defaults.set(preferredLexicalModelID: modelId, forKey: keyboardState.languageTag)
// call Keyman once after enabling both prediction and correction flags in UserDefaults
self.writePredictionSettings(languageId: keyboardState.languageTag, modelId: modelId, on: true)
self.writeCorrectionSettings(languageId: keyboardState.languageTag, modelId: modelId, on: true)
let applied = self.applyLexicalModelSettings(languageId: keyboardState.languageTag, modelId: modelId)
return applied
}
func disableLexicalModel(keyboardState: KeyboardState, modelId: String) -> Bool {
let removed = Manager.shared.removeLexicalModel(lexicalModelId: modelId, languageId: keyboardState.languageTag)
return removed
}
func downloadModel(keyboardState: KeyboardState, modelId: String) {
Manager.shared.downloadLexicalModel(withID: modelId, languageID: keyboardState.languageTag,
isUpdate: true, fetchRepositoryIfNeeded: true)
}
// TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
/*
* write prediction settings to UserDefaults but do not apply
*/
func writePredictionSettings(languageId: String, modelId: String, on: Bool) {
self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userPredictionSettings,
languageId: languageId, modelId: modelId, on: on)
}
// TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
/*
* write correction settings to UserDefaults but do not apply
*/
func writeCorrectionSettings(languageId: String, modelId: String, on: Bool) {
self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
languageId: languageId, modelId: modelId, on: on)
}
// TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
/*
* write prediction settings to UserDefaults and apply them
*/
func applyPredictionSettings(languageId: String, modelId: String, on: Bool) -> Bool {
self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userPredictionSettings,
languageId: languageId, modelId: modelId, on: on)
// automatically turn off corrections when predictions are turned off
if (!on) {
self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
languageId: languageId, modelId: modelId, on: false)
}
return self.applyLexicalModelSettings(languageId: languageId, modelId: modelId)
}
// TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
/*
* write correction settings to UserDefaults and apply them
*/
func applyCorrectionSettings(languageId: String, modelId: String, on: Bool) -> Bool {
self.writeLexicalModelSettings(userDefaultsKey: LexicalModelRepository.userCorrectionSettings,
languageId: languageId, modelId: modelId, on: on)
return self.applyLexicalModelSettings(languageId: languageId, modelId: modelId)
}
// TODO: delete functions for passing data via UserDefaults to KeymanEngine, replace with direct API call
/*
* method to update prediction or correction UserDefaults as specified
*/
func writeLexicalModelSettings(userDefaultsKey: String, languageId: String, modelId: String, on: Bool) {
var newSettings: [String:Bool] = [:]
// get current settings for all dictionaries, if they exist in UserDefaults
let sharedData: UserDefaults = FVShared.userDefaults()
if let settings = sharedData.dictionary(forKey: userDefaultsKey) as? [String : Bool] {
if settings[languageId] != nil {
newSettings = settings
}
}
newSettings[languageId] = on
sharedData.set(newSettings, forKey: userDefaultsKey)
}
// TODO: delete call to registerLexicalModel, replace with direct API call
/*
* call to KeymanEngine registerLexicalModel to force new settings to be applied
*/
func applyLexicalModelSettings(languageId: String, modelId: String) -> Bool {
let registered = Manager.shared.registerLexicalModel(lexicalModelId: modelId, languageId: languageId)
if (!registered) {
print("Could not register lexical model for prediction settings; lang=\(languageId), lexical model ID=\(modelId)")
}
return registered
}
}
class FVLexicalModel {
let name: String
let id: String
let packageUrl: URL
let languageTag: String
let version: String
internal init(name: String, id: String, packageUrl: String, languageTag: String, version: String) {
self.name = name
self.id = id
self.packageUrl = URL(string: packageUrl)!
self.languageTag = languageTag
self.version = version
}
}