Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ios/engine/KMEI/KeymanEngine/Classes/Constants.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public enum Key {
/// Dictionary of prediction/correction toggle settings keyed by language id in UserDefaults
static let userPredictSettings = "UserPredictionEnablementSettings"
static let userCorrectSettings = "UserCorrectionEnablementSettings"
static let userAutocorrectSettings = "UserAutocorrectionEnablementSettings"

// Internal user defaults keys
static let engineVersion = "KeymanEngineVersion"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,17 @@ public extension UserDefaults {
set(prefs, forKey: Key.userCorrectSettings)
}
}

// stores a dictionary of autocorrection-enablement settings keyed to language ids, i.e., [langID: Bool]
var autocorrectionEnablements: [String: Bool]? {
get {
return dictionary(forKey: Key.userAutocorrectSettings) as? [String : Bool]
}

set(prefs) {
set(prefs, forKey: Key.userAutocorrectSettings)
}
}

var portraitKeyboardHeight: Double {
get {
Expand Down Expand Up @@ -387,4 +398,22 @@ public extension UserDefaults {
prefs?[forLanguageID] = correctSetting
correctionEnablements = prefs
}

func autocorrectSettingForLanguage(languageID: String) -> Bool {
if let dict = autocorrectionEnablements {
return dict[languageID] ?? true
} else {
return true
}
}

func set(autocorrectSetting: Bool, forLanguageID: String) {
var prefs: [String: Bool]?
prefs = autocorrectionEnablements
if prefs == nil {
prefs = [String: Bool]()
}
prefs?[forLanguageID] = autocorrectSetting
autocorrectionEnablements = prefs
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ extension KeymanWebViewController {
}
view = nil
}

func languageMenuPosition(_ completion: @escaping (CGRect) -> Void) {
webView!.evaluateJavaScript("langMenuPos();") { result, _ in
guard let result = result as? String, !result.isEmpty else {
Expand Down Expand Up @@ -422,10 +422,11 @@ extension KeymanWebViewController {

let predict = userDefaults.predictSettingForLanguage(languageID: lexicalModel.languageID)
let correct = userDefaults.correctSettingForLanguage(languageID: lexicalModel.languageID)
let autocorrect = userDefaults.autocorrectSettingForLanguage(languageID: lexicalModel.languageID)

// Pass these off to KMW!
// We do these first so that they're automatically set for the to-be-registered model in advance.
webView!.evaluateJavaScript("enableSuggestions(\(stubString), \(predict), \(correct))")
webView!.evaluateJavaScript("enableSuggestions(\(stubString), \(predict), \(correct), \(autocorrect))")
self.activeModel = predict
} else { // We're registering a model in the background - don't change settings.
webView!.evaluateJavaScript("keyman.addModel(\(stubString));", completionHandler: nil)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@ class LanguageSettingsViewController: UITableViewController {

private var doPredictionsSwitch: UISwitch?
private var doCorrectionsSwitch: UISwitch?
private var doAutocorrectionsSwitch: UISwitch?
private var doCorrectionsLabel: UILabel?
private var doAutocorrectionsLabel: UILabel?
private var correctionsCell: UITableViewCell?
private var autocorrectionsCell: UITableViewCell?

public init(_ inLanguage: Language) {
language = inLanguage
Expand Down Expand Up @@ -87,17 +90,8 @@ class LanguageSettingsViewController: UITableViewController {
return switchFrame
}

@objc
func predictionSwitchValueChanged(source: UISwitch) {
let value = source.isOn;
func refreshModelIfNeeded() {
let userDefaults = Storage.active.userDefaults
userDefaults.set(predictSetting: value, forLanguageID: self.language.id)

// Reactively set the corrections switch interactivity state.
self.doCorrectionsSwitch?.isHidden = !value
self.doCorrectionsLabel?.isEnabled = value
self.correctionsCell?.isUserInteractionEnabled = value

if let lm = Manager.shared.preferredLexicalModel(userDefaults, forLanguage: self.language.id) {
if Manager.shared.currentKeyboardID?.languageID == self.language.id {
// re-register the model - that'll enact the settings.
Expand All @@ -111,23 +105,59 @@ class LanguageSettingsViewController: UITableViewController {
}
}

@objc
func predictionSwitchValueChanged(source: UISwitch) {
let value = source.isOn;
let userDefaults = Storage.active.userDefaults
userDefaults.set(predictSetting: value, forLanguageID: self.language.id)

// Reactively set the corrections switch interactivity state.
self.doCorrectionsSwitch?.isHidden = !value
self.doCorrectionsLabel?.isEnabled = value

let mayCorrect = userDefaults.autocorrectSettingForLanguage(languageID: self.language.id)
self.doAutocorrectionsSwitch?.isHidden = !(value && mayCorrect)
self.doAutocorrectionsLabel?.isEnabled = value && mayCorrect
self.correctionsCell?.isUserInteractionEnabled = value
}

@objc
func correctionSwitchValueChanged(source: UISwitch) {
let value = source.isOn;
let userDefaults = Storage.active.userDefaults
userDefaults.set(correctSetting: value, forLanguageID: self.language.id)

if let lm = Manager.shared.preferredLexicalModel(userDefaults, forLanguage: self.language.id) {
if Manager.shared.currentKeyboardID?.languageID == self.language.id {
// re-register the model - that'll enact the settings.
_ = Manager.shared.registerLexicalModel(lm)
}
// Based on how Manager chooses the model in setKeyboard.
} else if let lm = userDefaults.userLexicalModels?.first(where: { $0.languageID == self.language.id }) {
if Manager.shared.currentKeyboardID?.languageID == self.language.id {
_ = Manager.shared.registerLexicalModel(lm)
}
}
// This may only be triggered if the predict toggle is on,
// so we can rely on just the input value.
self.doAutocorrectionsSwitch?.isHidden = !value
self.doAutocorrectionsLabel?.isEnabled = value

refreshModelIfNeeded()
}

@objc
func autocorrectionSwitchValueChanged(source: UISwitch) {
let value = source.isOn;
let userDefaults = Storage.active.userDefaults
userDefaults.set(autocorrectSetting: value, forLanguageID: self.language.id)

refreshModelIfNeeded()
}

func addSwitchToTableCell(_ toggle: UISwitch, cell: UITableViewCell, isOn: Bool, selector: Selector) {
cell.accessoryType = .none
toggle.translatesAutoresizingMaskIntoConstraints = false

let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: toggle.frame.size)
toggle.frame = switchFrame

toggle.isOn = isOn
toggle.addTarget(self, action: selector, for: .valueChanged)
cell.addSubview(toggle)
cell.contentView.isUserInteractionEnabled = false

toggle.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true
toggle.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true
}

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
Expand All @@ -143,40 +173,44 @@ class LanguageSettingsViewController: UITableViewController {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
if 1 == indexPath.section {
if 0 == indexPath.row {
cell.accessoryType = .none
doPredictionsSwitch = UISwitch()
doPredictionsSwitch!.translatesAutoresizingMaskIntoConstraints = false

let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: doPredictionsSwitch!.frame.size)
doPredictionsSwitch!.frame = switchFrame

doPredictionsSwitch!.isOn = userDefaults.predictSettingForLanguage(languageID: self.language.id)
doPredictionsSwitch!.addTarget(self, action: #selector(self.predictionSwitchValueChanged), for: .valueChanged)
cell.addSubview(doPredictionsSwitch!)
cell.contentView.isUserInteractionEnabled = false

doPredictionsSwitch!.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true
doPredictionsSwitch!.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true
self.addSwitchToTableCell(
doPredictionsSwitch!,
cell: cell,
isOn: userDefaults.predictSettingForLanguage(languageID: self.language.id),
selector: #selector(self.predictionSwitchValueChanged)
)
} else if 1 == indexPath.row {
correctionsCell = cell
cell.accessoryType = .none
doCorrectionsSwitch = UISwitch()
doCorrectionsSwitch!.translatesAutoresizingMaskIntoConstraints = false

let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: doCorrectionsSwitch!.frame.size)
doCorrectionsSwitch!.frame = switchFrame

doCorrectionsSwitch!.isOn = userDefaults.correctSettingForLanguage(languageID: self.language.id)
doCorrectionsSwitch!.addTarget(self, action: #selector(self.correctionSwitchValueChanged), for: .valueChanged)
cell.addSubview(doCorrectionsSwitch!)
cell.contentView.isUserInteractionEnabled = false

doCorrectionsSwitch!.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true
doCorrectionsSwitch!.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true
self.addSwitchToTableCell(
doCorrectionsSwitch!,
cell: cell,
isOn: userDefaults.correctSettingForLanguage(languageID: self.language.id),
selector: #selector(self.correctionSwitchValueChanged)
)

// Disable interactivity if the prediction toggle is set to 'off'.
doCorrectionsSwitch!.isHidden = !userDefaults.predictSettingForLanguage(languageID: self.language.id)
cell.isUserInteractionEnabled = userDefaults.predictSettingForLanguage(languageID: self.language.id)
} else if 2 == indexPath.row {
autocorrectionsCell = cell
doAutocorrectionsSwitch = UISwitch()

self.addSwitchToTableCell(
doAutocorrectionsSwitch!,
cell: cell,
isOn: userDefaults.autocorrectSettingForLanguage(languageID: self.language.id),
selector: #selector(self.autocorrectionSwitchValueChanged)
)

// Disable interactivity if the prediction or correction toggle is set to 'off'.
let mayPredict = userDefaults.predictSettingForLanguage(languageID: self.language.id)
let mayCorrect = userDefaults.correctSettingForLanguage(languageID: self.language.id)
doAutocorrectionsSwitch!.isHidden = !(mayPredict && mayCorrect)
cell.isUserInteractionEnabled = mayPredict && mayCorrect
} else { // rows 3 and 4
cell.accessoryType = .disclosureIndicator
}
Expand Down Expand Up @@ -245,6 +279,10 @@ class LanguageSettingsViewController: UITableViewController {
cell.textLabel?.text = NSLocalizedString("menu-langsettings-toggle-correct", bundle: engineBundle, comment: "")
cell.textLabel?.isEnabled = !(doCorrectionsSwitch?.isHidden ?? false)
case 2:
doAutocorrectionsLabel = cell.textLabel
cell.textLabel?.text = NSLocalizedString("menu-langsettings-toggle-autocorrect", bundle: engineBundle, comment: "")
cell.textLabel?.isEnabled = !(doAutocorrectionsSwitch?.isHidden ?? false)
case 3:
cell.textLabel?.text = NSLocalizedString("menu-langsettings-label-lexical-models", bundle: engineBundle, comment: "")
cell.accessoryType = .disclosureIndicator
let modelCt = language.lexicalModels?.count ?? 0
Expand Down Expand Up @@ -332,8 +370,8 @@ class LanguageSettingsViewController: UITableViewController {
showKeyboardInfoView(kb: (language.keyboards?[safe: indexPath.row])!)
case 1:
switch indexPath.row {
// case 0, 1: the toggles - but a general 'click' not on the toggle itself.
case 2:
// case 0, 1, 2: the toggles - but a general 'click' not on the toggle itself.
case 3:
showLexicalModelsView()
default:
break
Expand Down
3 changes: 3 additions & 0 deletions ios/engine/KMEI/KeymanEngine/en.lproj/Localizable.strings
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,9 @@
/* Label for the toggle that enables corrections that is displayed within a language-specific settings menu */
"menu-langsettings-toggle-correct" = "Enable corrections";

/* Label for the toggle that enables auto-corrections that is displayed within a language-specific settings menu */
"menu-langsettings-toggle-autocorrect" = "Enable autocorrections";

/* Label for the toggle that enables predictions that is displayed within a language-specific settings menu */
"menu-langsettings-toggle-predict" = "Enable predictions";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -326,11 +326,12 @@ function toHex(theString) {
return hexString.substr(0, hexString.length-1);
}

function enableSuggestions(model, mayPredict, mayCorrect) {
function enableSuggestions(model, mayPredict, mayCorrect, mayAutocorrect) {
// Set the options first so that KMW's ModelCache can properly handle model enablement states
// the moment we actually register the new model.
keyman.core.languageProcessor.mayPredict = mayPredict;
keyman.core.languageProcessor.mayCorrect = mayCorrect;
keyman.core.languageProcessor.mayAutocorrect = mayAutocorrect;

keyman.addModel(model);
}
Expand Down
15 changes: 15 additions & 0 deletions web/src/engine/src/main/headless/languageProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,12 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {
return !!this.recentTranscriptions.get(transitionId);
}

/**
* When called, this requests the worker to generate predictions for the transcribed
* context transition.
* @param transcription
* @param layerId
*/
public predict(transcription: Transcription, layerId: string): Promise<Suggestion[]> {
if(!this.isActive) {
return null;
Expand Down Expand Up @@ -373,6 +379,9 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {
this.lmEngine.resetContext(context, transcription.token);
}

// The "may correct" setting is enforced here, in the main Web engine - not
// in the worker. As the desired setting may vary language-to-language,
// we'd rather do this than reconfigure the worker constantly.
let alternates = transcription.alternates;
if(!this.mayCorrect || !alternates || alternates.length == 0) {
alternates = [{
Expand All @@ -386,6 +395,12 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {

return promise.then((suggestions: Suggestion[]) => {
if(promise == this.currentPromise) {
if(!this.mayAutoCorrect) {
// We disable the auto-accept flag here, rather than in the worker -
// that way, we don't need to reconfigure the worker when settings
// change.
suggestions.forEach((s) => delete s.autoAccept);
}
const result = new ReadySuggestions(suggestions, transform.id);
this.emit("suggestionsready", result);
this.currentPromise = null;
Expand Down
Loading