Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): Add controls for auto-correct #12443

Merged
merged 8 commits into from
Oct 9, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -169,8 +169,9 @@ public void onStartInput(EditorInfo attribute, boolean restarting) {
if (kbInfo != null) {
String langId = kbInfo.getLanguageID();
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean mayPredict = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(langId), true);
KMManager.setBannerOptions(mayPredict);
int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(langId), KMManager.KMDefault_Suggestion);
// Enable banner if maySuggest is not SuggestionType.SUGGESTIONS_DISABLED (0)
KMManager.setBannerOptions(maySuggest > 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid using a number 0 here and use SuggestionType.SUGGESTIONS_DISABLED?

} else {
KMManager.setBannerOptions(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,13 @@
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.RadioGroup;
import android.widget.RadioGroup.OnCheckedChangeListener;
import android.widget.RelativeLayout;
import android.widget.TextView;

import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
Expand Down Expand Up @@ -55,50 +59,6 @@ public final class LanguageSettingsActivity extends AppCompatActivity {

private final static String TAG = "LanguageSettingsAct";

private class PreferenceToggleListener implements View.OnClickListener {
String prefsKey;
String lgCode;

public PreferenceToggleListener(String prefsKey, String lgCode) {
this.prefsKey = prefsKey;
this.lgCode = lgCode;
}

@Override
public void onClick(View v) {
// For predictions/corrections toggle
SwitchCompat toggle = (SwitchCompat) v;

SharedPreferences.Editor prefEditor = prefs.edit();

// predictionsToggle overrides correctionToggle and correctionsTextView
if (prefsKey.endsWith(KMManager.predictionPrefSuffix)) {
boolean override = toggle.isChecked();
overrideCorrectionsToggle(override);
}

// This will allow preemptively making settings for languages without models.
// Seems more trouble than it's worth to block this.
prefEditor.putBoolean(prefsKey, toggle.isChecked());
prefEditor.apply();

// Don't use/apply language modeling settings for languages without models.
if (associatedLexicalModel.isEmpty()) {
return;
}

Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(context);
if(kbInfo != null) {
// If the active keyboard is for this language, immediately enact the new pref setting.
String kbdLgCode = kbInfo.getLanguageID();
if (kbdLgCode.equals(lgCode)) {
// Not only registers the model but also applies our modeling preferences.
KMManager.registerAssociatedLexicalModel(lgCode);
}
}
}
}

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Expand Down Expand Up @@ -139,33 +99,59 @@ public void onCreate(Bundle savedInstanceState) {

FilteredKeyboardsAdapter adapter = new FilteredKeyboardsAdapter(context, KeyboardPickerActivity.getInstalledDataset(context), lgCode);

// The following two layouts/toggles will need to link with these objects.
// The following radio group will need to link with these objects.
Context appContext = this.getApplicationContext();
prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean mayPredict = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(lgCode), true);
boolean mayCorrect = prefs.getBoolean(KMManager.getLanguageCorrectionPreferenceKey(lgCode), true);

RelativeLayout layout = (RelativeLayout)findViewById(R.id.corrections_toggle);
int maySuggest = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(lgCode), KMManager.KMDefault_Suggestion);

// Radio button change listeners
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.suggestion_radio_group);
RadioButton radioButton;
// Initialize radio button group
switch(maySuggest) {
case 1:
radioButton = (RadioButton) radioGroup.findViewById(R.id.suggestion_radio_1);
break;
case 2:
radioButton = (RadioButton) radioGroup.findViewById(R.id.suggestion_radio_2);
break;
case 3:
radioButton = (RadioButton) radioGroup.findViewById(R.id.suggestion_radio_3);
break;
case 0:
default:
radioButton = (RadioButton) radioGroup.findViewById(R.id.suggestion_radio_0);
}
radioGroup.clearCheck();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would be neater with an array but it's not critical.

radioButton.setChecked(true);

correctionsTextView = (TextView) layout.findViewById(R.id.text1);
correctionsTextView.setText(getString(R.string.enable_corrections));
correctionsToggle = layout.findViewById(R.id.toggle);
correctionsToggle.setChecked(mayCorrect); // Link to persistent option storage! Also needs handler.
String prefsKey = KMManager.getLanguageCorrectionPreferenceKey(lgCode);
correctionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode));
radioGroup.setOnCheckedChangeListener(new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, @IdRes int checkId) {
RadioButton checkedButton = (RadioButton)radioGroup.findViewById(checkId);
int index = radioGroup.indexOfChild(checkedButton);
int radioButtonID = radioGroup.getCheckedRadioButtonId();

layout = (RelativeLayout)findViewById(R.id.predictions_toggle);
KMManager.setMaySuggest(lgCode, index);

textView = (TextView) layout.findViewById(R.id.text1);
textView.setText(getString(R.string.enable_predictions));
SwitchCompat predictionsToggle = layout.findViewById(R.id.toggle);
predictionsToggle.setChecked(mayPredict); // Link to persistent option storage! Also needs handler.
prefsKey = KMManager.getLanguagePredictionPreferenceKey(lgCode);
predictionsToggle.setOnClickListener(new PreferenceToggleListener(prefsKey, lgCode));
// Don't use/apply language modeling settings for languages without models.
if (associatedLexicalModel.isEmpty()) {
return;
}

overrideCorrectionsToggle(mayPredict);
Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(context);
if(kbInfo != null) {
// If the active keyboard is for this language, immediately enact the new pref setting.
String kbdLgCode = kbInfo.getLanguageID();
if (kbdLgCode.equals(lgCode)) {
// Not only registers the model but also applies our modeling preferences.
KMManager.registerAssociatedLexicalModel(lgCode);
}
}
}
});

layout = (RelativeLayout)findViewById(R.id.model_picker);
RelativeLayout layout = (RelativeLayout)findViewById(R.id.model_picker);
textView = (TextView) layout.findViewById(R.id.text1);
textView.setText(getString(R.string.model_label));

Expand Down
39 changes: 36 additions & 3 deletions android/KMEA/app/src/main/assets/android-host.js
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,44 @@ function deregisterModel(modelID) {
keyman.removeModel(modelID);
}

function enableSuggestions(model, mayPredict, mayCorrect) {
function enableSuggestions(model, mayPredict, maySuggest) {
darcywong00 marked this conversation as resolved.
Show resolved Hide resolved
// Set the options first so that KMW's ModelManager can properly handle model enablement states
// the moment we actually register the new model.
keyman.core.languageProcessor.mayPredict = mayPredict;
keyman.core.languageProcessor.mayCorrect = mayCorrect;
// Use console_debug
console_debug('enableSuggestions(mayPredict='+mayPredict+', maySuggest='+maySuggest+')');
if (!mayPredict) {
keyman.core.languageProcessor.mayPredict = false;
keyman.core.languageProcessor.mayCorrect = false;
// keyman.core.languageProcessor.mayAutoCorrect = false;
} else {
switch(maySuggest) {
case 1 :
// SuggestionType.PREDICTIONS_ONLY
keyman.core.languageProcessor.mayPredict = true;
keyman.core.languageProcessor.mayCorrect = false;
//keyman.core.languageProcessor.mayAutoCorrect = false;
break;
case 2 :
// SuggestionType.PREDICTIONS_WITH_CORRECTIONS
keyman.core.languageProcessor.mayPredict = true;
keyman.core.languageProcessor.mayCorrect = true;
//keyman.core.languageProcessor.mayAutoCorrect = false;
break;
case 3 :
// SuggesionType.PREDICTIONS_WITH_AUTO_CORRECT
keyman.core.languageProcessor.mayPredict = true;
keyman.core.languageProcessor.mayCorrect = true;
//keyman.core.languageProcessor.mayAutoCorrect = true;
break;
case 0 :
default :
// SuggestionType.SUGGESTIONS_DISABLED
keyman.core.languageProcessor.mayPredict = false;
keyman.core.languageProcessor.mayCorrect = false;
//keyman.core.languageProcessor.mayAutoCorrect = false;
break;
darcywong00 marked this conversation as resolved.
Show resolved Hide resolved
}
}

registerModel(model);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,8 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) {
SharedPreferences prefs = context.getSharedPreferences(context.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
boolean modelPredictionPref = false;
if (!KMManager.getMayPredictOverride() && KMManager.currentLexicalModel != null) {
modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true);
modelPredictionPref = prefs.getInt(KMManager.getLanguageAutoCorrectionPreferenceKey(
KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), KMManager.KMDefault_Suggestion) > 0;
}
KMManager.setBannerOptions(modelPredictionPref);
RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams();
Expand Down
61 changes: 57 additions & 4 deletions android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,36 @@ public enum EnterModeType {
DEFAULT, // Default ENTER action
}

// Enum for whether the suggestion banner allows predictions, corrections, auto-corrections
public enum SuggestionType {
// Suggestion Disabled - No Predictions, No corrections, No auto-corrections
SUGGESTIONS_DISABLED,

// Suggestions Enabled
PREDICTIONS_ONLY, // Predictions with no corrections
PREDICTIONS_WITH_CORRECTIONS, // Predictions with corrections
PREDICTIONS_WITH_AUTO_CORRECT; // Predictions with auto-corrections

public static SuggestionType fromInt(int mode) {
switch (mode) {
case 0:
return SUGGESTIONS_DISABLED;
case 1:
return PREDICTIONS_ONLY;
case 2:
return PREDICTIONS_WITH_CORRECTIONS;
case 3:
return PREDICTIONS_WITH_AUTO_CORRECT;
}
return SUGGESTIONS_DISABLED;
}

public int toInt() {
int modes[] = { 0, 1, 2, 3 };
return modes[this.ordinal()];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just return this.ordinal();?

}
}

protected static InputMethodService IMService;

private static boolean debugMode = false;
Expand Down Expand Up @@ -220,6 +250,7 @@ public enum EnterModeType {

public final static String predictionPrefSuffix = ".mayPredict";
public final static String correctionPrefSuffix = ".mayCorrect";
public final static String autoCorrectionPrefSuffix = ".mayAutoCorect";

// Special override for when the keyboard may have haptic feedback when typing.
// haptic feedback disabled for hardware keystrokes
Expand Down Expand Up @@ -315,6 +346,8 @@ public enum EnterModeType {
public static final int KMMinimum_LongpressDelay = 300;
public static final int KMMaximum_LongpressDelay = 1500;

// Default prediction/correction setting - corresponds to SuggestionType.PREDICTIONS_WITH_CORRECTIONS
public static final int KMDefault_Suggestion = 2;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still got a magic number here


// Keyman files
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
Expand Down Expand Up @@ -707,6 +740,10 @@ public static String getLanguageCorrectionPreferenceKey(String langID) {
return langID + correctionPrefSuffix;
}

public static String getLanguageAutoCorrectionPreferenceKey(String langID) {
return langID + autoCorrectionPrefSuffix;
}

public static void hideSystemKeyboard() {
if (SystemKeyboard != null) {
SystemKeyboard.hideKeyboard();
Expand Down Expand Up @@ -1350,6 +1387,22 @@ public static boolean getMayPredictOverride() {
return mayPredictOverride;
}

/**
* Maps radio button index 0..3 to SuggestionType and then store as preference
* @param languageID as String
* @param suggestType Radio button index 0 to 3
*/
public static void setMaySuggest(String languageID, int suggestType) {
if (suggestType < 0 || suggestType > 3) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use SuggestionType.SUGGESTIONS_DISABLED.toInt() or something like that? Everywhere we have integer values in the java code?

// Invalid values go to SUGGESTIONS_DISABLED
suggestType = 0;
}
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putInt(KMManager.getLanguageAutoCorrectionPreferenceKey(languageID), suggestType);
editor.commit();
}

/**
* Determines if the InputType field is a numeric field
* @param inputType
Expand Down Expand Up @@ -1464,22 +1517,22 @@ public static boolean registerLexicalModel(HashMap<String, String> lexicalModelI

// When entering password field, mayPredict should override to false
SharedPreferences prefs = appContext.getSharedPreferences(appContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
int maySuggest = prefs.getInt(getLanguageAutoCorrectionPreferenceKey(languageID), KMDefault_Suggestion);
boolean mayPredict = (mayPredictOverride) ? false :
prefs.getBoolean(getLanguagePredictionPreferenceKey(languageID), true);
boolean mayCorrect = prefs.getBoolean(getLanguageCorrectionPreferenceKey(languageID), true);
maySuggest > 0;

RelativeLayout.LayoutParams params;
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();

// Do NOT re-layout here; it'll be triggered once the banner loads.
InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
InAppKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %d)", model, mayPredict, maySuggest));
}
if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreTextChange() && modelFileExists) {
params = getKeyboardLayoutParams();

// Do NOT re-layout here; it'll be triggered once the banner loads.
SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %s)", model, mayPredict, mayCorrect));
SystemKeyboard.loadJavascript(KMString.format("enableSuggestions(%s, %s, %d)", model, mayPredict, maySuggest));
}
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,17 +60,44 @@
android:layout_height="2dp"
android:background="?attr/colorAccent" />

<include
android:id="@+id/predictions_toggle"
layout="@layout/list_row_layout4"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<include
android:id="@+id/corrections_toggle"
layout="@layout/list_row_layout4"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<!-- Predictions and corrections controls replaced by radio button group
Sadly can't use LinearLayouts of list_row_layout4 because
it breaks selections in RadioGroup -->
<RadioGroup
android:id="@+id/suggestion_radio_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">

<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/suggestion_radio_0"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_0" />

<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/suggestion_radio_1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_1" />

<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/suggestion_radio_2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_2" />

<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/suggestion_radio_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_3" />

</RadioGroup>

<include
android:id="@+id/model_picker"
Expand All @@ -79,10 +106,6 @@
android:layout_height="wrap_content" />
</LinearLayout>

<!-- Enable corrections toggle -->

<!-- Enable predictions toggle -->

<!-- Model picker -->

<!-- Model dictionary -->
Expand Down
Loading
Loading