Total engine refactoring (#44)
* totally refactored the Languages, the Database and the IME module. New and more clear folder/package structure * CharMap and LangHelper are no more * removed many unused icons, translations and other resources * deleted the old keymap samples * Update user-manual.md * Preferences are stored in Preferences, not in the database. * removed some unclear and unused settings from the Preferences screen and the code. * fixed issues with displaying the UI * removed all non-English words and words with foreign spelling from the English dictionary * 0 now works as it should in all modes. * a more clear newline character in suggestions view * last used input mode is now preserved and restored the next time * removed the smiley and symbol dialogs * capsMode -> textCase * language names are no longer translated * updated build instructions * better use of threads for DB operations * current text candidate is as long as the sequence, to make it more intuitive * single characters are added to the end of the suggestion, when there are no more in the database * Added ѝ to Bulgarian character map * disabled adding words in a user-friendly manner * when adding a new word is not possible, a toast message appears, for better user experience * an error is logged when there are duplicate language IDs * settings -> settings_legacy * custom Logger class for easier debugging and no logcat flood in the Release version * removed unnecessary single letters and invalid words from the dictionaries * more translations cleanup; also corrected some weird translations * upgraded gradle
This commit is contained in:
parent
af172b79c9
commit
78b6681812
225 changed files with 2723 additions and 4986 deletions
|
|
@ -1,301 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.Context;
|
||||
import android.inputmethodservice.KeyboardView;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public abstract class AbsSymDialog extends Dialog implements
|
||||
View.OnClickListener {
|
||||
|
||||
protected Context context;
|
||||
private View mainview;
|
||||
private int pagenum = 1;
|
||||
private int pageoffset = (pagenum - 1) * 10;
|
||||
|
||||
private int MAX_PAGE;
|
||||
private boolean started;
|
||||
|
||||
private static final int[] buttons = {
|
||||
R.id.text_keyone, R.id.text_keytwo,
|
||||
R.id.text_keythree, R.id.text_keyfour, R.id.text_keyfive,
|
||||
R.id.text_keysix, R.id.text_keyseven, R.id.text_keyeight,
|
||||
R.id.text_keynine, R.id.text_keyzero
|
||||
};
|
||||
private static final int[] buttons2 = {
|
||||
R.id.text_keystar,
|
||||
R.id.text_keypound
|
||||
};
|
||||
|
||||
public AbsSymDialog(Context c, View mv) {
|
||||
super(c);
|
||||
context = c;
|
||||
mainview = mv;
|
||||
started = true;
|
||||
setContentView(mv);
|
||||
|
||||
View button;
|
||||
for (int butt : buttons) {
|
||||
button = mv.findViewById(butt);
|
||||
button.setOnClickListener(this);
|
||||
}
|
||||
for (int butt : buttons2) {
|
||||
button = mv.findViewById(butt);
|
||||
button.setOnClickListener(this);
|
||||
}
|
||||
MAX_PAGE = getMaxPage();
|
||||
}
|
||||
|
||||
// must return a string array the same size as the length of the button string array.
|
||||
abstract String[] getContentDescription();
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
// Log.d("SymbolPopup - onClick", "click happen: " + v);
|
||||
switch (v.getId()) {
|
||||
case R.id.text_keyone:
|
||||
sendChar(pageoffset);
|
||||
break;
|
||||
case R.id.text_keytwo:
|
||||
sendChar(pageoffset + 1);
|
||||
break;
|
||||
case R.id.text_keythree:
|
||||
sendChar(pageoffset + 2);
|
||||
break;
|
||||
case R.id.text_keyfour:
|
||||
sendChar(pageoffset + 3);
|
||||
break;
|
||||
case R.id.text_keyfive:
|
||||
sendChar(pageoffset + 4);
|
||||
break;
|
||||
case R.id.text_keysix:
|
||||
sendChar(pageoffset + 5);
|
||||
break;
|
||||
case R.id.text_keyseven:
|
||||
sendChar(pageoffset + 6);
|
||||
break;
|
||||
case R.id.text_keyeight:
|
||||
sendChar(pageoffset + 7);
|
||||
break;
|
||||
case R.id.text_keynine:
|
||||
sendChar(pageoffset + 8);
|
||||
break;
|
||||
case R.id.text_keyzero:
|
||||
sendChar(pageoffset + 9);
|
||||
break;
|
||||
|
||||
case R.id.text_keypound:
|
||||
pageChange(1);
|
||||
break;
|
||||
case R.id.text_keystar:
|
||||
pageChange(-1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract String getSymbol(int index);
|
||||
protected abstract String getTitleText();
|
||||
protected abstract int getSymbolSize();
|
||||
protected abstract int getMaxPage();
|
||||
|
||||
private void sendChar(int index) {
|
||||
// Log.d("SymbolDialog - sendChar", "Sending index: " + index);
|
||||
|
||||
if (index < getSymbolSize()) {
|
||||
((KeyboardView.OnKeyboardActionListener) context).onText(getSymbol(index));
|
||||
// then close
|
||||
pagenum = 1;
|
||||
pageoffset = (pagenum - 1) * 10;
|
||||
this.dismiss();
|
||||
}
|
||||
}
|
||||
|
||||
private void pageChange(int amt) {
|
||||
pagenum = pagenum + amt;
|
||||
if (pagenum > MAX_PAGE) {
|
||||
pagenum = 1;
|
||||
} else if (pagenum < 1) {
|
||||
pagenum = MAX_PAGE;
|
||||
}
|
||||
pageoffset = (pagenum - 1) * 10;
|
||||
updateButtons();
|
||||
}
|
||||
|
||||
private void updateButtons() {
|
||||
// set page number text
|
||||
setTitle(String.format("%s\t\t%s", getTitleText(),
|
||||
context.getResources().getString(R.string.symbol_page, pagenum, MAX_PAGE)));
|
||||
// update button labels
|
||||
int symbx = pageoffset;
|
||||
int stop = symbx + 9;
|
||||
int nomore = stop;
|
||||
int symsize = getSymbolSize();
|
||||
if (nomore >= symsize - 1) {
|
||||
nomore = symsize - 1;
|
||||
}
|
||||
TextView tv;
|
||||
String[] cd = getContentDescription();
|
||||
|
||||
for (int buttx = 0; symbx <= stop; symbx++) {
|
||||
// Log.d("SymbolDialog - updateButtons", "buttx: " + buttx +
|
||||
// " symbx: " + symbx);
|
||||
if (symbx > nomore) {
|
||||
((TextView) mainview.findViewById(buttons[buttx])).setText("");
|
||||
} else {
|
||||
tv = (TextView) mainview.findViewById(buttons[buttx]);
|
||||
tv.setText(String.valueOf(getSymbol(symbx)));
|
||||
if (cd != null) {
|
||||
tv.setContentDescription(cd[symbx]);
|
||||
}
|
||||
}
|
||||
buttx++;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (event.getRepeatCount() != 0) {
|
||||
return true;
|
||||
}
|
||||
if (started) {
|
||||
started = false;
|
||||
}
|
||||
// TODO: remove emulator special keys
|
||||
switch (keyCode) {
|
||||
case 75:
|
||||
keyCode = KeyEvent.KEYCODE_POUND;
|
||||
break;
|
||||
case 74:
|
||||
keyCode = KeyEvent.KEYCODE_STAR;
|
||||
break;
|
||||
}
|
||||
// Log.d("AbsSymDialog.onKeyDown", "bootan pres: " + keyCode);
|
||||
switch (keyCode) {
|
||||
|
||||
case KeyEvent.KEYCODE_0:
|
||||
case KeyEvent.KEYCODE_1:
|
||||
case KeyEvent.KEYCODE_2:
|
||||
case KeyEvent.KEYCODE_3:
|
||||
case KeyEvent.KEYCODE_4:
|
||||
case KeyEvent.KEYCODE_5:
|
||||
case KeyEvent.KEYCODE_6:
|
||||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
event.startTracking();
|
||||
return true;
|
||||
}
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
// Log.d("AbsSymDialog.onKeyUp", "Key: " + keyCode);
|
||||
if (started) {
|
||||
started = false;
|
||||
return true;
|
||||
}
|
||||
// TODO: remove emulator special keys
|
||||
switch (keyCode) {
|
||||
case 75:
|
||||
keyCode = KeyEvent.KEYCODE_POUND;
|
||||
break;
|
||||
case 74:
|
||||
keyCode = KeyEvent.KEYCODE_STAR;
|
||||
break;
|
||||
}
|
||||
switch (keyCode) {
|
||||
// pass-through
|
||||
case KeyEvent.KEYCODE_0:
|
||||
case KeyEvent.KEYCODE_1:
|
||||
case KeyEvent.KEYCODE_2:
|
||||
case KeyEvent.KEYCODE_3:
|
||||
case KeyEvent.KEYCODE_4:
|
||||
case KeyEvent.KEYCODE_5:
|
||||
case KeyEvent.KEYCODE_6:
|
||||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
onKey(keyCode);
|
||||
return true;
|
||||
default:
|
||||
// KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD).getNumber(keyCode)
|
||||
// Log.w("onKeyUp", "Unhandled Key: " + keyCode + "(" +
|
||||
// event.toString() + ")");
|
||||
}
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
|
||||
private void onKey(int keyCode) {
|
||||
// Log.d("OnKey", "pri: " + keyCode);
|
||||
// Log.d("onKey", "START Cm: " + mCapsMode);
|
||||
// HANDLE SPECIAL KEYS
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_1:
|
||||
sendChar(pageoffset);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_2:
|
||||
sendChar(pageoffset + 1);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_3:
|
||||
sendChar(pageoffset + 2);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_4:
|
||||
sendChar(pageoffset + 3);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_5:
|
||||
sendChar(pageoffset + 4);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_6:
|
||||
sendChar(pageoffset + 5);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_7:
|
||||
sendChar(pageoffset + 6);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_8:
|
||||
sendChar(pageoffset + 7);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_9:
|
||||
sendChar(pageoffset + 8);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_0:
|
||||
sendChar(pageoffset + 9);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_STAR:
|
||||
pageChange(-1);
|
||||
break;
|
||||
case KeyEvent.KEYCODE_POUND:
|
||||
pageChange(1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void doShow(View v) {
|
||||
// based on http://stackoverflow.com/a/13962770
|
||||
started = true;
|
||||
Window win = getWindow();
|
||||
WindowManager.LayoutParams lp = win.getAttributes();
|
||||
lp.token = v.getWindowToken();
|
||||
lp.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
|
||||
win.setAttributes(lp);
|
||||
win.addFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
|
||||
updateButtons();
|
||||
try {
|
||||
show();
|
||||
} catch (Exception e) {
|
||||
Log.e("AbsSymDialog", "Cannot create Dialog:");
|
||||
Log.e("AbsSymDialog", Arrays.toString(e.getStackTrace()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,181 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import io.github.sspanak.tt9.LangHelper.LANGUAGE;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CharMap {
|
||||
protected static final AbstractList<Map<Character, Integer>> CHARTABLE = new ArrayList<Map<Character, Integer>>(LangHelper.NLANGS);
|
||||
static {
|
||||
// Punctuation
|
||||
Map<Character, Integer> commonMap = new HashMap<Character, Integer>();
|
||||
commonMap.put('.', 1); commonMap.put(',', 1); commonMap.put('!', 1); commonMap.put('?', 1);
|
||||
commonMap.put('-', 1); commonMap.put('"', 1); commonMap.put('\'', 1); commonMap.put('@', 1);
|
||||
commonMap.put('#', 1); commonMap.put('$', 1); commonMap.put('%', 1); commonMap.put('&', 1);
|
||||
commonMap.put('*', 1); commonMap.put('(', 1); commonMap.put(')', 1); commonMap.put(':', 1);
|
||||
commonMap.put(';', 1); commonMap.put('/', 1); commonMap.put('+', 1); commonMap.put('=', 1);
|
||||
commonMap.put('<', 1); commonMap.put('>', 1); commonMap.put('^', 1); commonMap.put('_', 1);
|
||||
commonMap.put('~', 1);
|
||||
commonMap.put('1', 1); commonMap.put('2', 2); commonMap.put('3', 3);
|
||||
commonMap.put('4', 4); commonMap.put('5', 5); commonMap.put('6', 6);
|
||||
commonMap.put('7', 7); commonMap.put('8', 8); commonMap.put('9', 9);
|
||||
commonMap.put('+', 0); commonMap.put('0', 0); // not sure why "+" is both on 1 on 0, but kept it anyway
|
||||
|
||||
/*** Latin Scripts ***/
|
||||
|
||||
// English
|
||||
// the English dictionary contains foreign words with their original spelling,
|
||||
// so non-English characters must be inside the map
|
||||
Map<Character, Integer> enMap = new HashMap<Character, Integer>(commonMap);
|
||||
enMap.put('a', 2); enMap.put('á', 2); enMap.put('ä', 2); enMap.put('â', 2);
|
||||
enMap.put('à', 2); enMap.put('å', 2); enMap.put('b', 2); enMap.put('c', 2);
|
||||
enMap.put('ç', 2);
|
||||
enMap.put('d', 3); enMap.put('e', 3); enMap.put('é', 3); enMap.put('ë', 3);
|
||||
enMap.put('è', 3); enMap.put('ê', 3); enMap.put('f', 3);
|
||||
enMap.put('g', 4); enMap.put('h', 4); enMap.put('i', 4); enMap.put('í', 4);
|
||||
enMap.put('ï', 4);
|
||||
enMap.put('j', 5); enMap.put('k', 5); enMap.put('l', 5); enMap.put('5', 5);
|
||||
enMap.put('m', 6); enMap.put('n', 6); enMap.put('ñ', 6); enMap.put('o', 6);
|
||||
enMap.put('ó', 6); enMap.put('ö', 6); enMap.put('ô', 6);
|
||||
enMap.put('p', 7); enMap.put('q', 7); enMap.put('r', 7); enMap.put('s', 7);
|
||||
enMap.put('t', 8); enMap.put('u', 8); enMap.put('û', 6); enMap.put('ü', 8);
|
||||
enMap.put('v', 8);
|
||||
enMap.put('w', 9); enMap.put('x', 9); enMap.put('y', 9); enMap.put('z', 9);
|
||||
|
||||
// add extra characters for German and French maps.
|
||||
enMap.put('€', 1); enMap.put('ß', 7); // German chars
|
||||
enMap.put('æ', 1); enMap.put('î', 4); enMap.put('ù', 8); enMap.put('œ', 6); // French chars
|
||||
enMap.put('ì', 4); enMap.put('ò', 8); // Italian chars
|
||||
|
||||
/*** Cyrillic Scripts ***/
|
||||
Map<Character, Integer> cyrillicMap = new HashMap<Character, Integer>(commonMap);
|
||||
cyrillicMap.put('а', 2); cyrillicMap.put('б', 2); cyrillicMap.put('в', 2); cyrillicMap.put('г', 2);
|
||||
cyrillicMap.put('д', 3); cyrillicMap.put('е', 3); cyrillicMap.put('ж', 3); cyrillicMap.put('з', 3);
|
||||
cyrillicMap.put('и', 4); cyrillicMap.put('й', 4); cyrillicMap.put('к', 4); cyrillicMap.put('л', 4);
|
||||
cyrillicMap.put('м', 5); cyrillicMap.put('н', 5); cyrillicMap.put('о', 5); cyrillicMap.put('п', 5);
|
||||
cyrillicMap.put('р', 6); cyrillicMap.put('с', 6); cyrillicMap.put('т', 6); cyrillicMap.put('у', 6);
|
||||
cyrillicMap.put('ф', 7); cyrillicMap.put('х', 7); cyrillicMap.put('ц', 7); cyrillicMap.put('ч', 7);
|
||||
cyrillicMap.put('ш', 8); cyrillicMap.put('щ', 8);
|
||||
cyrillicMap.put('ь', 9); cyrillicMap.put('ю', 9); cyrillicMap.put('я', 9);
|
||||
|
||||
// Bulgarian
|
||||
Map<Character, Integer> bgMap = new HashMap<Character, Integer>(cyrillicMap);
|
||||
bgMap.put('ъ', 8);
|
||||
|
||||
// Russian
|
||||
Map<Character, Integer> ruMap = new HashMap<Character, Integer>(bgMap);
|
||||
ruMap.put('ё', 3); ruMap.put('ы', 8); ruMap.put('э', 9);
|
||||
|
||||
// Ukrainian
|
||||
Map<Character, Integer> ukMap = new HashMap<Character, Integer>(cyrillicMap);
|
||||
ukMap.put('ґ', 2); ukMap.put('є', 3); ukMap.put('і', 4); ukMap.put('ї', 4);// Ukrainian chars
|
||||
|
||||
CHARTABLE.add(0, Collections.unmodifiableMap(enMap));
|
||||
CHARTABLE.add(1, Collections.unmodifiableMap(ruMap));
|
||||
CHARTABLE.add(2, Collections.unmodifiableMap(enMap));
|
||||
CHARTABLE.add(3, Collections.unmodifiableMap(enMap));
|
||||
CHARTABLE.add(4, Collections.unmodifiableMap(enMap));
|
||||
CHARTABLE.add(5, Collections.unmodifiableMap(ukMap));
|
||||
CHARTABLE.add(6, Collections.unmodifiableMap(bgMap));
|
||||
}
|
||||
|
||||
protected static final char[][] ENT9TABLE = { { '0', '+' },
|
||||
{ '.', ',', '?', '!', '"', '/', '-', '@', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'a', 'b', 'c', 'A', 'B', 'C', '2' }, { 'd', 'e', 'f', 'D', 'E', 'F', '3' },
|
||||
{ 'g', 'h', 'i', 'G', 'H', 'I', '4' }, { 'j', 'k', 'l', 'J', 'K', 'L', '5' },
|
||||
{ 'm', 'n', 'o', 'M', 'N', 'O', '6' }, { 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S', '7' },
|
||||
{ 't', 'u', 'v', 'T', 'U', 'V', '8' }, { 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
protected static final char[][] RUT9TABLE = { { '0', '+' },
|
||||
{ '.', ',', '?', '!', '"', '/', '-', '@', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'а', 'б', 'в', 'г', 'А', 'Б', 'В', 'Г', '2' }, { 'д', 'е', 'ё', 'ж', 'з', 'Д', 'Е', 'Ё', 'Ж', 'З', '3' },
|
||||
{ 'и', 'й', 'к', 'л', 'И', 'Й', 'К', 'Л', '4' }, { 'м', 'н', 'о', 'п', 'М', 'Н', 'О', 'П', '5' },
|
||||
{ 'р', 'с', 'т', 'у', 'Р', 'С', 'Т', 'У', '6' }, { 'ф', 'х', 'ц', 'ч', 'Ф', 'Х', 'Ц', 'Ч', '7' },
|
||||
{ 'ш', 'щ', 'ъ', 'ы', 'Ш', 'Щ', 'Ъ', 'Ы', '8' }, { 'ь', 'э', 'ю', 'я', 'Ь', 'Э', 'Ю', 'Я', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][] DET9TABLE = {
|
||||
{ '0', '+' },
|
||||
{ '.', ',', '?', '!', ':', ';', '"', '\'', '-', '@', '^', '€', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'a', 'b', 'c', 'A', 'B', 'C', 'ä', 'Ä','á', 'â', 'à', 'å', 'ç', 'Á', 'Â', 'À', 'Å', 'Ç', '2' },
|
||||
{ 'd', 'e', 'f', 'D', 'E', 'F', 'é','ë','è','ê', 'É', 'Ë', 'È', 'Ê', '3' },
|
||||
{ 'g', 'h', 'i', 'G', 'H', 'I', 'í', 'ï', 'Í', 'Ï', '4' },
|
||||
{ 'j', 'k', 'l', 'J', 'K', 'L', '5' },
|
||||
{ 'm', 'n', 'o', 'M', 'N', 'O', 'ö', 'Ö', 'ñ','ó','ô', 'Ñ', 'Ó', 'Ô', '6' },
|
||||
{ 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S', 'ß', '7' },
|
||||
{ 't', 'u', 'v', 'T', 'U', 'V', 'ü', 'Ü', 'û', 'Û', '8' },
|
||||
{ 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][] FRT9TABLE = {
|
||||
{ '0', '+' },
|
||||
{ '.', ',', '?', '!', ':', ';', '"', '/', '-', '@', '^', '€', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'a', 'b', 'c', 'A', 'B', 'C', '2', 'â', 'à', 'æ', 'ç', 'Â', 'À', 'Æ', 'Ç'},
|
||||
{ 'd', 'e', 'f', 'D', 'E', 'F', '3', 'é', 'è','ê', 'ë', 'É', 'È', 'Ê', 'Ë' },
|
||||
{ 'g', 'h', 'i', 'G', 'H', 'I', '4', 'î', 'ï', 'Î', 'Ï' },
|
||||
{ 'j', 'k', 'l', 'J', 'K', 'L', '5' },
|
||||
{ 'm', 'n', 'o', 'M', 'N', 'O', '6', 'ô', 'œ', 'Ô', 'Œ'},
|
||||
{ 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S', '7' },
|
||||
{ 't', 'u', 'v', 'T', 'U', 'V', '8', 'û', 'Û', 'ù', 'Ù', 'ü', 'Ü'},
|
||||
{ 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][] ITT9TABLE = {
|
||||
{ '+', '0' },
|
||||
{ '.', ',', '?', '!', ':', ';', '"', '/', '-', '@', '^', '€', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'a', 'b', 'c', 'A', 'B', 'C', 'à', 'À', '2' }, { 'd', 'e', 'f', 'D', 'E', 'F', 'é', 'è', 'É', 'È', '3' },
|
||||
{ 'g', 'h', 'i', 'G', 'H', 'I', 'ì', 'Ì', '4' }, { 'j', 'k', 'l', 'J', 'K', 'L', '5' },
|
||||
{ 'm', 'n', 'o', 'M', 'N', 'O', 'ò', 'Ò', '6' }, { 'p', 'q', 'r', 's', 'P', 'Q', 'R', 'S', '7' },
|
||||
{ 't', 'u', 'v', 'T', 'U', 'V', 'ù', 'Ù', '8' }, { 'w', 'x', 'y', 'z', 'W', 'X', 'Y', 'Z', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][] UKT9TABLE = { { '0', '+' },
|
||||
{ '.', ',', '?', '!', '\'', '"', '/', '-', '@', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'а', 'б', 'в', 'г', 'ґ', 'А', 'Б', 'В', 'Г', 'Ґ', '2' }, { 'д', 'е', 'є', 'ж', 'з', 'Д', 'Е', 'Є', 'Ж', 'З', '3' },
|
||||
{ 'и', 'і', 'ї', 'й', 'к', 'л', 'И', 'І', 'Ї', 'Й', 'К', 'Л', '4' }, { 'м', 'н', 'о', 'п', 'М', 'Н', 'О', 'П', '5' },
|
||||
{ 'р', 'с', 'т', 'у', 'Р', 'С', 'Т', 'У', '6' }, { 'ф', 'х', 'ц', 'ч', 'Ф', 'Х', 'Ц', 'Ч', '7' },
|
||||
{ 'ш', 'щ', 'Ш', 'Щ', '8' }, { 'ь', 'ю', 'я', 'Ь', 'Ю', 'Я', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][] BGT9TABLE = { { '0', '+' },
|
||||
{ '.', ',', '?', '!', '\'', '"', '/', '-', '@', '$', '%', '&', '*', '#', '(', ')', '_', '1' },
|
||||
{ 'а', 'б', 'в', 'г', 'А', 'Б', 'В', 'Г', '2' }, { 'д', 'е', 'ж', 'з', 'Д', 'Е', 'Ж', 'З', '3' },
|
||||
{ 'и', 'й', 'к', 'л', 'И', 'Й', 'К', 'Л', '4' }, { 'м', 'н', 'о', 'п', 'М', 'Н', 'О', 'П', '5' },
|
||||
{ 'р', 'с', 'т', 'у', 'Р', 'С', 'Т', 'У', '6' }, { 'ф', 'х', 'ц', 'ч', 'Ф', 'Х', 'Ц', 'Ч', '7' },
|
||||
{ 'ш', 'щ', 'ъ', 'Ш', 'Щ', 'Ъ', '8' }, { 'ь', 'ю', 'я', 'Ь', 'Ю', 'Я', '9' },
|
||||
{ ' ', '\n' }, { ' ', '0', '+' }, { '\n' } }; // LAST TWO SPACE ON 0
|
||||
|
||||
protected static final char[][][] T9TABLE = { ENT9TABLE, RUT9TABLE, DET9TABLE, FRT9TABLE, ITT9TABLE, UKT9TABLE, BGT9TABLE };
|
||||
|
||||
// last 2 don't matter, are for spaceOnZero extra 'slots' 0 position, and 10 position
|
||||
protected static final int[] ENT9CAPSTART = { 0, 0, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0 };
|
||||
protected static final int[] RUT9CAPSTART = { 0, 0, 4, 5, 4, 4, 4, 4, 4, 4, 0, 0, 0 };
|
||||
protected static final int[] DET9CAPSTART = { 0, 0, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0 };
|
||||
protected static final int[] FRT9CAPSTART = { 0, 0, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0 };
|
||||
protected static final int[] ITT9CAPSTART = { 0, 0, 3, 3, 3, 3, 3, 4, 3, 4, 0, 0, 0 };
|
||||
protected static final int[] UKT9CAPSTART = { 0, 0, 5, 5, 6, 4, 4, 4, 2, 3, 0, 0, 0 };
|
||||
protected static final int[] BGT9CAPSTART = { 0, 0, 4, 4, 4, 4, 4, 4, 3, 3, 0, 0, 0 };
|
||||
protected static final int[][] T9CAPSTART = {ENT9CAPSTART, RUT9CAPSTART, DET9CAPSTART, FRT9CAPSTART, ITT9CAPSTART, UKT9CAPSTART, BGT9CAPSTART};
|
||||
|
||||
public static String getStringSequence(String word, LANGUAGE lang) {
|
||||
StringBuilder seq = new StringBuilder();
|
||||
String tword = word.toLowerCase(LangHelper.LOCALES[lang.index]);
|
||||
for (int i = 0; i < word.length(); i++) {
|
||||
char c = tword.charAt(i);
|
||||
Integer z = CharMap.CHARTABLE.get(lang.index).get(c);
|
||||
if (z == null) {
|
||||
Log.e("getStringSequence",
|
||||
"ERROR: " + (int) c + " NOT FOUND FOR [" + lang.name() + "] (" + Integer.toHexString((int) c) + ") Index: " + i);
|
||||
throw new NullPointerException();
|
||||
}
|
||||
seq.append(z.toString());
|
||||
}
|
||||
return seq.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,127 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.ViewSwitcher;
|
||||
|
||||
public class InterfaceHandler implements View.OnClickListener, View.OnLongClickListener {
|
||||
|
||||
private static final int[] buttons = { R.id.main_left, R.id.main_right, R.id.main_mid };
|
||||
private TraditionalT9 parent;
|
||||
private View mainview;
|
||||
|
||||
public InterfaceHandler(View mainview, TraditionalT9 iparent) {
|
||||
this.parent = iparent;
|
||||
changeView(mainview);
|
||||
}
|
||||
|
||||
protected View getMainview() {
|
||||
return mainview;
|
||||
}
|
||||
|
||||
|
||||
protected void changeView(View v) {
|
||||
this.mainview = v;
|
||||
View button;
|
||||
for (int buttid : buttons) {
|
||||
button = v.findViewById(buttid);
|
||||
button.setOnClickListener(this);
|
||||
if (!parent.mAddingWord) {
|
||||
button.setOnLongClickListener(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void setPressed(int keyCode, boolean pressed) {
|
||||
int id = 0;
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_SOFT_LEFT:
|
||||
id = R.id.main_left;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_SOFT_RIGHT:
|
||||
id = R.id.main_right;
|
||||
break;
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER:
|
||||
id = R.id.main_mid;
|
||||
break;
|
||||
}
|
||||
if (id != 0) {
|
||||
((View) mainview.findViewById(id)).setPressed(pressed);
|
||||
}
|
||||
}
|
||||
|
||||
protected void showNotFound(boolean notfound) {
|
||||
if (notfound) {
|
||||
((TextView) mainview.findViewById(R.id.left_hold_upper))
|
||||
.setText(R.string.main_left_notfound);
|
||||
((TextView) mainview.findViewById(R.id.left_hold_lower))
|
||||
.setText(R.string.main_left_insert);
|
||||
} else {
|
||||
((TextView) mainview.findViewById(R.id.left_hold_upper))
|
||||
.setText(R.string.main_left_insert);
|
||||
((TextView) mainview.findViewById(R.id.left_hold_lower))
|
||||
.setText(R.string.main_left_addword);
|
||||
}
|
||||
}
|
||||
|
||||
protected void emulateMiddleButton() {
|
||||
((Button) mainview.findViewById(R.id.main_mid)).performClick();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.main_left:
|
||||
if (parent.mAddingWord) {
|
||||
parent.showSymbolPage();
|
||||
} else {
|
||||
if (parent.mWordFound) {
|
||||
parent.showSymbolPage();
|
||||
} else {
|
||||
parent.showAddWord();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case R.id.main_mid:
|
||||
parent.handleMidButton();
|
||||
break;
|
||||
case R.id.main_right:
|
||||
parent.nextKeyMode();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected void showHold(boolean show) {
|
||||
ViewSwitcher vs = (ViewSwitcher) mainview.findViewById(R.id.main_left);
|
||||
if (show) {
|
||||
vs.setDisplayedChild(1);
|
||||
} else {
|
||||
vs.setDisplayedChild(0);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
switch (v.getId()) {
|
||||
case R.id.main_left:
|
||||
parent.showAddWord();
|
||||
break;
|
||||
case R.id.main_right:
|
||||
parent.launchOptions();
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void hideView() {
|
||||
mainview.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
protected void showView() {
|
||||
mainview.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
||||
public class LangHelper {
|
||||
protected static final Locale BULGARIAN = new Locale("bg", "BG");
|
||||
protected static final Locale RUSSIAN = new Locale("ru","RU");
|
||||
protected static final Locale UKRAINIAN = new Locale("uk","UA");
|
||||
|
||||
|
||||
public enum LANGUAGE {
|
||||
// MAKE SURE THESE MATCH WITH values/const.xml
|
||||
// (index, id) Where index is index in arrays like LOCALES and MUST increment and MUST be in
|
||||
// the same order as arrays.xml/pref_lang_values, and id is the identifier used in
|
||||
// the database and such. id should never change unless database update is done.
|
||||
// id MUST increment in doubles (as the enabled languages are stored as an integer)
|
||||
NONE(-1, -1), EN(0,1), RU(1,2), DE(2,4), FR(3,8), IT(4,16), UK(5,32), BG(6, 64);
|
||||
public final int index;
|
||||
public final int id;
|
||||
// lookup map
|
||||
private static final Map<Integer, LANGUAGE> lookup = new HashMap<Integer, LANGUAGE>();
|
||||
private static final LANGUAGE[] ids = LANGUAGE.values();
|
||||
static { for (LANGUAGE l : ids) lookup.put(l.id, l); }
|
||||
|
||||
private LANGUAGE(int index, int id) { this.index = index; this.id = id; }
|
||||
|
||||
public static LANGUAGE get(int i) { return lookup.get(i);}
|
||||
}
|
||||
|
||||
public static final Locale[] LOCALES = {Locale.ENGLISH, RUSSIAN, Locale.GERMAN, Locale.FRENCH, Locale.ITALIAN, UKRAINIAN, BULGARIAN};
|
||||
|
||||
public static final int LANG_DEFAULT = LANGUAGE.EN.id;
|
||||
|
||||
protected static final int NLANGS = LANGUAGE.lookup.size();
|
||||
|
||||
protected static String getString(int lang) {
|
||||
return LANGUAGE.get(lang).name();
|
||||
}
|
||||
|
||||
protected static int getIndex(LANGUAGE l) {
|
||||
return l.index;
|
||||
}
|
||||
|
||||
//[LANG][MODE][CAPSMODE] = iconref
|
||||
// first group en, first line LANG, second line TEXT, last line NUM
|
||||
protected static final int[][][] ICONMAP = {
|
||||
{
|
||||
//English resources
|
||||
{R.drawable.ime_lang_en_lower, R.drawable.ime_lang_en_single, R.drawable.ime_lang_en_upper},
|
||||
{R.drawable.ime_lang_latin_lower, R.drawable.ime_lang_latin_single, R.drawable.ime_lang_latin_upper},
|
||||
{R.drawable.ime_number},
|
||||
},
|
||||
{
|
||||
// Russian resources
|
||||
{R.drawable.ime_lang_ru_lower, R.drawable.ime_lang_ru_single, R.drawable.ime_lang_ru_upper}, //LANG
|
||||
{R.drawable.ime_lang_cyrillic_lower, R.drawable.ime_lang_cyrillic_single, R.drawable.ime_lang_cyrillic_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
{
|
||||
// German resources
|
||||
{R.drawable.ime_lang_de_lower, R.drawable.ime_lang_de_single, R.drawable.ime_lang_de_upper}, //LANG
|
||||
{R.drawable.ime_lang_latin_lower, R.drawable.ime_lang_latin_single, R.drawable.ime_lang_latin_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
{
|
||||
// French resources
|
||||
{R.drawable.ime_lang_fr_lower, R.drawable.ime_lang_fr_single, R.drawable.ime_lang_fr_upper}, //LANG
|
||||
{R.drawable.ime_lang_latin_lower, R.drawable.ime_lang_latin_single, R.drawable.ime_lang_latin_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
{
|
||||
// Italian resources
|
||||
{R.drawable.ime_lang_it_lower, R.drawable.ime_lang_it_single, R.drawable.ime_lang_it_upper}, //LANG
|
||||
{R.drawable.ime_lang_latin_lower, R.drawable.ime_lang_latin_single, R.drawable.ime_lang_latin_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
{
|
||||
// Ukrainian resources
|
||||
{R.drawable.ime_lang_uk_lower, R.drawable.ime_lang_uk_single, R.drawable.ime_lang_uk_upper}, //LANG
|
||||
{R.drawable.ime_lang_cyrillic_lower, R.drawable.ime_lang_cyrillic_single, R.drawable.ime_lang_cyrillic_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
{
|
||||
// Bulgarian resources
|
||||
{R.drawable.ime_lang_bg_lower, R.drawable.ime_lang_bg_single, R.drawable.ime_lang_bg_upper}, //LANG
|
||||
{R.drawable.ime_lang_cyrillic_lower, R.drawable.ime_lang_cyrillic_single, R.drawable.ime_lang_cyrillic_upper}, //TEXT
|
||||
{R.drawable.ime_number}, //NUM
|
||||
},
|
||||
};
|
||||
|
||||
public static LANGUAGE[] buildLangs(int i) {
|
||||
int num = 0;
|
||||
//calc size of filtered array
|
||||
for (LANGUAGE l : LANGUAGE.ids) {
|
||||
if ((i & l.id) == l.id) {
|
||||
num++;
|
||||
}
|
||||
}
|
||||
LANGUAGE[] la = new LANGUAGE[num];
|
||||
int lai = 0;
|
||||
for (LANGUAGE l : LANGUAGE.ids) {
|
||||
if ((i & l.id) == l.id) {
|
||||
la[lai] = l;
|
||||
lai++;
|
||||
}
|
||||
}
|
||||
return la;
|
||||
}
|
||||
|
||||
public static int shrinkLangs(LANGUAGE[] langs) {
|
||||
int i = 0;
|
||||
for (LANGUAGE l : langs)
|
||||
i = i | l.id;
|
||||
return i;
|
||||
}
|
||||
public static int shrinkLangs(int[] langs) {
|
||||
int i = 0;
|
||||
for (int l : langs)
|
||||
i = i | l;
|
||||
return i;
|
||||
}
|
||||
|
||||
protected static int findIndex(LANGUAGE[] ia, LANGUAGE target) {
|
||||
for (int x=0; x<ia.length; x++) {
|
||||
if (ia[x] == target)
|
||||
return x;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
37
src/io/github/sspanak/tt9/Logger.java
Normal file
37
src/io/github/sspanak/tt9/Logger.java
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class Logger {
|
||||
public static int LEVEL = Log.ERROR;
|
||||
|
||||
static public void v(String tag, String msg) {
|
||||
if (LEVEL <= Log.VERBOSE) {
|
||||
Log.v(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static public void d(String tag, String msg) {
|
||||
if (LEVEL <= Log.DEBUG) {
|
||||
Log.d(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static public void i(String tag, String msg) {
|
||||
if (LEVEL <= Log.INFO) {
|
||||
Log.i(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static public void w(String tag, String msg) {
|
||||
if (LEVEL <= Log.WARN) {
|
||||
Log.w(tag, msg);
|
||||
}
|
||||
}
|
||||
|
||||
static public void e(String tag, String msg) {
|
||||
if (LEVEL <= Log.ERROR) {
|
||||
Log.e(tag, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
public class SmileyDialog extends AbsSymDialog {
|
||||
|
||||
private static final String[] symbols = {
|
||||
// lol wiki http://en.wikipedia.org/wiki/List_of_emoticons
|
||||
":-)", ":o)", ":]", ":3", ":c)", ":>", "=]", "=)", ":}", ":-D",
|
||||
"8-D", "X-D", "=-D", "B^D", "<:-)", ">:-[", ":-(", ":-<", ":o(", ":{",
|
||||
":'-(", ":'-)", ":@", "D:<", "D8", "v.v","D-':", ">:O", ":-O", "o_0",
|
||||
":*", ";-)", ";-D", ">:-P", ":-P", "X-P", "=p", ">:-/", ":-/", ":-.",
|
||||
":S", ">.<", ":-|", ":$", ":-X", ":-#", ":-%", ":С", ":-E", ":-*",
|
||||
"0:-3", "0:-)", ">;-)", ">:-)", ">_>", "*<|:-)", "\\o/", "<3", "</3", "=-3", };
|
||||
|
||||
private static final int MAX_PAGE = (int) Math.ceil(symbols.length / 10.0);
|
||||
|
||||
public SmileyDialog(Context c, View mv) {
|
||||
super(c, mv);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getContentDescription() {
|
||||
return context.getResources().getStringArray(R.array.smileyContentDescription);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSymbol(int index) {
|
||||
return symbols[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitleText() {
|
||||
return context.getString(R.string.smiley_insert);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSymbolSize() {
|
||||
return symbols.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaxPage() {
|
||||
return MAX_PAGE;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
public class SymbolDialog extends AbsSymDialog {
|
||||
|
||||
private static final char[] symbols = {
|
||||
'.', ',', '!', '?', '$', '&', '%', '#', '@', '"', '\'', ':', ';', '(', ')', '/', '\\',
|
||||
'-', '+', '=', '*', '<', '>', '[', ']', '{', '}', '^', '|', '_', '~', '`' }; // 32
|
||||
private static final int MAX_PAGE = (int) Math.ceil(symbols.length / 10.0);
|
||||
|
||||
public SymbolDialog(Context c, View mv) {
|
||||
super(c, mv);
|
||||
}
|
||||
|
||||
@Override
|
||||
String[] getContentDescription() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getSymbol(int index) {
|
||||
return String.valueOf(symbols[index]);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTitleText() {
|
||||
return context.getString(R.string.symbol_insert);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getSymbolSize() {
|
||||
return symbols.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getMaxPage() {
|
||||
return MAX_PAGE;
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,77 +0,0 @@
|
|||
package io.github.sspanak.tt9;
|
||||
|
||||
import android.text.InputType;
|
||||
import android.util.Log;
|
||||
|
||||
class Utils {
|
||||
interface SpecialInputType extends InputType {
|
||||
public static final int TYPE_SHARP_007H_PHONE_BOOK = 65633;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public static void printFlags(int inputType) {
|
||||
if ((inputType & InputType.TYPE_CLASS_DATETIME) == InputType.TYPE_CLASS_DATETIME)
|
||||
Log.i("Utils.printFlags", "TYPE_CLASS_DATETIME");
|
||||
if ((inputType & InputType.TYPE_CLASS_NUMBER) == InputType.TYPE_CLASS_NUMBER)
|
||||
Log.i("Utils.printFlags", "TYPE_CLASS_NUMBER");
|
||||
if ((inputType & InputType.TYPE_CLASS_PHONE) == InputType.TYPE_CLASS_PHONE)
|
||||
Log.i("Utils.printFlags", "TYPE_CLASS_PHONE");
|
||||
if ((inputType & InputType.TYPE_CLASS_TEXT) == InputType.TYPE_CLASS_TEXT)
|
||||
Log.i("Utils.printFlags", "TYPE_CLASS_TEXT");
|
||||
if ((inputType & InputType.TYPE_DATETIME_VARIATION_DATE) == InputType.TYPE_DATETIME_VARIATION_DATE)
|
||||
Log.i("Utils.printFlags", "TYPE_DATETIME_VARIATION_DATE");
|
||||
if ((inputType & InputType.TYPE_DATETIME_VARIATION_NORMAL) == InputType.TYPE_DATETIME_VARIATION_NORMAL)
|
||||
Log.i("Utils.printFlags", "TYPE_DATETIME_VARIATION_NORMAL");
|
||||
if ((inputType & InputType.TYPE_DATETIME_VARIATION_TIME) == InputType.TYPE_DATETIME_VARIATION_TIME)
|
||||
Log.i("Utils.printFlags", "YPE_DATETIME_VARIATION_TIME");
|
||||
if ((inputType & InputType.TYPE_NULL) == InputType.TYPE_NULL)
|
||||
Log.i("Utils.printFlags", "TYPE_NULL");
|
||||
if ((inputType & InputType.TYPE_NUMBER_FLAG_DECIMAL) == InputType.TYPE_NUMBER_FLAG_DECIMAL)
|
||||
Log.i("Utils.printFlags", "TYPE_NUMBER_FLAG_DECIMAL");
|
||||
if ((inputType & InputType.TYPE_NUMBER_FLAG_SIGNED) == InputType.TYPE_NUMBER_FLAG_SIGNED)
|
||||
Log.i("Utils.printFlags", "TYPE_NUMBER_FLAG_SIGNED");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE) == InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_AUTO_COMPLETE");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_AUTO_CORRECT) == InputType.TYPE_TEXT_FLAG_AUTO_CORRECT)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_AUTO_CORRECT");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS) == InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_CAP_CHARACTERS");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) == InputType.TYPE_TEXT_FLAG_CAP_SENTENCES)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_CAP_SENTENCES");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_CAP_WORDS) == InputType.TYPE_TEXT_FLAG_CAP_WORDS)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_CAP_WORDS");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE) == InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_IME_MULTI_LINE");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_MULTI_LINE) == InputType.TYPE_TEXT_FLAG_MULTI_LINE)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_MULTI_LINE");
|
||||
if ((inputType & InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS) == InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_FLAG_NO_SUGGESTIONS");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS) == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_EMAIL_ADDRESS");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT) == InputType.TYPE_TEXT_VARIATION_EMAIL_SUBJECT)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_EMAIL_SUBJECT");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_FILTER) == InputType.TYPE_TEXT_VARIATION_FILTER)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_FILTER");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE) == InputType.TYPE_TEXT_VARIATION_LONG_MESSAGE)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_LONG_MESSAGE");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_NORMAL) == InputType.TYPE_TEXT_VARIATION_NORMAL)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_NORMAL");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_PASSWORD) == InputType.TYPE_TEXT_VARIATION_PASSWORD)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_PASSWORD");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_PERSON_NAME) == InputType.TYPE_TEXT_VARIATION_PERSON_NAME)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_PERSON_NAME");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_PHONETIC) == InputType.TYPE_TEXT_VARIATION_PHONETIC)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_PHONETIC");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS) == InputType.TYPE_TEXT_VARIATION_POSTAL_ADDRESS)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_POSTAL_ADDRESS");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE) == InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_SHORT_MESSAGE");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_URI) == InputType.TYPE_TEXT_VARIATION_URI)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_URI");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD) == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_VISIBLE_PASSWORD");
|
||||
if ((inputType & InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT) == InputType.TYPE_TEXT_VARIATION_WEB_EDIT_TEXT)
|
||||
Log.i("Utils.printFlags", "TYPE_TEXT_VARIATION_WEB_EDIT_TEXT");
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
public class DBException extends Exception {
|
||||
private static final long serialVersionUID = 376752656441823823L;
|
||||
|
||||
protected DBException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,82 +0,0 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import android.app.IntentService;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
|
||||
public class DBUpdateService extends IntentService {
|
||||
private static final int UPDATING_NOTIFICATION_ID = 9640142;
|
||||
|
||||
private Handler mHandler;
|
||||
|
||||
// http://stackoverflow.com/a/20690225
|
||||
public class DisplayToast implements Runnable {
|
||||
private final Context mContext;
|
||||
CharSequence mText;
|
||||
|
||||
public DisplayToast(Context mContext, CharSequence text){
|
||||
this.mContext = mContext;
|
||||
mText = text;
|
||||
}
|
||||
|
||||
public void run(){
|
||||
Toast.makeText(mContext, mText, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public DBUpdateService() {
|
||||
super("DBUpdateService");
|
||||
mHandler = new Handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* The IntentService calls this method from the default worker thread with
|
||||
* the intent that started the service. When this method returns, IntentService
|
||||
* stops the service, as appropriate.
|
||||
*/
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
// do things
|
||||
T9DB dbhelper = T9DB.getInstance(this);
|
||||
if (dbhelper.getSQLDB(this) != null) {
|
||||
return;
|
||||
}
|
||||
Log.d("T9DBUpdate.onHandle", "Update pass check.");
|
||||
|
||||
// do real things
|
||||
Intent notificationIntent = new Intent(this, DBUpdateService.class);
|
||||
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
|
||||
|
||||
NotificationManager notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
|
||||
Notification.Builder nBuilder = new Notification.Builder(this);
|
||||
Notification notification = nBuilder
|
||||
.setContentIntent(pendingIntent)
|
||||
.setContentTitle(getText(R.string.updating_database_title))
|
||||
.setContentText(getText(R.string.updating_database))
|
||||
.setSmallIcon(R.drawable.ime_lang_en_lower)
|
||||
.getNotification();
|
||||
|
||||
notificationManager.notify(UPDATING_NOTIFICATION_ID, notification);
|
||||
|
||||
startForeground(UPDATING_NOTIFICATION_ID, notification);
|
||||
|
||||
//put this in a thread
|
||||
mHandler.post(new DisplayToast(this, getText(R.string.updating_database)));
|
||||
|
||||
SQLiteDatabase tdb = dbhelper.getWritableDatabase();
|
||||
dbhelper.setSQLDB(tdb);
|
||||
mHandler.post(new DisplayToast(this, getText(R.string.updating_database_done)));
|
||||
Log.d("T9DBUpdate.onHandle", "done.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,56 +0,0 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.database.sqlite.SQLiteOpenHelper;
|
||||
import android.util.Log;
|
||||
|
||||
public class DatabaseHelper extends SQLiteOpenHelper {
|
||||
Context mContext = null;
|
||||
|
||||
DatabaseHelper(Context context) {
|
||||
super(context, T9DB.DATABASE_NAME, null, T9DB.DATABASE_VERSION);
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
// partial code from parent class SQLiteOpenHelper
|
||||
protected boolean needsUpgrading() {
|
||||
//quick and dirty check to see if an existing database exists.
|
||||
if (mContext.databaseList().length > 0) {
|
||||
SQLiteDatabase db = mContext.openOrCreateDatabase(T9DB.DATABASE_NAME, 0, null);
|
||||
int version = db.getVersion();
|
||||
db.close();
|
||||
return version < T9DB.DATABASE_VERSION;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS " + T9DB.WORD_TABLE_NAME + " (" +
|
||||
T9DB.COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
|
||||
T9DB.COLUMN_LANG + " INTEGER, " +
|
||||
T9DB.COLUMN_SEQ + " TEXT, " +
|
||||
T9DB.COLUMN_WORD + " TEXT, " +
|
||||
T9DB.COLUMN_FREQUENCY + " INTEGER, " +
|
||||
"UNIQUE(" + T9DB.COLUMN_LANG + ", " + T9DB.COLUMN_WORD + ") )");
|
||||
db.execSQL("CREATE INDEX IF NOT EXISTS idx ON " + T9DB.WORD_TABLE_NAME + "("
|
||||
+ T9DB.COLUMN_LANG + ", " + T9DB.COLUMN_SEQ + " ASC, " + T9DB.COLUMN_FREQUENCY + " DESC )");
|
||||
db.execSQL("CREATE TRIGGER IF NOT EXISTS " + T9DB.FREQ_TRIGGER_NAME +
|
||||
" AFTER UPDATE ON " + T9DB.WORD_TABLE_NAME +
|
||||
" WHEN NEW." + T9DB.COLUMN_FREQUENCY + " > " + T9DB.FREQ_MAX +
|
||||
" BEGIN" +
|
||||
" UPDATE " + T9DB.WORD_TABLE_NAME + " SET " + T9DB.COLUMN_FREQUENCY + " = "
|
||||
+ T9DB.COLUMN_FREQUENCY + " / " + T9DB.FREQ_DIV +
|
||||
" WHERE " + T9DB.COLUMN_SEQ + " = NEW." + T9DB.COLUMN_SEQ + ";" +
|
||||
" END;");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
Log.i("T9DB.onUpgrade", "Upgrading database from version " + oldVersion + " to " + newVersion);
|
||||
onCreate(db);
|
||||
// subsequent database migrations go here
|
||||
Log.i("T9DB.onUpgrade", "Done.");
|
||||
}
|
||||
}
|
||||
186
src/io/github/sspanak/tt9/db/DictionaryDb.java
Normal file
186
src/io/github/sspanak/tt9/db/DictionaryDb.java
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.room.Room;
|
||||
import androidx.room.RoomDatabase;
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
|
||||
import java.io.NotActiveException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||
|
||||
public class DictionaryDb {
|
||||
private static T9RoomDb dbInstance;
|
||||
|
||||
private static final RoomDatabase.Callback TRIGGER_CALLBACK = new RoomDatabase.Callback() {
|
||||
@Override
|
||||
public void onCreate(@NonNull SupportSQLiteDatabase db) {
|
||||
super.onCreate(db);
|
||||
db.execSQL(
|
||||
"CREATE TRIGGER IF NOT EXISTS normalize_freq " +
|
||||
" AFTER UPDATE ON words " +
|
||||
" WHEN NEW.freq > 50000 " +
|
||||
" BEGIN" +
|
||||
" UPDATE words SET freq = freq / 10000 " +
|
||||
" WHERE seq = NEW.seq; " +
|
||||
"END;"
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(@NonNull SupportSQLiteDatabase db) {
|
||||
super.onOpen(db);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
private static synchronized void createInstance(Context context) {
|
||||
dbInstance = Room.databaseBuilder(context, T9RoomDb.class, "t9dict.db")
|
||||
.addCallback(TRIGGER_CALLBACK)
|
||||
.build();
|
||||
}
|
||||
|
||||
|
||||
public static T9RoomDb getInstance(Context context) {
|
||||
if (dbInstance == null) {
|
||||
createInstance(context);
|
||||
}
|
||||
|
||||
return dbInstance;
|
||||
}
|
||||
|
||||
|
||||
public static void beginTransaction(Context context) {
|
||||
getInstance(context).beginTransaction();
|
||||
}
|
||||
|
||||
|
||||
public static void endTransaction(Context context, boolean success) {
|
||||
if (success) {
|
||||
getInstance(context).setTransactionSuccessful();
|
||||
}
|
||||
getInstance(context).endTransaction();
|
||||
}
|
||||
|
||||
|
||||
private static void sendSuggestions(Handler handler, ArrayList<String> data) {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putStringArrayList("suggestions", data);
|
||||
Message msg = new Message();
|
||||
msg.setData(bundle);
|
||||
handler.sendMessage(msg);
|
||||
}
|
||||
|
||||
|
||||
public static void truncateWords(Context context, Handler handler) {
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
getInstance(context).clearAllTables();
|
||||
handler.sendEmptyMessage(0);
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
public static void insertWord(Context context, String word, int languageId) throws InvalidLanguageException, InsertBlankWordException, NotActiveException {
|
||||
if (languageId <= 0) {
|
||||
throw new InvalidLanguageException("Cannot insert a word for an invalid language with ID: '" + languageId + "'");
|
||||
}
|
||||
|
||||
if (word == null || word.length() == 0) {
|
||||
throw new InsertBlankWordException();
|
||||
}
|
||||
|
||||
// @todo: insert async with max priority for this sequence.
|
||||
throw new NotActiveException("Adding new words is disabled in this version. Please, check for updates.");
|
||||
}
|
||||
|
||||
|
||||
public static void insertWordsSync(Context context, List<Word> words) {
|
||||
getInstance(context).wordsDao().insertWords(words);
|
||||
}
|
||||
|
||||
|
||||
public static void incrementWordFrequency(Context context, int langId, String word, String sequence) throws Exception {
|
||||
if (langId <= 0) {
|
||||
throw new InvalidLanguageException("Cannot increment word frequency for an invalid language: '" + langId + "'");
|
||||
}
|
||||
|
||||
// If both are empty, it is the same as changing the frequency of: "", which is simply a no-op.
|
||||
if ((word == null || word.length() == 0) && (sequence == null || sequence.length() == 0)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If one of them is empty, then this is an invalid operation,
|
||||
// because a digit sequence exist for every word.
|
||||
if (word == null || word.length() == 0 || sequence == null || sequence.length() == 0) {
|
||||
throw new Exception("Cannot increment word frequency. Word: '" + word + "', Sequence: '" + sequence + "'");
|
||||
}
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
getInstance(context).wordsDao().incrementFrequency(langId, word, sequence);
|
||||
} catch (Exception e) {
|
||||
Logger.e(
|
||||
DictionaryDb.class.getName(),
|
||||
"Failed incrementing word frequency. Word: '" + word + "', Sequence: '" + sequence + "'. " + e.getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
|
||||
|
||||
public static void getSuggestions(Context context, Handler handler, int langId, String sequence, int minimumWords, int maximumWords) {
|
||||
final int minWords = Math.max(minimumWords, 0);
|
||||
final int maxWords = Math.max(maximumWords, minimumWords);
|
||||
|
||||
new Thread() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (sequence == null || sequence.length() == 0) {
|
||||
sendSuggestions(handler, new ArrayList<>());
|
||||
return;
|
||||
}
|
||||
|
||||
// get exact sequence matches, for example: "9422" -> "what"
|
||||
List<Word> exactMatches = getInstance(context).wordsDao().getMany(langId, sequence, maxWords);
|
||||
Logger.d("getWords", "Exact matches: " + exactMatches.size());
|
||||
|
||||
ArrayList<String> suggestions = new ArrayList<>();
|
||||
for (Word word : exactMatches) {
|
||||
Logger.d("getWords", "exact match: " + word.word + ", priority: " + word.frequency);
|
||||
suggestions.add(word.word);
|
||||
}
|
||||
|
||||
// if the exact matches are too few, add some more words that start with the same characters,
|
||||
// for example: "rol" => "roll", "roller", "rolling", ...
|
||||
if (exactMatches.size() < minWords && sequence.length() >= 2) {
|
||||
int extraWordsNeeded = minWords - exactMatches.size();
|
||||
List<Word> extraWords = getInstance(context).wordsDao().getFuzzy(langId, sequence, extraWordsNeeded);
|
||||
Logger.d("getWords", "Fuzzy matches: " + extraWords.size());
|
||||
|
||||
for (Word word : extraWords) {
|
||||
Logger.d("getWords", "fuzzy match: " + word.word + ", sequence: " + word.sequence);
|
||||
suggestions.add(word.word);
|
||||
}
|
||||
}
|
||||
|
||||
// pack the words in a message and send it to the calling thread
|
||||
sendSuggestions(handler, suggestions);
|
||||
}
|
||||
}.start();
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
public class InsertBlankWordException extends Exception {
|
||||
protected InsertBlankWordException() {
|
||||
super("Cannot insert a blank word.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,299 +0,0 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteConstraintException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.provider.BaseColumns;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import io.github.sspanak.tt9.CharMap;
|
||||
import io.github.sspanak.tt9.LangHelper;
|
||||
import io.github.sspanak.tt9.LangHelper.LANGUAGE;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
public class T9DB {
|
||||
|
||||
private static volatile T9DB instance = null;
|
||||
protected boolean ready = true;
|
||||
|
||||
public static final String DATABASE_NAME = "t9dict.db";
|
||||
public static final int DATABASE_VERSION = 5; // Versions < 5 belong to the original project. We don't care about
|
||||
// them and we don't migrate them, because the APP ID used to be
|
||||
// different. This means the TT9 must be installed as a new application
|
||||
// since version 5, which eliminates the possibility of reusing any
|
||||
// legacy data.
|
||||
public static final String WORD_TABLE_NAME = "word";
|
||||
public static final String FREQ_TRIGGER_NAME = "freqtrigger";
|
||||
// 50k, 10k
|
||||
public static final int FREQ_MAX = 50000;
|
||||
public static final int FREQ_DIV = 10000;
|
||||
// This seems to be pretty fast on my phone. 10 is pretty slow (Might be because > MAX_RESULTS (8).)
|
||||
private static final int MINHITS = 4;
|
||||
|
||||
public static final String COLUMN_ID = BaseColumns._ID;
|
||||
public static final String COLUMN_LANG = "lang";
|
||||
public static final String COLUMN_SEQ = "seq";
|
||||
public static final String COLUMN_WORD = "word";
|
||||
public static final String COLUMN_FREQUENCY = "freq";
|
||||
|
||||
private static final String QUERY1 =
|
||||
"SELECT " + COLUMN_ID + ", " + COLUMN_WORD +
|
||||
" FROM " + WORD_TABLE_NAME +
|
||||
" WHERE " + COLUMN_LANG + "=? AND " + COLUMN_SEQ + "=?" +
|
||||
" ORDER BY " + COLUMN_FREQUENCY + " DESC";
|
||||
|
||||
private static final String UPDATEQ =
|
||||
"UPDATE " + WORD_TABLE_NAME +
|
||||
" SET " + COLUMN_FREQUENCY + " = " + COLUMN_FREQUENCY + "+1" +
|
||||
" WHERE " + COLUMN_ID + "=";
|
||||
|
||||
private static final int MAX_RESULTS = 8;
|
||||
private static final int MAX_MAX_RESULTS = 30; // to make sure we don't exceed candidate view array.
|
||||
|
||||
private DatabaseHelper mOpenHelper;
|
||||
private SQLiteDatabase db;
|
||||
|
||||
private Context mContext;
|
||||
|
||||
public T9DB(Context caller) {
|
||||
// create db
|
||||
mContext = caller;
|
||||
mOpenHelper = new DatabaseHelper(caller);
|
||||
}
|
||||
|
||||
public static T9DB getInstance(Context caller) {
|
||||
if (instance == null) {
|
||||
synchronized (T9DB.class){
|
||||
if (instance == null) {
|
||||
instance = new T9DB (caller);
|
||||
instance.init();
|
||||
}
|
||||
}
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
public static SQLiteDatabase getSQLDB(Context caller) {
|
||||
T9DB t9dbhelper = getInstance(caller);
|
||||
//Log.d("T9DB.getSQLDB", "db:" + t9dbhelper.db.isOpen());
|
||||
return t9dbhelper.db;
|
||||
}
|
||||
|
||||
private void init() {
|
||||
if (mOpenHelper.needsUpgrading() ) {
|
||||
ready = false;
|
||||
Log.i("T9.init", "needsUpgrading");
|
||||
// start updating service
|
||||
if (db != null) {
|
||||
try {
|
||||
db.close();
|
||||
} catch (NullPointerException ignored) { }
|
||||
db = null;
|
||||
}
|
||||
Intent intent = new Intent(mContext, DBUpdateService.class);
|
||||
Log.i("T9.init", "Invoking update service...");
|
||||
mContext.startService(intent);
|
||||
} else {
|
||||
db = mOpenHelper.getWritableDatabase();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean ensureDb() {
|
||||
if (ready) {
|
||||
if (db == null) {
|
||||
db = getWritableDatabase();
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected SQLiteDatabase getWritableDatabase() {
|
||||
return mOpenHelper.getWritableDatabase();
|
||||
}
|
||||
|
||||
protected void setSQLDB(SQLiteDatabase tdb) {
|
||||
synchronized (T9DB.class){
|
||||
db = tdb;
|
||||
ready = true;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isReady() {
|
||||
return this.ready;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try { db.close(); }
|
||||
catch (NullPointerException ignored) { }
|
||||
db = null;
|
||||
}
|
||||
|
||||
public void truncate() {
|
||||
Log.i("T9DB.truncate", "Truncating words table...");
|
||||
synchronized (T9DB.class) {
|
||||
ready = false;
|
||||
db = getWritableDatabase();
|
||||
db.delete(WORD_TABLE_NAME, null, null);
|
||||
ready = true;
|
||||
}
|
||||
Log.i("T9DB.truncate", "Done...");
|
||||
}
|
||||
|
||||
public void showDBaccessError() {
|
||||
Toast.makeText(mContext, R.string.database_notready, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public void addWord(String iword, LANGUAGE lang) throws DBException {
|
||||
Resources r = mContext.getResources();
|
||||
if (iword.equals("")) {
|
||||
throw new DBException(r.getString(R.string.add_word_blank));
|
||||
}
|
||||
// get int sequence
|
||||
String seq;
|
||||
try {
|
||||
seq = CharMap.getStringSequence(iword, lang);
|
||||
} catch (NullPointerException e) {
|
||||
throw new DBException(r.getString(R.string.add_word_badchar, lang.name(), iword));
|
||||
}
|
||||
// add int sequence into num table
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(COLUMN_SEQ, seq);
|
||||
values.put(COLUMN_LANG, lang.id);
|
||||
// add word into word
|
||||
values.put(COLUMN_WORD, iword);
|
||||
values.put(COLUMN_FREQUENCY, 1);
|
||||
if (!ensureDb()) {
|
||||
Log.e("T9DB.addWord", "not ready");
|
||||
this.showDBaccessError();
|
||||
return;
|
||||
}
|
||||
try {
|
||||
db.insertOrThrow(WORD_TABLE_NAME, null, values);
|
||||
} catch (SQLiteConstraintException e) {
|
||||
String msg = r.getString(R.string.add_word_exist2, iword, lang.name());
|
||||
Log.w("T9DB.addWord", msg);
|
||||
throw new DBException(msg);
|
||||
}
|
||||
}
|
||||
|
||||
public void incrementWord(int id) {
|
||||
if (!ensureDb()) {
|
||||
Log.e("T9DB.incrementWord", "not ready");
|
||||
this.showDBaccessError();
|
||||
return;
|
||||
}
|
||||
db.execSQL(UPDATEQ + id);
|
||||
// if id's freq is greater than FREQ_MAX, it gets normalized with trigger
|
||||
}
|
||||
|
||||
public void updateWords(String is, AbstractList<String> stringList, List<Integer> intList,
|
||||
int capsMode, LANGUAGE lang) {
|
||||
stringList.clear();
|
||||
intList.clear();
|
||||
// String[] sa = packInts(stringToInts(is), true);
|
||||
int islen = is.length();
|
||||
|
||||
if (!ensureDb()) {
|
||||
Log.e("T9DB.updateWords", "not ready");
|
||||
this.showDBaccessError();
|
||||
return;
|
||||
}
|
||||
Cursor cur = db.rawQuery(QUERY1, new String[] { String.valueOf(lang.id), is });
|
||||
|
||||
int hits = 0;
|
||||
for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {
|
||||
intList.add(cur.getInt(0));
|
||||
stringList.add(cur.getString(1));
|
||||
if (hits >= MAX_MAX_RESULTS) { break; } // to stop index error in candidate view
|
||||
hits++;
|
||||
}
|
||||
cur.close();
|
||||
|
||||
if ((hits < MINHITS) && (islen >= 2)) {
|
||||
char c = is.charAt(islen - 1);
|
||||
c++;
|
||||
String q = "SELECT " + COLUMN_ID + ", " + COLUMN_WORD +
|
||||
" FROM " + WORD_TABLE_NAME +
|
||||
" WHERE " + COLUMN_LANG + "=? AND " + COLUMN_SEQ + " >= '" + is + "1" +
|
||||
"' AND " + COLUMN_SEQ + " < '" + is.substring(0, islen - 1) + c + "'" +
|
||||
" ORDER BY " + COLUMN_FREQUENCY + " DESC, " + COLUMN_SEQ + " ASC" +
|
||||
" LIMIT " + (MAX_RESULTS - hits);
|
||||
cur = db.rawQuery(q, new String[] { String.valueOf(lang.id) });
|
||||
|
||||
for (cur.moveToFirst(); !cur.isAfterLast(); cur.moveToNext()) {
|
||||
intList.add(cur.getInt(0));
|
||||
stringList.add(cur.getString(1));
|
||||
if (hits >= MAX_MAX_RESULTS) {
|
||||
break;
|
||||
}
|
||||
hits++;
|
||||
}
|
||||
cur.close();
|
||||
}
|
||||
// Log.d("T9DB.updateWords", "pre: " + stringList);
|
||||
if (capsMode == T9Preferences.CASE_LOWER) {
|
||||
return;
|
||||
}
|
||||
// Log.d("T9DB.updateWords", "filtering...");
|
||||
// filter list
|
||||
Iterator<String> iter = stringList.iterator();
|
||||
String word;
|
||||
String wordtemp;
|
||||
int index = 0;
|
||||
boolean removed = false;
|
||||
while (iter.hasNext()) {
|
||||
word = iter.next();
|
||||
switch (capsMode) {
|
||||
case T9Preferences.CASE_UPPER:
|
||||
wordtemp = word.toUpperCase(LangHelper.LOCALES[lang.index]);
|
||||
if (wordtemp.equals(word)) {
|
||||
index++;
|
||||
continue;
|
||||
} else if (stringList.contains(wordtemp)) {
|
||||
// remove this entry
|
||||
iter.remove();
|
||||
removed = true;
|
||||
} else {
|
||||
stringList.set(index, wordtemp);
|
||||
}
|
||||
break;
|
||||
case T9Preferences.CASE_CAPITALIZE:
|
||||
if (word.length() > 1) {
|
||||
wordtemp = word.substring(0, 1).toUpperCase(LangHelper.LOCALES[lang.index]) + word.substring(1);
|
||||
} else {
|
||||
wordtemp = word.toUpperCase(LangHelper.LOCALES[lang.index]);
|
||||
}
|
||||
if (wordtemp.equals(word)) {
|
||||
index++;
|
||||
continue;
|
||||
} else if (stringList.contains(wordtemp)) {
|
||||
// remove this entry
|
||||
iter.remove();
|
||||
removed = true;
|
||||
} else {
|
||||
stringList.set(index, wordtemp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (removed) {
|
||||
intList.remove(index);
|
||||
removed = false;
|
||||
} else {
|
||||
index++;
|
||||
}
|
||||
}
|
||||
//Log.d("T9DB.updateWords", "i:" + is + " words:" + Arrays.toString(stringList.toArray()));
|
||||
}
|
||||
}
|
||||
9
src/io/github/sspanak/tt9/db/T9RoomDb.java
Normal file
9
src/io/github/sspanak/tt9/db/T9RoomDb.java
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import androidx.room.Database;
|
||||
import androidx.room.RoomDatabase;
|
||||
|
||||
@Database(version = 5, entities = Word.class, exportSchema = false)
|
||||
abstract class T9RoomDb extends RoomDatabase {
|
||||
public abstract WordsDao wordsDao();
|
||||
}
|
||||
29
src/io/github/sspanak/tt9/db/Word.java
Normal file
29
src/io/github/sspanak/tt9/db/Word.java
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import androidx.room.ColumnInfo;
|
||||
import androidx.room.Entity;
|
||||
import androidx.room.Index;
|
||||
import androidx.room.PrimaryKey;
|
||||
|
||||
@Entity(
|
||||
indices = {
|
||||
@Index(value = {"lang", "word"}, unique = true),
|
||||
@Index(value = {"lang", "seq", "freq"})
|
||||
},
|
||||
tableName = "words"
|
||||
)
|
||||
public class Word {
|
||||
@PrimaryKey(autoGenerate = true)
|
||||
public int id;
|
||||
|
||||
@ColumnInfo(name = "lang")
|
||||
public int langId;
|
||||
|
||||
public String word;
|
||||
|
||||
@ColumnInfo(name = "seq")
|
||||
public String sequence;
|
||||
|
||||
@ColumnInfo(name = "freq")
|
||||
public int frequency;
|
||||
}
|
||||
39
src/io/github/sspanak/tt9/db/WordsDao.java
Normal file
39
src/io/github/sspanak/tt9/db/WordsDao.java
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package io.github.sspanak.tt9.db;
|
||||
|
||||
import androidx.room.Dao;
|
||||
import androidx.room.Insert;
|
||||
import androidx.room.OnConflictStrategy;
|
||||
import androidx.room.Query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Dao
|
||||
interface WordsDao {
|
||||
@Query(
|
||||
"SELECT * " +
|
||||
"FROM words " +
|
||||
"WHERE lang = :langId AND seq = :sequence " +
|
||||
"ORDER BY freq DESC " +
|
||||
"LIMIT :limit"
|
||||
)
|
||||
List<Word> getMany(int langId, String sequence, int limit);
|
||||
|
||||
@Query(
|
||||
"SELECT * " +
|
||||
"FROM words " +
|
||||
"WHERE lang = :langId AND seq > :sequence AND seq <= :sequence || '99' " +
|
||||
"ORDER BY freq DESC, seq ASC " +
|
||||
"LIMIT :limit"
|
||||
)
|
||||
List<Word> getFuzzy(int langId, String sequence, int limit);
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
void insertWords(List<Word> words);
|
||||
|
||||
@Query(
|
||||
"UPDATE words " +
|
||||
"SET freq = (SELECT IFNULL(MAX(freq), 0) FROM words WHERE lang = :langId AND seq = :sequence AND word <> :word) + 1 " +
|
||||
"WHERE lang = :langId AND word = :word "
|
||||
)
|
||||
void incrementFrequency(int langId, String word, String sequence);
|
||||
}
|
||||
179
src/io/github/sspanak/tt9/ime/InputFieldHelper.java
Normal file
179
src/io/github/sspanak/tt9/ime/InputFieldHelper.java
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import android.text.InputType;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.ExtractedText;
|
||||
import android.view.inputmethod.ExtractedTextRequest;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
|
||||
class InputFieldHelper {
|
||||
public static boolean isThereText(InputConnection currentInputConnection) {
|
||||
if (currentInputConnection == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ExtractedText extractedText = currentInputConnection.getExtractedText(new ExtractedTextRequest(), 0);
|
||||
return extractedText != null && extractedText.text.length() > 0;
|
||||
}
|
||||
|
||||
|
||||
public static boolean isSpecializedTextField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int variation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return (
|
||||
variation == InputType.TYPE_TEXT_VARIATION_PASSWORD
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_URI
|
||||
|| variation == InputType.TYPE_TEXT_VARIATION_FILTER
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* isFilterTextField
|
||||
* handle filter list cases... do not hijack DPAD center and make sure back's go through proper
|
||||
*/
|
||||
public static boolean isFilterTextField(EditorInfo inputField) {
|
||||
if (inputField == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int inputType = inputField.inputType & InputType.TYPE_MASK_CLASS;
|
||||
int inputVariation = inputField.inputType & InputType.TYPE_MASK_VARIATION;
|
||||
|
||||
return inputType == InputType.TYPE_CLASS_TEXT && inputVariation == InputType.TYPE_TEXT_VARIATION_FILTER;
|
||||
}
|
||||
|
||||
/**
|
||||
* isDialerField
|
||||
* Dialer fields seem to take care of numbers and backspace on their own,
|
||||
* so we need to be aware of them.
|
||||
*/
|
||||
public static boolean isDialerField(EditorInfo inputField) {
|
||||
return
|
||||
inputField != null
|
||||
&& inputField.inputType == InputType.TYPE_CLASS_PHONE
|
||||
&& inputField.packageName.equals("com.android.dialer");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* determineInputModes
|
||||
* Determine the typing mode based on the input field being edited. Returns an ArrayList of the allowed modes.
|
||||
*
|
||||
* @return ArrayList<T9Preferences.MODE_ABC | T9Preferences.MODE_123 | T9Preferences.MODE_PREDICTIVE>
|
||||
*/
|
||||
public static ArrayList<Integer> determineInputModes(EditorInfo inputField) {
|
||||
final int INPUT_TYPE_SHARP_007H_PHONE_BOOK = 65633;
|
||||
|
||||
ArrayList<Integer> allowedModes = new ArrayList<>();
|
||||
|
||||
if (inputField == null) {
|
||||
allowedModes.add(TraditionalT9.MODE_123);
|
||||
return allowedModes;
|
||||
}
|
||||
|
||||
if (
|
||||
inputField.inputType == INPUT_TYPE_SHARP_007H_PHONE_BOOK
|
||||
|| (
|
||||
inputField.privateImeOptions != null
|
||||
&& inputField.privateImeOptions.equals("io.github.sspanak.tt9.addword=true")
|
||||
)
|
||||
) {
|
||||
allowedModes.add(TraditionalT9.MODE_123);
|
||||
allowedModes.add(TraditionalT9.MODE_ABC);
|
||||
return allowedModes;
|
||||
}
|
||||
|
||||
switch (inputField.inputType & InputType.TYPE_MASK_CLASS) {
|
||||
case InputType.TYPE_CLASS_NUMBER:
|
||||
case InputType.TYPE_CLASS_DATETIME:
|
||||
// Numbers and dates default to the symbols keyboard, with
|
||||
// no extra features.
|
||||
case InputType.TYPE_CLASS_PHONE:
|
||||
// Phones will also default to the symbols keyboard, though
|
||||
// often you will want to have a dedicated phone keyboard.
|
||||
allowedModes.add(TraditionalT9.MODE_123);
|
||||
return allowedModes;
|
||||
|
||||
case InputType.TYPE_CLASS_TEXT:
|
||||
// This is general text editing. We will default to the
|
||||
// normal alphabetic keyboard, and assume that we should
|
||||
// be doing predictive text (showing candidates as the
|
||||
// user types).
|
||||
if (!isSpecializedTextField(inputField)) {
|
||||
allowedModes.add(TraditionalT9.MODE_PREDICTIVE);
|
||||
}
|
||||
|
||||
// ↓ fallthrough to add ABC and 123 modes ↓
|
||||
|
||||
default:
|
||||
// For all unknown input types, default to the alphabetic
|
||||
// keyboard with no special features.
|
||||
allowedModes.add(TraditionalT9.MODE_123);
|
||||
allowedModes.add(TraditionalT9.MODE_ABC);
|
||||
|
||||
return allowedModes;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper to update the shift state of our keyboard based on the initial
|
||||
* editor state.
|
||||
*/
|
||||
public static void determineTextCase(EditorInfo inputField) {
|
||||
// Logger.d("updateShift", "CM start: " + mCapsMode);
|
||||
// if (inputField != null && mCapsMode != T9Preferences.CASE_UPPER) {
|
||||
// int caps = 0;
|
||||
// if (inputField.inputType != InputType.TYPE_NULL) {
|
||||
// caps = currentInputConnection.getCursorCapsMode(inputField.inputType);
|
||||
// }
|
||||
// // mInputView.setShifted(mCapsLock || caps != 0);
|
||||
// // Logger.d("updateShift", "caps: " + caps);
|
||||
// if ((caps & TextUtils.CAP_MODE_CHARACTERS) == TextUtils.CAP_MODE_CHARACTERS) {
|
||||
// mCapsMode = T9Preferences.CASE_UPPER;
|
||||
// } else if ((caps & TextUtils.CAP_MODE_SENTENCES) == TextUtils.CAP_MODE_SENTENCES) {
|
||||
// mCapsMode = T9Preferences.CASE_CAPITALIZE;
|
||||
// } else if ((caps & TextUtils.CAP_MODE_WORDS) == TextUtils.CAP_MODE_WORDS) {
|
||||
// mCapsMode = T9Preferences.CASE_CAPITALIZE;
|
||||
// } else {
|
||||
// mCapsMode = T9Preferences.CASE_LOWER;
|
||||
// }
|
||||
// updateStatusIcon();
|
||||
// }
|
||||
// Logger.d("updateShift", "CM end: " + mCapsMode);
|
||||
}
|
||||
|
||||
|
||||
public static String getSurroundingWord(InputConnection currentInputConnection) {
|
||||
CharSequence before = currentInputConnection.getTextBeforeCursor(50, 0);
|
||||
CharSequence after = currentInputConnection.getTextAfterCursor(50, 0);
|
||||
int bounds = -1;
|
||||
if (!TextUtils.isEmpty(before)) {
|
||||
bounds = before.length() -1;
|
||||
while (bounds > 0 && !Character.isWhitespace(before.charAt(bounds))) {
|
||||
bounds--;
|
||||
}
|
||||
before = before.subSequence(bounds, before.length());
|
||||
}
|
||||
if (!TextUtils.isEmpty(after)) {
|
||||
bounds = 0;
|
||||
while (bounds < after.length() && !Character.isWhitespace(after.charAt(bounds))) {
|
||||
bounds++;
|
||||
}
|
||||
after = after.subSequence(0, bounds);
|
||||
}
|
||||
return before.toString().trim() + after.toString().trim();
|
||||
}
|
||||
}
|
||||
74
src/io/github/sspanak/tt9/ime/InputModeValidator.java
Normal file
74
src/io/github/sspanak/tt9/ime/InputModeValidator.java
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.languages.definitions.English;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
public class InputModeValidator {
|
||||
public static ArrayList<Integer> validateEnabledLanguages(T9Preferences prefs, ArrayList<Integer> enabledLanguageIds) {
|
||||
ArrayList<Language> validLanguages = LanguageCollection.getAll(enabledLanguageIds);
|
||||
ArrayList<Integer> validLanguageIds = new ArrayList<>();
|
||||
for (Language lang : validLanguages) {
|
||||
validLanguageIds.add(lang.getId());
|
||||
}
|
||||
if (validLanguageIds.size() == 0) {
|
||||
validLanguageIds.add(1);
|
||||
Logger.e("tt9/validateEnabledLanguages", "The language list seems to be corrupted. Resetting to first language only.");
|
||||
}
|
||||
|
||||
prefs.saveEnabledLanguages(validLanguageIds);
|
||||
|
||||
return validLanguageIds;
|
||||
}
|
||||
|
||||
public static Language validateLanguage(T9Preferences prefs, Language language, ArrayList<Integer> validLanguageIds) {
|
||||
if (language != null && validLanguageIds.contains(language.getId())) {
|
||||
return language;
|
||||
}
|
||||
|
||||
String error = language != null ? "Language: " + language.getId() + " is not enabled." : "Invalid language.";
|
||||
|
||||
Language validLanguage = LanguageCollection.getLanguage(validLanguageIds.get(0));
|
||||
validLanguage = validLanguage == null ? LanguageCollection.getLanguage(1) : validLanguage;
|
||||
validLanguage = validLanguage == null ? new English() : validLanguage;
|
||||
prefs.saveInputLanguage(validLanguage.getId());
|
||||
|
||||
Logger.w("tt9/validateSavedLanguage", error + " Enforcing language: " + validLanguage.getId());
|
||||
|
||||
return validLanguage;
|
||||
}
|
||||
|
||||
public static int validateMode(T9Preferences prefs, int inputMode, ArrayList<Integer> allowedModes) {
|
||||
if (allowedModes.size() > 0 && allowedModes.contains(inputMode)) {
|
||||
return inputMode;
|
||||
}
|
||||
|
||||
int newMode = allowedModes.size() > 0 ? allowedModes.get(0) : TraditionalT9.MODE_123;
|
||||
prefs.saveInputMode(newMode);
|
||||
|
||||
if (newMode != inputMode) {
|
||||
Logger.w("tt9/validateMode", "Invalid input mode: " + inputMode + " Enforcing: " + newMode);
|
||||
}
|
||||
|
||||
return newMode;
|
||||
}
|
||||
|
||||
public static int validateTextCase(T9Preferences prefs, int textCase, ArrayList<Integer> allowedTextCases) {
|
||||
if (allowedTextCases.size() > 0 && allowedTextCases.contains(textCase)) {
|
||||
return textCase;
|
||||
}
|
||||
|
||||
int newCase = allowedTextCases.size() > 0 ? allowedTextCases.get(0) : TraditionalT9.CASE_LOWER;
|
||||
prefs.saveTextCase(newCase);
|
||||
|
||||
if (textCase != newCase) {
|
||||
Logger.w("tt9/validateTextCase", "Invalid text case: " + textCase + " Enforcing: " + newCase);
|
||||
}
|
||||
|
||||
return newCase;
|
||||
}
|
||||
}
|
||||
418
src/io/github/sspanak/tt9/ime/KeyPadHandler.java
Normal file
418
src/io/github/sspanak/tt9/ime/KeyPadHandler.java
Normal file
|
|
@ -0,0 +1,418 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
import android.text.InputType;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputConnection;
|
||||
|
||||
import io.github.sspanak.tt9.ui.CandidateView;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
|
||||
abstract class KeyPadHandler extends InputMethodService {
|
||||
protected InputConnection currentInputConnection = null;
|
||||
|
||||
protected CandidateView mSuggestionView;
|
||||
protected T9Preferences prefs;
|
||||
|
||||
// editing mode
|
||||
protected static final int NON_EDIT = 0;
|
||||
protected static final int EDITING = 1;
|
||||
protected static final int EDITING_NOSHOW = 2;
|
||||
protected static final int EDITING_STRICT_NUMERIC = 3;
|
||||
protected static final int EDITING_DIALER = 4; // see: https://github.com/sspanak/tt9/issues/46
|
||||
protected int mEditing = NON_EDIT;
|
||||
|
||||
// temporal key handling
|
||||
private int ignoreNextKeyUp = 0;
|
||||
private int lastKeyCode = 0;
|
||||
protected boolean isNumKeyRepeated = false;
|
||||
|
||||
// throttling
|
||||
private static final int BACKSPACE_DEBOUNCE_TIME = 80;
|
||||
private long lastBackspaceCall = 0;
|
||||
|
||||
|
||||
/**
|
||||
* Main initialization of the input method component. Be sure to call to
|
||||
* super class.
|
||||
*/
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
prefs = T9Preferences.getInstance(this);
|
||||
|
||||
onInit();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onEvaluateInputViewShown() {
|
||||
super.onEvaluateInputViewShown();
|
||||
return mEditing != EDITING_NOSHOW;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onEvaluateFullscreenMode() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by the framework when your view for creating input needs to be
|
||||
* generated. This will be called the first time your input method is
|
||||
* displayed, and every time it needs to be re-created such as due to a
|
||||
* configuration change.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateInputView() {
|
||||
return createSoftKeyView();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Called by the framework when your view for showing candidates needs to be
|
||||
* generated, like {@link #onCreateInputView}.
|
||||
*/
|
||||
@Override
|
||||
public View onCreateCandidatesView() {
|
||||
if (mSuggestionView == null) {
|
||||
mSuggestionView = new CandidateView(this);
|
||||
}
|
||||
return mSuggestionView;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is the main point where we do our initialization of the input method
|
||||
* to begin operating on an application. At this point we have been bound to
|
||||
* the client, and are now receiving all of the detailed information about
|
||||
* the target of our edits.
|
||||
*/
|
||||
@Override
|
||||
public void onStartInput(EditorInfo inputField, boolean restarting) {
|
||||
currentInputConnection = getCurrentInputConnection();
|
||||
// Logger.d("T9.onStartInput", "INPUTTYPE: " + inputField.inputType + " FIELDID: " + inputField.fieldId +
|
||||
// " FIELDNAME: " + inputField.fieldName + " PACKAGE NAME: " + inputField.packageName);
|
||||
|
||||
mEditing = NON_EDIT;
|
||||
|
||||
// https://developer.android.com/reference/android/text/InputType#TYPE_NULL
|
||||
// Special or limited input type. This means the input connection is not rich,
|
||||
// or it can not process or show things like candidate text, nor retrieve the current text.
|
||||
// We just let Android handle this input.
|
||||
if (currentInputConnection == null || inputField == null || inputField.inputType == InputType.TYPE_NULL) {
|
||||
onFinish();
|
||||
return;
|
||||
}
|
||||
|
||||
onRestart(inputField);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This is called when the user is done editing a field. We can use this to
|
||||
* reset our state.
|
||||
*/
|
||||
@Override
|
||||
public void onFinishInput() {
|
||||
super.onFinishInput();
|
||||
// Logger.d("onFinishInput", "When is this called?");
|
||||
if (mEditing == EDITING || mEditing == EDITING_NOSHOW) {
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use this to monitor key events being delivered to the application. We get
|
||||
* first crack at them, and can either resume them or let them continue to
|
||||
* the app.
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (isOff()) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
// Logger.d("onKeyDown", "Key: " + event + " repeat?: " + event.getRepeatCount() + " long-time: " + event.isLongPress());
|
||||
|
||||
// "backspace" key must repeat its function, when held down, so we handle it in a special way
|
||||
// Also dialer fields seem to handle backspace on their own and we must ignore it,
|
||||
// otherwise, keyDown race condition occur for all keys.
|
||||
if (mEditing != EDITING_DIALER && keyCode == prefs.getKeyBackspace()) {
|
||||
boolean isThereTextBefore = InputFieldHelper.isThereText(currentInputConnection);
|
||||
boolean backspaceHandleStatus = handleBackspaceHold();
|
||||
|
||||
// Allow BACK key to function as back when there is no text
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
return isThereTextBefore;
|
||||
} else {
|
||||
return backspaceHandleStatus;
|
||||
}
|
||||
}
|
||||
|
||||
// In numeric fields, we do not want to handle anything, but "backspace"
|
||||
if (mEditing == EDITING_STRICT_NUMERIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// start tracking key hold
|
||||
if (keyCode == KeyEvent.KEYCODE_0 || shouldTrackNumPress()) {
|
||||
event.startTracking();
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// In dialer fields we only handle "0", when held, and convert it to "+"
|
||||
if (mEditing == EDITING_DIALER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
keyCode == prefs.getKeyOtherActions()
|
||||
|| keyCode == prefs.getKeyInputMode()
|
||||
|| keyCode == KeyEvent.KEYCODE_STAR
|
||||
|| keyCode == KeyEvent.KEYCODE_POUND
|
||||
|| (isNumber(keyCode) && shouldTrackNumPress())
|
||||
|| ((keyCode == KeyEvent.KEYCODE_DPAD_UP || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) && shouldTrackArrows())
|
||||
|| (mEditing != EDITING_NOSHOW && keyCode == KeyEvent.KEYCODE_DPAD_CENTER)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
if (isOff()) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
// Logger.d("onLongPress", "LONG PRESS: " + keyCode);
|
||||
|
||||
if (event.getRepeatCount() > 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
ignoreNextKeyUp = keyCode;
|
||||
|
||||
if (keyCode == prefs.getKeyOtherActions()) {
|
||||
return onKeyOtherAction(true);
|
||||
}
|
||||
|
||||
if (keyCode == prefs.getKeyInputMode()) {
|
||||
return onKeyInputMode(true);
|
||||
}
|
||||
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_0: return on0(true);
|
||||
case KeyEvent.KEYCODE_1:
|
||||
case KeyEvent.KEYCODE_2:
|
||||
case KeyEvent.KEYCODE_3:
|
||||
case KeyEvent.KEYCODE_4:
|
||||
case KeyEvent.KEYCODE_5:
|
||||
case KeyEvent.KEYCODE_6:
|
||||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
return on1to9(keyCodeToKeyNumber(keyCode), true);
|
||||
}
|
||||
|
||||
ignoreNextKeyUp = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Use this to monitor key events being delivered to the application. We get
|
||||
* first crack at them, and can either resume them or let them continue to
|
||||
* the app.
|
||||
*/
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (isOff()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == ignoreNextKeyUp) {
|
||||
// Logger.d("onKeyUp", "Ignored: " + keyCode);
|
||||
ignoreNextKeyUp = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isNumber(keyCode)) {
|
||||
isNumKeyRepeated = (lastKeyCode == keyCode);
|
||||
lastKeyCode = keyCode;
|
||||
}
|
||||
|
||||
// Logger.d("onKeyUp", "Key: " + keyCode + " repeat?: " + event.getRepeatCount());
|
||||
|
||||
if (
|
||||
mEditing != EDITING_DIALER // dialer fields seem to handle backspace on their own
|
||||
&& keyCode == prefs.getKeyBackspace()
|
||||
&& InputFieldHelper.isThereText(currentInputConnection)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// in numeric fields, we just handle backspace and let the rest go as-is.
|
||||
if (mEditing == EDITING_STRICT_NUMERIC) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == KeyEvent.KEYCODE_0) {
|
||||
return on0(false);
|
||||
}
|
||||
|
||||
// dialer fields are similar to pure numeric fields, but for user convenience, holding "0"
|
||||
// is converted to "+"
|
||||
if (mEditing == EDITING_DIALER) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (keyCode == prefs.getKeyOtherActions()) {
|
||||
return onKeyOtherAction(false);
|
||||
}
|
||||
|
||||
if (keyCode == prefs.getKeyInputMode()) {
|
||||
return onKeyInputMode(false);
|
||||
}
|
||||
|
||||
switch(keyCode) {
|
||||
case KeyEvent.KEYCODE_DPAD_CENTER: return onOK();
|
||||
case KeyEvent.KEYCODE_DPAD_UP: return onUp();
|
||||
case KeyEvent.KEYCODE_DPAD_DOWN: return onDown();
|
||||
case KeyEvent.KEYCODE_1:
|
||||
case KeyEvent.KEYCODE_2:
|
||||
case KeyEvent.KEYCODE_3:
|
||||
case KeyEvent.KEYCODE_4:
|
||||
case KeyEvent.KEYCODE_5:
|
||||
case KeyEvent.KEYCODE_6:
|
||||
case KeyEvent.KEYCODE_7:
|
||||
case KeyEvent.KEYCODE_8:
|
||||
case KeyEvent.KEYCODE_9:
|
||||
return on1to9(keyCodeToKeyNumber(keyCode), false);
|
||||
case KeyEvent.KEYCODE_STAR: return onStar();
|
||||
case KeyEvent.KEYCODE_POUND: return onPound();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected boolean handleBackspaceHold() {
|
||||
if (System.currentTimeMillis() - lastBackspaceCall < BACKSPACE_DEBOUNCE_TIME) {
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean handled = onBackspace();
|
||||
lastBackspaceCall = System.currentTimeMillis();
|
||||
|
||||
return handled;
|
||||
}
|
||||
|
||||
|
||||
private boolean isOff() {
|
||||
return currentInputConnection == null || mEditing == NON_EDIT;
|
||||
}
|
||||
|
||||
|
||||
private boolean isNumber(int keyCode) {
|
||||
return keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9;
|
||||
}
|
||||
|
||||
|
||||
protected void resetKeyRepeat() {
|
||||
isNumKeyRepeated = false;
|
||||
lastKeyCode = 0;
|
||||
}
|
||||
|
||||
|
||||
private int keyCodeToKeyNumber(int keyCode) {
|
||||
switch (keyCode) {
|
||||
case KeyEvent.KEYCODE_0:
|
||||
return 0;
|
||||
case KeyEvent.KEYCODE_1:
|
||||
return 1;
|
||||
case KeyEvent.KEYCODE_2:
|
||||
return 2;
|
||||
case KeyEvent.KEYCODE_3:
|
||||
return 3;
|
||||
case KeyEvent.KEYCODE_4:
|
||||
return 4;
|
||||
case KeyEvent.KEYCODE_5:
|
||||
return 5;
|
||||
case KeyEvent.KEYCODE_6:
|
||||
return 6;
|
||||
case KeyEvent.KEYCODE_7:
|
||||
return 7;
|
||||
case KeyEvent.KEYCODE_8:
|
||||
return 8;
|
||||
case KeyEvent.KEYCODE_9:
|
||||
return 9;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// toggle handlers
|
||||
abstract protected boolean shouldTrackArrows();
|
||||
abstract protected boolean shouldTrackNumPress();
|
||||
|
||||
// default hardware key handlers
|
||||
abstract public boolean onBackspace();
|
||||
abstract public boolean onOK();
|
||||
abstract protected boolean onUp();
|
||||
abstract protected boolean onDown();
|
||||
abstract protected boolean on0(boolean hold);
|
||||
abstract protected boolean on1to9(int key, boolean hold);
|
||||
abstract protected boolean onStar();
|
||||
abstract protected boolean onPound();
|
||||
|
||||
// customized key handlers
|
||||
abstract protected boolean onKeyInputMode(boolean hold);
|
||||
abstract protected boolean onKeyOtherAction(boolean hold);
|
||||
|
||||
// helpers
|
||||
abstract protected void onInit();
|
||||
abstract protected void onRestart(EditorInfo inputField);
|
||||
abstract protected void onFinish();
|
||||
abstract protected View createSoftKeyView();
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////// THE ONES BELOW MAY BE UNNECESSARY. IMPLEMENT IF NEEDED. /////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///
|
||||
/**
|
||||
* Deal with the editor reporting movement of its cursor.
|
||||
*/
|
||||
/* @Override
|
||||
public void onUpdateSelection(
|
||||
int oldSelStart,
|
||||
int oldSelEnd,
|
||||
int newSelStart,
|
||||
int newSelEnd,
|
||||
int candidatesStart,
|
||||
int candidatesEnd
|
||||
) {
|
||||
// @todo: implement if necessary, but probably in TraditionalT9, not here
|
||||
// ... handle any interesting cursor movement
|
||||
// commitCurrentSuggestion()
|
||||
// setSuggestions(null)
|
||||
}*/
|
||||
|
||||
}
|
||||
48
src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
Normal file
48
src/io/github/sspanak/tt9/ime/SoftKeyHandler.java
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
class SoftKeyHandler implements View.OnTouchListener {
|
||||
private static final int[] buttons = { R.id.main_left, R.id.main_right, R.id.main_mid };
|
||||
private final TraditionalT9 parent;
|
||||
|
||||
public SoftKeyHandler(View mainView, TraditionalT9 tt9) {
|
||||
this.parent = tt9;
|
||||
changeView(mainView);
|
||||
}
|
||||
|
||||
|
||||
public void changeView(View v) {
|
||||
for (int buttonId : buttons) {
|
||||
View button = v.findViewById(buttonId);
|
||||
button.setOnTouchListener(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View view, MotionEvent event) {
|
||||
int action = event.getAction();
|
||||
int buttonId = view.getId();
|
||||
|
||||
if (buttonId == R.id.main_left && action == MotionEvent.ACTION_UP) {
|
||||
UI.showPreferencesScreen(parent);
|
||||
return view.performClick();
|
||||
}
|
||||
|
||||
if (buttonId == R.id.main_mid && action == MotionEvent.ACTION_UP) {
|
||||
parent.onOK();
|
||||
return view.performClick();
|
||||
}
|
||||
|
||||
if (buttonId == R.id.main_right && action == MotionEvent.AXIS_PRESSURE) {
|
||||
return parent.handleBackspaceHold();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
620
src/io/github/sspanak/tt9/ime/TraditionalT9.java
Normal file
620
src/io/github/sspanak/tt9/ime/TraditionalT9.java
Normal file
|
|
@ -0,0 +1,620 @@
|
|||
package io.github.sspanak.tt9.ime;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
|
||||
import java.io.NotActiveException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||
import io.github.sspanak.tt9.db.InsertBlankWordException;
|
||||
import io.github.sspanak.tt9.languages.InvalidLanguageException;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
import io.github.sspanak.tt9.ui.UI;
|
||||
|
||||
public class TraditionalT9 extends KeyPadHandler {
|
||||
// input mode
|
||||
public static final int MODE_PREDICTIVE = 0;
|
||||
public static final int MODE_ABC = 1;
|
||||
public static final int MODE_123 = 2;
|
||||
private ArrayList<Integer> allowedInputModes = new ArrayList<>();
|
||||
private int mInputMode = MODE_123;
|
||||
|
||||
// text case
|
||||
public static final int CASE_LOWER = 0;
|
||||
public static final int CASE_CAPITALIZE = 1;
|
||||
public static final int CASE_UPPER = 2;
|
||||
private ArrayList<Integer> allowedTextCases = new ArrayList<>();
|
||||
private int mTextCase = CASE_LOWER;
|
||||
|
||||
// language
|
||||
protected ArrayList<Integer> mEnabledLanguages;
|
||||
protected Language mLanguage;
|
||||
|
||||
// soft key view
|
||||
private SoftKeyHandler softKeyHandler = null;
|
||||
private View softKeyView = null;
|
||||
|
||||
// @todo: move predictive mode stuff in its own class in #66
|
||||
private String predictionSequence = "";
|
||||
|
||||
|
||||
private void loadPreferences() {
|
||||
mLanguage = LanguageCollection.getLanguage(prefs.getInputLanguage());
|
||||
mEnabledLanguages = prefs.getEnabledLanguages();
|
||||
mInputMode = prefs.getInputMode();
|
||||
mTextCase = prefs.getTextCase();
|
||||
}
|
||||
|
||||
private void validateLanguages() {
|
||||
mEnabledLanguages = InputModeValidator.validateEnabledLanguages(prefs, mEnabledLanguages);
|
||||
mLanguage = InputModeValidator.validateLanguage(prefs, mLanguage, mEnabledLanguages);
|
||||
}
|
||||
|
||||
private void validatePreferences() {
|
||||
validateLanguages();
|
||||
mInputMode = InputModeValidator.validateMode(prefs, mInputMode, allowedInputModes);
|
||||
mTextCase = InputModeValidator.validateTextCase(prefs, mTextCase, allowedTextCases);
|
||||
}
|
||||
|
||||
|
||||
protected void onInit() {
|
||||
if (softKeyHandler == null) {
|
||||
softKeyHandler = new SoftKeyHandler(getLayoutInflater().inflate(R.layout.mainview, null), this);
|
||||
}
|
||||
|
||||
loadPreferences();
|
||||
}
|
||||
|
||||
|
||||
protected void onRestart(EditorInfo inputField) {
|
||||
// in case we are back from Preferences screen, update the language list
|
||||
mEnabledLanguages = prefs.getEnabledLanguages();
|
||||
validatePreferences();
|
||||
|
||||
// reset all UI elements
|
||||
predictionSequence = "";
|
||||
clearSuggestions();
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
displaySoftKeyMenu();
|
||||
if (!isInputViewShown()) {
|
||||
showWindow(true);
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P && mEditing != EDITING_STRICT_NUMERIC && mEditing != EDITING_DIALER) {
|
||||
requestShowSelf(1);
|
||||
}
|
||||
|
||||
determineAllowedInputModes(inputField);
|
||||
determineAllowedTextCases();
|
||||
|
||||
// @todo: handle word adding
|
||||
}
|
||||
|
||||
|
||||
protected void onFinish() {
|
||||
predictionSequence = "";
|
||||
clearSuggestions();
|
||||
|
||||
// @todo: clear previous word
|
||||
|
||||
hideStatusIcon();
|
||||
hideWindow();
|
||||
|
||||
if (softKeyView != null) {
|
||||
softKeyView.setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public boolean onBackspace() {
|
||||
if (!InputFieldHelper.isThereText(currentInputConnection)) {
|
||||
Logger.d("onBackspace", "backspace ignored");
|
||||
return false;
|
||||
}
|
||||
|
||||
resetKeyRepeat();
|
||||
|
||||
if (mInputMode == MODE_PREDICTIVE && predictionSequence.length() > 1) {
|
||||
predictionSequence = predictionSequence.substring(0, predictionSequence.length() - 1);
|
||||
applyPredictionSequence();
|
||||
} else {
|
||||
commitCurrentSuggestion();
|
||||
super.sendDownUpKeyEvents(KeyEvent.KEYCODE_DEL);
|
||||
}
|
||||
|
||||
Logger.d("onBackspace", "backspace handled");
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean onOK() {
|
||||
Logger.d("onOK", "enter handler");
|
||||
|
||||
acceptCurrentSuggestion();
|
||||
resetKeyRepeat();
|
||||
|
||||
return !isSuggestionViewHidden();
|
||||
}
|
||||
|
||||
|
||||
protected boolean onUp() {
|
||||
return previousSuggestion();
|
||||
}
|
||||
|
||||
|
||||
protected boolean onDown() {
|
||||
return nextSuggestion();
|
||||
}
|
||||
|
||||
|
||||
protected boolean on0(boolean hold) {
|
||||
if (!hold && nextSuggestionInModeAbc()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
acceptCurrentSuggestion();
|
||||
|
||||
setSuggestions(
|
||||
mInputMode == MODE_ABC && !hold ? mLanguage.getKeyCharacters(0) : null,
|
||||
0
|
||||
);
|
||||
|
||||
if (hold) {
|
||||
String chr = mInputMode == MODE_123 ? "+" : "0";
|
||||
currentInputConnection.commitText(chr, 1);
|
||||
} else if (mInputMode == MODE_PREDICTIVE) {
|
||||
currentInputConnection.commitText(" ", 1);
|
||||
} else if (mInputMode == MODE_123) {
|
||||
currentInputConnection.commitText("0", 1);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* on1to9
|
||||
*
|
||||
* @param key Must be a number from 1 to 9, not a "KeyEvent.KEYCODE_X"
|
||||
* @param hold If "true" we are calling the handler, because the key is being held.
|
||||
* @return boolean
|
||||
*/
|
||||
protected boolean on1to9(int key, boolean hold) {
|
||||
if (mInputMode == MODE_123) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hold) {
|
||||
commitCurrentSuggestion(); // commit the previous one before adding the "hold" character
|
||||
currentInputConnection.commitText(String.valueOf(key), 1);
|
||||
return true;
|
||||
} else if (wordInPredictiveMode(key)) {
|
||||
return true;
|
||||
} else if (emoticonInPredictiveMode(key)) {
|
||||
return true;
|
||||
} else if (nextSuggestionInModeAbc()) {
|
||||
return true;
|
||||
} else if (mInputMode == MODE_ABC || mInputMode == MODE_PREDICTIVE) {
|
||||
commitCurrentSuggestion(); // commit the previous one before suggesting the new one
|
||||
setSuggestions(mLanguage.getKeyCharacters(key, mTextCase == CASE_LOWER));
|
||||
setComposingTextFromCurrentSuggestion();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
protected boolean onPound() {
|
||||
currentInputConnection.commitText("#", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean onStar() {
|
||||
currentInputConnection.commitText("*", 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean onKeyInputMode(boolean hold) {
|
||||
if (hold) {
|
||||
nextLang();
|
||||
} else {
|
||||
nextKeyMode();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean onKeyOtherAction(boolean hold) {
|
||||
if (hold) {
|
||||
UI.showPreferencesScreen(this);
|
||||
} else {
|
||||
showAddWord();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
protected boolean shouldTrackNumPress() {
|
||||
return mInputMode != TraditionalT9.MODE_123;
|
||||
}
|
||||
|
||||
|
||||
protected boolean shouldTrackArrows() {
|
||||
return mEditing != EDITING_NOSHOW && !isSuggestionViewHidden();
|
||||
}
|
||||
|
||||
|
||||
private boolean isSuggestionViewHidden() {
|
||||
return mSuggestionView == null || !mSuggestionView.isShown();
|
||||
}
|
||||
|
||||
|
||||
private boolean previousSuggestion() {
|
||||
if (isSuggestionViewHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mSuggestionView.scrollToSuggestion(-1);
|
||||
|
||||
String word = mSuggestionView.getCurrentSuggestion();
|
||||
currentInputConnection.setComposingText(word, word.length());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean nextSuggestion() {
|
||||
if (isSuggestionViewHidden()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mSuggestionView.scrollToSuggestion(1);
|
||||
|
||||
String word = mSuggestionView.getCurrentSuggestion();
|
||||
currentInputConnection.setComposingText(word, word.length());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean emoticonInPredictiveMode(int key) {
|
||||
if (key != 1 || mInputMode != MODE_PREDICTIVE || !isNumKeyRepeated) {
|
||||
return false;
|
||||
}
|
||||
|
||||
setSuggestions(Punctuation.Emoticons);
|
||||
setComposingTextFromCurrentSuggestion();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private boolean wordInPredictiveMode(int key) {
|
||||
if (
|
||||
mInputMode != MODE_PREDICTIVE ||
|
||||
// 0 is not a word, but space, so we handle it in on0().
|
||||
key == 0 ||
|
||||
// double 1 is not a word, but an emoticon and it is handled elsewhere
|
||||
(key == 1 && isNumKeyRepeated)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
if (
|
||||
// Punctuation is considered "a word", so that we can increase the priority as needed
|
||||
// Also, it must break the current word.
|
||||
(key == 1 && predictionSequence.length() > 0) ||
|
||||
// On the other hand, letters also "break" punctuation.
|
||||
(key != 1 && predictionSequence.endsWith("1"))
|
||||
) {
|
||||
acceptCurrentSuggestion();
|
||||
}
|
||||
|
||||
predictionSequence += key;
|
||||
applyPredictionSequence();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final Handler handleSuggestions = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
ArrayList<String> suggestions = msg.getData().getStringArrayList("suggestions");
|
||||
suggestions = guessSuggestionsWhenNone(suggestions, mSuggestionView.getCurrentSuggestion());
|
||||
|
||||
setSuggestions(suggestions);
|
||||
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
||||
|
||||
// Put the first suggestion in the text field,
|
||||
// but cut it off to the length of the sequence (how many keys were pressed),
|
||||
// for a more intuitive experience.
|
||||
String word = mSuggestionView.getCurrentSuggestion();
|
||||
word = mSuggestionView.getCurrentSuggestion().substring(0, Math.min(predictionSequence.length(), word.length()));
|
||||
currentInputConnection.setComposingText(word, word.length());
|
||||
}
|
||||
};
|
||||
|
||||
private void applyPredictionSequence() {
|
||||
if (predictionSequence.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
DictionaryDb.getSuggestions(
|
||||
this,
|
||||
handleSuggestions,
|
||||
mLanguage.getId(),
|
||||
predictionSequence,
|
||||
prefs.getSuggestionsMin(),
|
||||
prefs.getSuggestionsMax()
|
||||
);
|
||||
}
|
||||
|
||||
private ArrayList<String> guessSuggestionsWhenNone(ArrayList<String> suggestions, String lastWord) {
|
||||
if (
|
||||
(suggestions != null && suggestions.size() > 0) ||
|
||||
predictionSequence.length() == 0 ||
|
||||
predictionSequence.charAt(0) == '1'
|
||||
) {
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
lastWord = lastWord.substring(0, Math.min(predictionSequence.length() - 1, lastWord.length()));
|
||||
try {
|
||||
int lastDigit = predictionSequence.charAt(predictionSequence.length() - 1) - '0';
|
||||
lastWord += mLanguage.getKeyCharacters(lastDigit).get(0);
|
||||
} catch (Exception e) {
|
||||
lastWord += predictionSequence.charAt(predictionSequence.length() - 1);
|
||||
}
|
||||
|
||||
return new ArrayList<>(Collections.singletonList(lastWord));
|
||||
}
|
||||
|
||||
|
||||
private boolean nextSuggestionInModeAbc() {
|
||||
return isNumKeyRepeated && mInputMode == MODE_ABC && nextSuggestion();
|
||||
}
|
||||
|
||||
|
||||
private void acceptCurrentSuggestion() {
|
||||
// bring this word up in the suggestions list next time
|
||||
if (mInputMode == MODE_PREDICTIVE) {
|
||||
String currentWord = mSuggestionView.getCurrentSuggestion();
|
||||
if (currentWord.length() == 0) {
|
||||
Logger.i("acceptCurrentSuggestion", "Current word is empty. Nothing to accept.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
String sequence = mLanguage.getDigitSequenceForWord(currentWord);
|
||||
DictionaryDb.incrementWordFrequency(this, mLanguage.getId(), currentWord, sequence);
|
||||
} catch (Exception e) {
|
||||
Logger.e(getClass().getName(), "Failed incrementing priority of word: '" + currentWord + "'. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
commitCurrentSuggestion();
|
||||
}
|
||||
|
||||
|
||||
private void commitCurrentSuggestion() {
|
||||
predictionSequence = "";
|
||||
|
||||
// commit the current suggestion to the input field
|
||||
if (!isSuggestionViewHidden()) {
|
||||
if (mSuggestionView.getCurrentSuggestion().equals(" ")) {
|
||||
// finishComposingText() seems to ignore a single space,
|
||||
// so we have to force commit it.
|
||||
currentInputConnection.commitText(" ", 1);
|
||||
} else {
|
||||
currentInputConnection.finishComposingText();
|
||||
}
|
||||
}
|
||||
|
||||
setSuggestions(null);
|
||||
}
|
||||
|
||||
|
||||
private void clearSuggestions() {
|
||||
if (currentInputConnection != null) {
|
||||
currentInputConnection.setComposingText("", 1);
|
||||
currentInputConnection.finishComposingText();
|
||||
}
|
||||
|
||||
setSuggestions(null);
|
||||
}
|
||||
|
||||
protected void setSuggestions(List<String> suggestions) {
|
||||
setSuggestions(suggestions, 0);
|
||||
}
|
||||
|
||||
protected void setSuggestions(List<String> suggestions, int initialSel) {
|
||||
if (mSuggestionView == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
boolean show = suggestions != null && suggestions.size() > 0;
|
||||
|
||||
mSuggestionView.setSuggestions(suggestions, initialSel);
|
||||
setCandidatesViewShown(show);
|
||||
}
|
||||
|
||||
|
||||
private void nextKeyMode() {
|
||||
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
|
||||
clearSuggestions();
|
||||
mInputMode = MODE_123;
|
||||
}
|
||||
// when typing a word or viewing scrolling the suggestions, only change the case
|
||||
else if (!isSuggestionViewHidden()) {
|
||||
determineAllowedTextCases();
|
||||
|
||||
int modeIndex = (allowedTextCases.indexOf(mTextCase) + 1) % allowedTextCases.size();
|
||||
mTextCase = allowedTextCases.get(modeIndex);
|
||||
|
||||
mSuggestionView.changeCase(mTextCase, mLanguage.getLocale());
|
||||
setComposingTextFromCurrentSuggestion();
|
||||
}
|
||||
// make "abc" and "ABC" separate modes from user perspective
|
||||
else if (mInputMode == MODE_ABC && mTextCase == CASE_LOWER) {
|
||||
mTextCase = CASE_UPPER;
|
||||
} else {
|
||||
int modeIndex = (allowedInputModes.indexOf(mInputMode) + 1) % allowedInputModes.size();
|
||||
mInputMode = allowedInputModes.get(modeIndex);
|
||||
|
||||
mTextCase = mInputMode == MODE_PREDICTIVE ? CASE_CAPITALIZE : CASE_LOWER;
|
||||
}
|
||||
|
||||
// save the settings for the next time
|
||||
prefs.saveInputMode(mInputMode);
|
||||
prefs.saveTextCase(mTextCase);
|
||||
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
}
|
||||
|
||||
private void setComposingTextFromCurrentSuggestion() {
|
||||
if (!isSuggestionViewHidden()) {
|
||||
currentInputConnection.setComposingText(mSuggestionView.getCurrentSuggestion(), 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void nextLang() {
|
||||
if (mEditing == EDITING_STRICT_NUMERIC || mEditing == EDITING_DIALER) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearSuggestions();
|
||||
|
||||
// select the next language
|
||||
int previousLangId = mEnabledLanguages.indexOf(mLanguage.getId());
|
||||
int nextLangId = previousLangId == -1 ? 0 : (previousLangId + 1) % mEnabledLanguages.size();
|
||||
mLanguage = LanguageCollection.getLanguage(mEnabledLanguages.get(nextLangId));
|
||||
|
||||
validateLanguages();
|
||||
|
||||
// save it for the next time
|
||||
prefs.saveInputLanguage(mLanguage.getId());
|
||||
|
||||
UI.updateStatusIcon(this, mLanguage, mInputMode, mTextCase);
|
||||
}
|
||||
|
||||
|
||||
private void determineAllowedInputModes(EditorInfo inputField) {
|
||||
allowedInputModes = InputFieldHelper.determineInputModes(inputField);
|
||||
|
||||
int lastInputMode = prefs.getInputMode();
|
||||
if (allowedInputModes.contains(lastInputMode)) {
|
||||
mInputMode = lastInputMode;
|
||||
} else if (allowedInputModes.contains(TraditionalT9.MODE_ABC)) {
|
||||
mInputMode = TraditionalT9.MODE_ABC;
|
||||
} else {
|
||||
mInputMode = allowedInputModes.get(0);
|
||||
}
|
||||
|
||||
if (InputFieldHelper.isDialerField(inputField)) {
|
||||
mEditing = EDITING_DIALER;
|
||||
} else if (mInputMode == TraditionalT9.MODE_123 && allowedInputModes.size() == 1) {
|
||||
mEditing = EDITING_STRICT_NUMERIC;
|
||||
} else {
|
||||
mEditing = InputFieldHelper.isFilterTextField(inputField) ? EDITING_NOSHOW : EDITING;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void determineAllowedTextCases() {
|
||||
// @todo: determine case from input
|
||||
|
||||
allowedTextCases = new ArrayList<>();
|
||||
|
||||
if (mInputMode == TraditionalT9.MODE_PREDICTIVE) {
|
||||
allowedTextCases.add(TraditionalT9.CASE_LOWER);
|
||||
allowedTextCases.add(TraditionalT9.CASE_CAPITALIZE);
|
||||
allowedTextCases.add(TraditionalT9.CASE_UPPER);
|
||||
} else if (mInputMode == TraditionalT9.MODE_ABC) {
|
||||
allowedTextCases.add(TraditionalT9.CASE_LOWER);
|
||||
allowedTextCases.add(TraditionalT9.CASE_UPPER);
|
||||
} else {
|
||||
allowedTextCases.add(TraditionalT9.CASE_LOWER);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void showAddWord() {
|
||||
if (mInputMode != MODE_PREDICTIVE) {
|
||||
UI.toastLong(this, R.string.add_word_only_in_predictive_mode);
|
||||
return;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////
|
||||
// @todo: remove this try..catch in #55 and display the dialog
|
||||
try {
|
||||
DictionaryDb.insertWord(this, "a", mLanguage.getId());
|
||||
} catch (InsertBlankWordException e) {
|
||||
Logger.e("tt9/showAddWord", e.getMessage());
|
||||
UI.toastLong(this, R.string.add_word_blank);
|
||||
return;
|
||||
} catch (InvalidLanguageException e) {
|
||||
Logger.e("tt9/showAddWord", e.getMessage());
|
||||
UI.toastLong(this, R.string.add_word_invalid_language);
|
||||
return;
|
||||
} catch (NotActiveException e) {
|
||||
UI.toastLong(this, e.getMessage());
|
||||
return;
|
||||
}
|
||||
//////////////////////////////////////////////////////////////
|
||||
|
||||
clearSuggestions();
|
||||
|
||||
String template = "";
|
||||
|
||||
// @todo: get the current word template from the input connection
|
||||
// template = getSurroundingWord();
|
||||
|
||||
UI.showAddWordDialog(this, mLanguage.getId(), template);
|
||||
}
|
||||
|
||||
|
||||
private void restoreLastWordIfAny() {
|
||||
// mAddingWord = false;
|
||||
String word = prefs.getLastWord();
|
||||
if (word.equals("")) {
|
||||
prefs.saveLastWord("");
|
||||
|
||||
// @todo: push the word to the text field
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* createSoftKeyView
|
||||
* Generates the actual UI of TT9.
|
||||
*/
|
||||
protected View createSoftKeyView() {
|
||||
if (softKeyView == null) {
|
||||
softKeyView = getLayoutInflater().inflate(R.layout.mainview, null);
|
||||
}
|
||||
softKeyHandler.changeView(softKeyView);
|
||||
return softKeyView;
|
||||
}
|
||||
|
||||
|
||||
private void displaySoftKeyMenu() {
|
||||
createSoftKeyView();
|
||||
softKeyView.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
public class InvalidLanguageException extends Exception {
|
||||
public InvalidLanguageException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
87
src/io/github/sspanak/tt9/languages/Language.java
Normal file
87
src/io/github/sspanak/tt9/languages/Language.java
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Locale;
|
||||
|
||||
|
||||
public class Language {
|
||||
protected int id;
|
||||
protected String name;
|
||||
protected Locale locale;
|
||||
protected int icon;
|
||||
protected String dictionaryFile;
|
||||
protected int abcLowerCaseIcon;
|
||||
protected int abcUpperCaseIcon;
|
||||
protected ArrayList<ArrayList<String>> characterMap = new ArrayList<>();
|
||||
|
||||
final public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
final public Locale getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
final public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
final public int getIcon() {
|
||||
return icon;
|
||||
}
|
||||
|
||||
final public String getDictionaryFile() {
|
||||
return dictionaryFile;
|
||||
}
|
||||
|
||||
final public int getAbcIcon(boolean lowerCase) {
|
||||
return lowerCase ? abcLowerCaseIcon : abcUpperCaseIcon;
|
||||
}
|
||||
|
||||
public ArrayList<String> getKeyCharacters(int key) {
|
||||
return getKeyCharacters(key, true);
|
||||
}
|
||||
|
||||
public ArrayList<String> getKeyCharacters(int key, boolean lowerCase) {
|
||||
if (key < 0 || key >= characterMap.size()) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
|
||||
ArrayList<String> chars = lowerCase ? new ArrayList<>(characterMap.get(key)) : getUpperCaseChars(key);
|
||||
if (chars.size() > 0) {
|
||||
chars.add(String.valueOf(key));
|
||||
}
|
||||
|
||||
return chars;
|
||||
}
|
||||
|
||||
private ArrayList<String> getUpperCaseChars(int mapId) {
|
||||
ArrayList<String> uppercaseChars = new ArrayList<>();
|
||||
for (String ch : characterMap.get(mapId)) {
|
||||
uppercaseChars.add(ch.toUpperCase(locale));
|
||||
}
|
||||
|
||||
return uppercaseChars;
|
||||
}
|
||||
|
||||
public String getDigitSequenceForWord(String word) throws Exception {
|
||||
StringBuilder sequence = new StringBuilder();
|
||||
String lowerCaseWord = word.toLowerCase(locale);
|
||||
|
||||
for (int i = 0; i < lowerCaseWord.length(); i++) {
|
||||
for (int key = 0; key <= 9; key++) {
|
||||
if (getKeyCharacters(key).contains(Character.toString(lowerCaseWord.charAt(i)))) {
|
||||
sequence.append(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (word.length() != sequence.length()) {
|
||||
throw new Exception(
|
||||
"Failed generating digit sequence for word: '" + word + "'. Some characters are not supported in language: " + name
|
||||
);
|
||||
}
|
||||
|
||||
return sequence.toString();
|
||||
}
|
||||
}
|
||||
71
src/io/github/sspanak/tt9/languages/LanguageCollection.java
Normal file
71
src/io/github/sspanak/tt9/languages/LanguageCollection.java
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.languages.definitions.*;
|
||||
|
||||
public class LanguageCollection {
|
||||
private static LanguageCollection self;
|
||||
|
||||
private final HashMap<Integer, Language> languages = new HashMap<>();
|
||||
|
||||
private LanguageCollection() {
|
||||
List<Class<? extends Language>> languageList = Arrays.asList(
|
||||
// Add languages here, to enable them in the UI and
|
||||
// please, maintain the alphabetical order.
|
||||
Bulgarian.class,
|
||||
English.class,
|
||||
French.class,
|
||||
German.class,
|
||||
Italian.class,
|
||||
Russian.class,
|
||||
Ukrainian.class
|
||||
);
|
||||
|
||||
// initialize the language objects from the class list above
|
||||
for (Class<? extends Language> languageClass : languageList) {
|
||||
try {
|
||||
Language lang = languageClass.newInstance();
|
||||
if (languages.containsKey(lang.getId())) {
|
||||
throw new Exception("Duplicate language ID: " + lang.getId() + " for language: " + lang.getName());
|
||||
}
|
||||
languages.put(lang.getId(), lang);
|
||||
} catch (Exception e) {
|
||||
Logger.e("tt9.LanguageCollection", "Skipping an invalid language. " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static LanguageCollection getInstance() {
|
||||
if (self == null) {
|
||||
self = new LanguageCollection();
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
public static Language getLanguage(int langId) {
|
||||
if (getInstance().languages.containsKey(langId)) {
|
||||
return getInstance().languages.get(langId);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static ArrayList<Language> getAll(ArrayList<Integer> languageIds) {
|
||||
ArrayList<Language> langList = new ArrayList<>();
|
||||
|
||||
for (int languageId : languageIds) {
|
||||
Language lang = getLanguage(languageId);
|
||||
if (lang != null) {
|
||||
langList.add(lang);
|
||||
}
|
||||
}
|
||||
|
||||
return langList;
|
||||
}
|
||||
}
|
||||
19
src/io/github/sspanak/tt9/languages/Punctuation.java
Normal file
19
src/io/github/sspanak/tt9/languages/Punctuation.java
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package io.github.sspanak.tt9.languages;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class Punctuation {
|
||||
final public static ArrayList<String> Main = new ArrayList<>(Arrays.asList(
|
||||
".", ",", "-", "?", "!", ")", "(", "'", "\"", "@", ":", "/", ";", "%"
|
||||
));
|
||||
|
||||
final public static ArrayList<String> Secondary = new ArrayList<>(Arrays.asList(
|
||||
" ", "+", "\n"
|
||||
));
|
||||
|
||||
final public static ArrayList<String> Emoticons = new ArrayList<>(Arrays.asList(
|
||||
"👍", ":)", ":D", ";)", ":(", ":P"
|
||||
));
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
|
||||
public class Bulgarian extends Language {
|
||||
public Bulgarian() {
|
||||
id = 7;
|
||||
name = "български";
|
||||
locale = new Locale("bg","BG");
|
||||
dictionaryFile = "bg-utf8.txt";
|
||||
icon = R.drawable.ime_lang_bg;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
new ArrayList<>(Arrays.asList("а", "б", "в", "г")), // 2
|
||||
new ArrayList<>(Arrays.asList("д", "е", "ж", "з")), // 3
|
||||
new ArrayList<>(Arrays.asList("и", "й", "к", "л", "ѝ")), // 4
|
||||
new ArrayList<>(Arrays.asList("м", "н", "о", "п")), // 5
|
||||
new ArrayList<>(Arrays.asList("р", "с", "т", "у")), // 6
|
||||
new ArrayList<>(Arrays.asList("ф", "х", "ц", "ч")), // 7
|
||||
new ArrayList<>(Arrays.asList("ш", "щ", "ъ")), // 8
|
||||
new ArrayList<>(Arrays.asList("ь", "ю", "я")) // 9
|
||||
));
|
||||
}
|
||||
}
|
||||
34
src/io/github/sspanak/tt9/languages/definitions/English.java
Normal file
34
src/io/github/sspanak/tt9/languages/definitions/English.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
|
||||
public class English extends Language {
|
||||
public English() {
|
||||
id = 1;
|
||||
name = "English";
|
||||
locale = Locale.ENGLISH;
|
||||
dictionaryFile = "en-utf8.txt";
|
||||
icon = R.drawable.ime_lang_en;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_latin_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_latin_upper;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
new ArrayList<>(Arrays.asList("a", "b", "c")), // 2
|
||||
new ArrayList<>(Arrays.asList("d", "e", "f")), // 3
|
||||
new ArrayList<>(Arrays.asList("g", "h", "i")), // 4
|
||||
new ArrayList<>(Arrays.asList("j", "k", "l")), // 5
|
||||
new ArrayList<>(Arrays.asList("m", "n", "o")), // 6
|
||||
new ArrayList<>(Arrays.asList("p", "q", "r", "s")), // 7
|
||||
new ArrayList<>(Arrays.asList("t", "u", "v")), // 8
|
||||
new ArrayList<>(Arrays.asList("w", "x", "y", "z")) // 9
|
||||
));
|
||||
}
|
||||
}
|
||||
25
src/io/github/sspanak/tt9/languages/definitions/French.java
Normal file
25
src/io/github/sspanak/tt9/languages/definitions/French.java
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
|
||||
public class French extends English {
|
||||
public French() {
|
||||
super();
|
||||
|
||||
id = 4;
|
||||
name = "Français";
|
||||
locale = Locale.FRENCH;
|
||||
dictionaryFile = "fr-utf8.txt";
|
||||
icon = R.drawable.ime_lang_fr;
|
||||
|
||||
characterMap.get(2).addAll(Arrays.asList("à", "â", "æ", "ç"));
|
||||
characterMap.get(3).addAll(Arrays.asList("é", "è", "ê", "ë"));
|
||||
characterMap.get(4).addAll(Arrays.asList("î", "ï"));
|
||||
characterMap.get(6).addAll(Arrays.asList("ô", "œ"));
|
||||
characterMap.get(8).addAll(Arrays.asList("ù", "û", "ü"));
|
||||
characterMap.get(9).add("ÿ");
|
||||
}
|
||||
}
|
||||
22
src/io/github/sspanak/tt9/languages/definitions/German.java
Normal file
22
src/io/github/sspanak/tt9/languages/definitions/German.java
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
|
||||
public class German extends English {
|
||||
public German() {
|
||||
super();
|
||||
|
||||
id = 3;
|
||||
name = "Deutsch";
|
||||
locale = Locale.GERMAN;
|
||||
dictionaryFile = "de-utf8.txt";
|
||||
icon = R.drawable.ime_lang_de;
|
||||
|
||||
characterMap.get(2).add("ä");
|
||||
characterMap.get(6).add("ö");
|
||||
characterMap.get(7).add("ß");
|
||||
characterMap.get(8).add("ü");
|
||||
}
|
||||
}
|
||||
24
src/io/github/sspanak/tt9/languages/definitions/Italian.java
Normal file
24
src/io/github/sspanak/tt9/languages/definitions/Italian.java
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
|
||||
public class Italian extends English {
|
||||
public Italian() {
|
||||
super();
|
||||
|
||||
id = 5;
|
||||
name = "Italiano";
|
||||
locale = Locale.ITALIAN;
|
||||
dictionaryFile = "it-utf8.txt";
|
||||
icon = R.drawable.ime_lang_it;
|
||||
|
||||
characterMap.get(2).add("à");
|
||||
characterMap.get(3).addAll(Arrays.asList("é", "è"));
|
||||
characterMap.get(4).addAll(Arrays.asList("ì", "í", "î"));
|
||||
characterMap.get(6).addAll(Arrays.asList("ò", "ó"));
|
||||
characterMap.get(8).addAll(Arrays.asList("ù", "ú"));
|
||||
}
|
||||
}
|
||||
34
src/io/github/sspanak/tt9/languages/definitions/Russian.java
Normal file
34
src/io/github/sspanak/tt9/languages/definitions/Russian.java
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
|
||||
public class Russian extends Language {
|
||||
public Russian() {
|
||||
id = 2;
|
||||
name = "русский";
|
||||
locale = new Locale("ru","RU");
|
||||
dictionaryFile = "ru-utf8.txt";
|
||||
icon = R.drawable.ime_lang_ru;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
new ArrayList<>(Arrays.asList("а", "б", "в", "г")), // 2
|
||||
new ArrayList<>(Arrays.asList("д", "е", "ё", "ж", "з")), // 3
|
||||
new ArrayList<>(Arrays.asList("и", "й", "к", "л")), // 4
|
||||
new ArrayList<>(Arrays.asList("м", "н", "о", "п")), // 5
|
||||
new ArrayList<>(Arrays.asList("р", "с", "т", "у")), // 6
|
||||
new ArrayList<>(Arrays.asList("ф", "х", "ц", "ч")), // 7
|
||||
new ArrayList<>(Arrays.asList("ш", "щ", "ъ", "ы")), // 8
|
||||
new ArrayList<>(Arrays.asList("ь", "э", "ю", "я")) // 9
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package io.github.sspanak.tt9.languages.definitions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.Punctuation;
|
||||
|
||||
public class Ukrainian extends Language {
|
||||
public Ukrainian() {
|
||||
id = 6;
|
||||
name = "українська";
|
||||
locale = new Locale("uk","UA");
|
||||
dictionaryFile = "uk-utf8.txt";
|
||||
icon = R.drawable.ime_lang_uk;
|
||||
abcLowerCaseIcon = R.drawable.ime_lang_cyrillic_lower;
|
||||
abcUpperCaseIcon = R.drawable.ime_lang_cyrillic_upper;
|
||||
|
||||
characterMap = new ArrayList<>(Arrays.asList(
|
||||
Punctuation.Secondary, // 0
|
||||
Punctuation.Main, // 1
|
||||
new ArrayList<>(Arrays.asList("а", "б", "в", "г", "ґ")), // 2
|
||||
new ArrayList<>(Arrays.asList("д", "е", "є", "ж", "з")), // 3
|
||||
new ArrayList<>(Arrays.asList("и", "і", "ї", "й", "к", "л")), // 4
|
||||
new ArrayList<>(Arrays.asList("м", "н", "о", "п")), // 5
|
||||
new ArrayList<>(Arrays.asList("р", "с", "т", "у")), // 6
|
||||
new ArrayList<>(Arrays.asList("ф", "х", "ц", "ч")), // 7
|
||||
new ArrayList<>(Arrays.asList("ш", "щ")), // 8
|
||||
new ArrayList<>(Arrays.asList("ь", "ю", "я")) // 9
|
||||
));
|
||||
}
|
||||
}
|
||||
|
|
@ -6,19 +6,21 @@ import androidx.preference.PreferenceManager;
|
|||
|
||||
import android.view.KeyEvent;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
|
||||
|
||||
public class T9Preferences {
|
||||
public static final int MAX_LANGUAGES = 32;
|
||||
|
||||
private static T9Preferences self;
|
||||
|
||||
private SharedPreferences prefs;
|
||||
private SharedPreferences.Editor prefsEditor;
|
||||
|
||||
public static final int CASE_LOWER = 0;
|
||||
public static final int CASE_CAPITALIZE = 1;
|
||||
public static final int CASE_UPPER = 2;
|
||||
|
||||
public static final int MODE_PREDICTIVE = 0;
|
||||
public static final int MODE_ABC = 1;
|
||||
public static final int MODE_123 = 2;
|
||||
private final SharedPreferences prefs;
|
||||
private final SharedPreferences.Editor prefsEditor;
|
||||
|
||||
public T9Preferences (Context context) {
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
|
||||
|
|
@ -33,77 +35,141 @@ public class T9Preferences {
|
|||
return self;
|
||||
}
|
||||
|
||||
public int getEnabledLanguages() {
|
||||
return prefs.getInt("pref_enabled_languages", 1);
|
||||
/************* VALIDATORS *************/
|
||||
|
||||
private boolean doesLanguageExist(int langId) {
|
||||
return LanguageCollection.getLanguage(langId) != null;
|
||||
}
|
||||
|
||||
public T9Preferences setEnabledLanguages(int languageMask) {
|
||||
private boolean isLanguageInRange(int langId) {
|
||||
return langId > 0 && langId <= MAX_LANGUAGES;
|
||||
}
|
||||
|
||||
private boolean validateSavedLanguage(int langId, String logTag) {
|
||||
if (!doesLanguageExist(langId)) {
|
||||
Logger.w(logTag, "Not saving invalid language with ID: " + langId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isLanguageInRange(langId)) {
|
||||
Logger.w(logTag, "Valid language ID range is [0, 31]. Not saving out-of-range language: " + langId);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean isIntInList(int number, ArrayList<Integer> list, String logTag, String logMsg) {
|
||||
if (!list.contains(number)) {
|
||||
Logger.w(logTag, logMsg);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/************* PREFERENCES OPERATIONS *************/
|
||||
|
||||
public ArrayList<Integer> getEnabledLanguages() {
|
||||
int languageMask = prefs.getInt("pref_enabled_languages", 1);
|
||||
ArrayList<Integer>languageIds = new ArrayList<>();
|
||||
|
||||
for (int langId = 1; langId < MAX_LANGUAGES; langId++) {
|
||||
int maskBit = 1 << (langId - 1);
|
||||
if ((maskBit & languageMask) != 0) {
|
||||
languageIds.add(langId);
|
||||
}
|
||||
}
|
||||
|
||||
return languageIds;
|
||||
}
|
||||
|
||||
public void saveEnabledLanguages(ArrayList<Integer> languageIds) {
|
||||
int languageMask = 0;
|
||||
for (Integer langId : languageIds) {
|
||||
if (!validateSavedLanguage(langId, "tt9/saveEnabledLanguages")){
|
||||
continue;
|
||||
}
|
||||
|
||||
int languageMaskBit = 1 << (langId - 1);
|
||||
languageMask |= languageMaskBit;
|
||||
}
|
||||
|
||||
prefsEditor.putInt("pref_enabled_languages", languageMask);
|
||||
prefsEditor.apply();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
// public int getInputCase() {
|
||||
// return prefs.getInt("pref_input_case", CASE_CAPITALIZE);
|
||||
// }
|
||||
public int getTextCase() {
|
||||
return prefs.getInt("pref_text_case", TraditionalT9.CASE_LOWER);
|
||||
}
|
||||
|
||||
public void saveTextCase(int textCase) {
|
||||
boolean isTextCaseValid = isIntInList(
|
||||
textCase,
|
||||
new ArrayList<>(Arrays.asList(TraditionalT9.CASE_CAPITALIZE, TraditionalT9.CASE_LOWER, TraditionalT9.CASE_UPPER)),
|
||||
"tt9/saveTextCase",
|
||||
"Not saving invalid text case: " + textCase
|
||||
);
|
||||
|
||||
if (isTextCaseValid) {
|
||||
prefsEditor.putInt("pref_text_case", textCase);
|
||||
prefsEditor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public int getInputLanguage() {
|
||||
return prefs.getInt("pref_input_language", 1);
|
||||
}
|
||||
|
||||
public T9Preferences setInputLanguage(int language) {
|
||||
prefsEditor.putInt("pref_input_language", language);
|
||||
prefsEditor.apply();
|
||||
|
||||
return this;
|
||||
public void saveInputLanguage(int language) {
|
||||
if (validateSavedLanguage(language, "tt9/saveInputLanguage")){
|
||||
prefsEditor.putInt("pref_input_language", language);
|
||||
prefsEditor.apply();
|
||||
}
|
||||
}
|
||||
|
||||
public int getInputMode() {
|
||||
return prefs.getInt("pref_input_mode", MODE_PREDICTIVE);
|
||||
return prefs.getInt("pref_input_mode", TraditionalT9.MODE_PREDICTIVE);
|
||||
}
|
||||
|
||||
public T9Preferences setInputMode(int mode) throws Exception {
|
||||
if (mode != MODE_PREDICTIVE && mode != MODE_ABC && mode != MODE_123) {
|
||||
throw new Exception("Invalid input mode: '" + mode + "'");
|
||||
public void saveInputMode(int mode) {
|
||||
boolean isModeValid = isIntInList(
|
||||
mode,
|
||||
new ArrayList<>(Arrays.asList(TraditionalT9.MODE_123, TraditionalT9.MODE_ABC, TraditionalT9.MODE_PREDICTIVE)),
|
||||
"tt9/saveInputMode",
|
||||
"Not saving invalid text case: " + mode
|
||||
);
|
||||
|
||||
if (isModeValid) {
|
||||
prefsEditor.putInt("pref_input_mode", mode);
|
||||
prefsEditor.apply();
|
||||
}
|
||||
|
||||
prefsEditor.putInt("pref_input_mode", mode);
|
||||
prefsEditor.apply();
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public int getKeyBackspace() {
|
||||
return prefs.getInt("pref_key_backspace", KeyEvent.KEYCODE_DEL);
|
||||
return prefs.getInt("pref_key_backspace", KeyEvent.KEYCODE_BACK);
|
||||
}
|
||||
|
||||
// public int getKeyInputMode() {
|
||||
// return prefs.getInt("pref_key_inputmode", KeyEvent.KEYCODE_POUND);
|
||||
// }
|
||||
public int getKeyInputMode() { return prefs.getInt("pref_key_input_mode", KeyEvent.KEYCODE_POUND); }
|
||||
|
||||
// public int getKeyOtherActions() {
|
||||
// return prefs.getInt("pref_key_other_actions", KeyEvent.KEYCODE_CALL);
|
||||
// }
|
||||
public int getKeyOtherActions() { return prefs.getInt("pref_key_other_actions", KeyEvent.KEYCODE_STAR); }
|
||||
|
||||
// public boolean getSoftBackspaceEnabled() {
|
||||
// return prefs.getBoolean("pref_softkey_backspace", true);
|
||||
// }
|
||||
|
||||
// public boolean getSoftPrefsEnabled() {
|
||||
// return prefs.getBoolean("pref_softkey_prefs", true);
|
||||
// }
|
||||
public int getSuggestionsMin() { return 8; }
|
||||
public int getSuggestionsMax() { return 20; }
|
||||
|
||||
|
||||
public String getLastWord() {
|
||||
return prefs.getString("last_word", "");
|
||||
}
|
||||
|
||||
public T9Preferences setLastWord(String lastWord) {
|
||||
public void saveLastWord(String lastWord) {
|
||||
// "last_word" was part of the original Preferences implementation.
|
||||
// It is weird, but it is simple and it works, so I decided to keep it.
|
||||
prefsEditor.putString("last_word", lastWord);
|
||||
prefsEditor.apply();
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.settings;
|
||||
package io.github.sspanak.tt9.settings_legacy;
|
||||
|
||||
// http://stackoverflow.com/a/8488691
|
||||
|
||||
|
|
@ -22,7 +22,7 @@ public class CustomInflater {
|
|||
if (token == XmlPullParser.START_TAG) {
|
||||
if (!parser.getName().equals("Settings")) {
|
||||
//prepend package
|
||||
Class aClass = Class.forName("io.github.sspanak.tt9.settings."+parser.getName());
|
||||
Class aClass = Class.forName("io.github.sspanak.tt9.settings_legacy."+parser.getName());
|
||||
Class<?>[] params = new Class[]{Context.class, AttributeSet.class, isettings.getClass()};
|
||||
Constructor<?> constructor = aClass.getConstructor(params);
|
||||
try {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.settings;
|
||||
package io.github.sspanak.tt9.settings_legacy;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.settings;
|
||||
package io.github.sspanak.tt9.settings_legacy;
|
||||
|
||||
// https://github.com/codepath/android_guides/wiki/Using-an-ArrayAdapter-with-ListView
|
||||
|
||||
|
|
@ -1,13 +1,11 @@
|
|||
package io.github.sspanak.tt9.settings;
|
||||
package io.github.sspanak.tt9.settings_legacy;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
public class SettingList extends Setting {
|
||||
String[] entries;
|
||||
|
|
@ -30,12 +28,6 @@ public class SettingList extends Setting {
|
|||
}
|
||||
}
|
||||
|
||||
if (id.equals("pref_inputmode")){
|
||||
if (isettings[0] != null)
|
||||
value = (Integer)isettings[0];
|
||||
else
|
||||
value = defaultValue;
|
||||
}
|
||||
widgetID = R.layout.preference_dialog;
|
||||
layout = R.layout.setting_widget;
|
||||
}
|
||||
|
|
@ -55,14 +47,6 @@ public class SettingList extends Setting {
|
|||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
|
||||
if (id.equals("pref_inputmode")) {
|
||||
try {
|
||||
T9Preferences.getInstance(context).setInputMode(entryValues[which]);
|
||||
} catch (Exception e) {
|
||||
Log.e("SettingsList", e.getMessage());
|
||||
}
|
||||
}
|
||||
value = entryValues[which];
|
||||
dialog.dismiss();
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9.settings;
|
||||
package io.github.sspanak.tt9.settings_legacy;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
|
|
@ -6,18 +6,19 @@ import android.content.DialogInterface;
|
|||
import android.util.AttributeSet;
|
||||
import android.widget.TextView;
|
||||
|
||||
import io.github.sspanak.tt9.LangHelper;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
public class SettingMultiList extends SettingList {
|
||||
boolean[] selectedEntries = new boolean[0];
|
||||
boolean[] selectedEntries;
|
||||
|
||||
public SettingMultiList (Context context, AttributeSet attrs, Object[] isettings) {
|
||||
super(context, attrs, isettings);
|
||||
selectedEntries = new boolean[entries.length];
|
||||
for (LangHelper.LANGUAGE l : LangHelper.buildLangs((Integer)isettings[1])) {
|
||||
selectedEntries[l.index] = true;
|
||||
for (int langId : T9Preferences.getInstance(context).getEnabledLanguages()) {
|
||||
selectedEntries[langId - 1] = true; // languages are 1-based, unlike arrays
|
||||
}
|
||||
summary = buildItems();
|
||||
}
|
||||
|
|
@ -38,7 +39,7 @@ public class SettingMultiList extends SettingList {
|
|||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if (id.equals("pref_lang_support")) {
|
||||
T9Preferences.getInstance(context).setEnabledLanguages(LangHelper.shrinkLangs(buildSelection()));
|
||||
T9Preferences.getInstance(context).saveEnabledLanguages(buildSelection());
|
||||
}
|
||||
summary = buildItems();
|
||||
dialog.dismiss();
|
||||
|
|
@ -55,19 +56,17 @@ public class SettingMultiList extends SettingList {
|
|||
builderMulti.show();
|
||||
}
|
||||
|
||||
private int[] buildSelection(){
|
||||
int count = 0;
|
||||
for (boolean b: selectedEntries) {if (b) count++;}
|
||||
int[] selection = new int[count];
|
||||
count = 0;
|
||||
private ArrayList<Integer> buildSelection(){
|
||||
ArrayList<Integer> selection = new ArrayList<>();
|
||||
for (int x=0;x<selectedEntries.length;x++) {
|
||||
if (selectedEntries[x]) {
|
||||
selection[count] = entryValues[x];
|
||||
count++;
|
||||
selection.add(entryValues[x]);
|
||||
}
|
||||
}
|
||||
if (selection.length < 1)
|
||||
return new int[] {entryValues[0]};
|
||||
|
||||
if (selection.size() < 1) {
|
||||
selection.add(entryValues[0]);
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
private String buildItems() {
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
package io.github.sspanak.tt9;
|
||||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.View;
|
||||
import android.widget.EditText;
|
||||
|
||||
import io.github.sspanak.tt9.db.DBException;
|
||||
import io.github.sspanak.tt9.db.T9DB;
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
|
||||
public class AddWordAct extends Activity {
|
||||
|
|
@ -30,9 +29,9 @@ public class AddWordAct extends Activity {
|
|||
|
||||
lang = i.getIntExtra("io.github.sspanak.tt9.lang", -1);
|
||||
if (lang == -1) {
|
||||
Log.e("AddWordAct.onCreate", "lang is invalid. How?");
|
||||
Logger.e("AddWordAct.onCreate", "lang is invalid. How?");
|
||||
}
|
||||
// Log.d("AddWord", "data.get: " + word);
|
||||
// Logger.d("AddWord", "data.get: " + word);
|
||||
et.setText(origword);
|
||||
et.setSelection(origword.length());
|
||||
setContentView(v);
|
||||
|
|
@ -41,34 +40,24 @@ public class AddWordAct extends Activity {
|
|||
|
||||
public void addWordButton(View v) {
|
||||
EditText et = (EditText) main.findViewById(R.id.add_word_text);
|
||||
// Log.d("AddWordAct", "adding word: " + et.getText());
|
||||
// Logger.d("AddWordAct", "adding word: " + et.getText());
|
||||
doAddWord(et.getText().toString());
|
||||
this.finish();
|
||||
}
|
||||
|
||||
public void doAddWord(String text) {
|
||||
try {
|
||||
T9DB.getInstance(this).addWord(text, LangHelper.LANGUAGE.get(lang));
|
||||
} catch (DBException e) {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
String msg = e.getMessage();
|
||||
//Log.e("AddWord.doAddWord", msg);
|
||||
builder.setMessage(msg).setTitle(R.string.add_word)
|
||||
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
dialog.dismiss();
|
||||
}
|
||||
});
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.show();
|
||||
DictionaryDb.insertWord(this, text, LanguageCollection.getLanguage(lang).getId());
|
||||
} catch (Exception e) {
|
||||
UI.toast(this, e.getMessage());
|
||||
return;
|
||||
}
|
||||
T9Preferences.getInstance(this).setLastWord(text);
|
||||
T9Preferences.getInstance(this).saveLastWord(text);
|
||||
}
|
||||
|
||||
|
||||
public void cancelButton(View v) {
|
||||
// Log.d("AddWordAct", "Cancelled...");
|
||||
// Logger.d("AddWordAct", "Cancelled...");
|
||||
this.finish();
|
||||
}
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9;
|
||||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
|
@ -10,6 +10,10 @@ import android.view.View;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
|
||||
public class CandidateView extends View {
|
||||
|
||||
|
|
@ -124,6 +128,10 @@ public class CandidateView extends View {
|
|||
|
||||
for (int i = 0; i < count; i++) {
|
||||
String suggestion = mSuggestions.get(i);
|
||||
if (suggestion.equals("\n")) {
|
||||
suggestion = "⏎"; // make it more clear it is a new line
|
||||
}
|
||||
|
||||
float textWidth = paint.measureText(suggestion);
|
||||
final int wordWidth = (int) textWidth + X_GAP * 2;
|
||||
|
||||
|
|
@ -170,11 +178,15 @@ public class CandidateView extends View {
|
|||
invalidate();
|
||||
}
|
||||
|
||||
protected void setSuggestions(List<String> suggestions, int initialSel) {
|
||||
public String getCurrentSuggestion() {
|
||||
return mSuggestions != null && mSelectedIndex >= 0 && mSelectedIndex < mSuggestions.size() ? mSuggestions.get(mSelectedIndex) : "";
|
||||
}
|
||||
|
||||
public void setSuggestions(List<String> suggestions, int initialSel) {
|
||||
clear();
|
||||
if (suggestions != null) {
|
||||
mSuggestions = suggestions;
|
||||
mSelectedIndex = initialSel;
|
||||
mSelectedIndex = Math.max(initialSel, 0);
|
||||
}
|
||||
scrollTo(0, 0);
|
||||
mTargetScrollX = 0;
|
||||
|
|
@ -184,13 +196,30 @@ public class CandidateView extends View {
|
|||
requestLayout();
|
||||
}
|
||||
|
||||
public void changeCase(int textCase, Locale locale) {
|
||||
ArrayList<String> newSuggestions = new ArrayList<>();
|
||||
|
||||
for (String s : mSuggestions) {
|
||||
if (textCase == TraditionalT9.CASE_LOWER) {
|
||||
newSuggestions.add(s.toLowerCase(locale));
|
||||
} else if (textCase == TraditionalT9.CASE_CAPITALIZE) {
|
||||
String cs = s.substring(0, 1).toUpperCase(locale) + s.substring(1).toLowerCase(locale);
|
||||
newSuggestions.add(cs);
|
||||
} else {
|
||||
newSuggestions.add(s.toUpperCase(locale));
|
||||
}
|
||||
}
|
||||
|
||||
setSuggestions(newSuggestions, mSelectedIndex);
|
||||
}
|
||||
|
||||
protected void clear() {
|
||||
mSuggestions = EMPTY_LIST;
|
||||
mSelectedIndex = -1;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected void scrollToSuggestion(int increment) {
|
||||
public void scrollToSuggestion(int increment) {
|
||||
if (mSuggestions != null && mSuggestions.size() > 1) {
|
||||
mSelectedIndex = mSelectedIndex + increment;
|
||||
if (mSelectedIndex == mSuggestions.size()) {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package io.github.sspanak.tt9;
|
||||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
|
|
@ -7,28 +7,31 @@ import android.app.ProgressDialog;
|
|||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.database.DatabaseUtils.InsertHelper;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.stackoverflow.answer.UnicodeBOMInputStream;
|
||||
|
||||
import io.github.sspanak.tt9.LangHelper.LANGUAGE;
|
||||
import io.github.sspanak.tt9.db.T9DB;
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.db.DictionaryDb;
|
||||
import io.github.sspanak.tt9.db.Word;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
import io.github.sspanak.tt9.languages.LanguageCollection;
|
||||
import io.github.sspanak.tt9.preferences.T9Preferences;
|
||||
import io.github.sspanak.tt9.settings.CustomInflater;
|
||||
import io.github.sspanak.tt9.settings.Setting;
|
||||
import io.github.sspanak.tt9.settings.SettingAdapter;
|
||||
import io.github.sspanak.tt9.settings_legacy.CustomInflater;
|
||||
import io.github.sspanak.tt9.settings_legacy.Setting;
|
||||
import io.github.sspanak.tt9.settings_legacy.SettingAdapter;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.Closeable;
|
||||
|
|
@ -38,25 +41,21 @@ import java.io.FileNotFoundException;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
|
||||
public class TraditionalT9Settings extends ListActivity implements DialogInterface.OnCancelListener {
|
||||
|
||||
AsyncTask<String, Integer, Reply> task = null;
|
||||
final static String dictname = "%s-utf8.txt";
|
||||
final static String userdictname = "user.%s.dict";
|
||||
final static String sddir = "tt9";
|
||||
|
||||
final int BACKUP_Q_LIMIT = 1000;
|
||||
|
||||
Context mContext = null;
|
||||
|
||||
public class LoadException extends Exception {
|
||||
public static class LoadException extends Exception {
|
||||
private static final long serialVersionUID = 3323913652550046354L;
|
||||
|
||||
public LoadException() {
|
||||
|
|
@ -64,13 +63,13 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
}
|
||||
}
|
||||
|
||||
private class Reply {
|
||||
private static class Reply {
|
||||
public boolean status;
|
||||
private List<String> msgs;
|
||||
private final List<String> msgs;
|
||||
|
||||
protected Reply() {
|
||||
this.status = true;
|
||||
this.msgs = new ArrayList<String>(4);
|
||||
this.msgs = new ArrayList<>(4);
|
||||
}
|
||||
|
||||
protected void addMsg(String msg) throws LoadException {
|
||||
|
|
@ -88,17 +87,17 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
|
||||
private void finishAndShowError(ProgressDialog pd, Reply result, int title){
|
||||
if (pd != null) {
|
||||
// Log.d("onPostExecute", "pd");
|
||||
// Logger.d("onPostExecute", "pd");
|
||||
if (pd.isShowing()) {
|
||||
pd.dismiss();
|
||||
}
|
||||
}
|
||||
if (result == null) {
|
||||
// bad thing happened
|
||||
Log.e("onPostExecute", "Bad things happen?");
|
||||
Logger.e("onPostExecute", "Bad things happen?");
|
||||
} else {
|
||||
String msg = TextUtils.join("\n", result.msgs);
|
||||
Log.d("onPostExecute", "Result: " + result.status + " " + msg);
|
||||
Logger.d("onPostExecute", "Result: " + result.status + " " + msg);
|
||||
if (!result.status) {
|
||||
showErrorDialog(getResources().getString(title), msg);
|
||||
}
|
||||
|
|
@ -127,18 +126,18 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
long pos;
|
||||
boolean internal;
|
||||
String[] dicts;
|
||||
LANGUAGE[] mSupportedLanguages;
|
||||
ArrayList<Language> mSupportedLanguages;
|
||||
|
||||
LoadDictTask(int msgid, boolean intern, LANGUAGE[] supportedLanguages) {
|
||||
LoadDictTask(int msgid, boolean intern, ArrayList<Language> supportedLanguages) {
|
||||
internal = intern;
|
||||
|
||||
int suplanglen = supportedLanguages.length;
|
||||
dicts = new String[suplanglen];
|
||||
for (int x=0; x<suplanglen; x++) {
|
||||
dicts = new String[supportedLanguages.size()];
|
||||
int x = 0;
|
||||
for (Language language : supportedLanguages) {
|
||||
if (intern) {
|
||||
dicts[x] = String.format(dictname, supportedLanguages[x].name().toLowerCase(Locale.ENGLISH));
|
||||
dicts[x++] = language.getDictionaryFile();
|
||||
} else {
|
||||
dicts[x] = String.format(userdictname, supportedLanguages[x].name().toLowerCase(Locale.ENGLISH));
|
||||
dicts[x++] = String.format(userdictname, language.getName().toLowerCase(Locale.ENGLISH));
|
||||
}
|
||||
}
|
||||
mSupportedLanguages = supportedLanguages;
|
||||
|
|
@ -162,11 +161,11 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
return total;
|
||||
|
||||
} catch (IOException e) {
|
||||
Log.e("getDictSizes", "Unable to get dict sizes");
|
||||
Logger.e("getDictSizes", "Unable to get dict sizes");
|
||||
e.printStackTrace();
|
||||
return -1;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.e("getDictSizes", "Unable to parse sizes");
|
||||
Logger.e("getDictSizes", "Unable to parse sizes");
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
|
|
@ -200,27 +199,16 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
@Override
|
||||
protected Reply doInBackground(String... mode) {
|
||||
Reply reply = new Reply();
|
||||
SQLiteDatabase db;
|
||||
db = T9DB.getSQLDB(mContext);
|
||||
if (db == null) {
|
||||
reply.forceMsg("Database unavailable at this time. (May be updating)");
|
||||
reply.status = false;
|
||||
return reply;
|
||||
}
|
||||
db.setLockingEnabled(false);
|
||||
|
||||
long startnow, endnow;
|
||||
startnow = SystemClock.uptimeMillis();
|
||||
|
||||
// add characters first, then dictionary:
|
||||
Log.d("doInBackground", "Adding characters...");
|
||||
// load characters from supported langs
|
||||
for (LANGUAGE lang : mSupportedLanguages) {
|
||||
processChars(db, lang);
|
||||
}
|
||||
Log.d("doInBackground", "done.");
|
||||
Logger.d("doInBackground", "Adding characters...");
|
||||
processChars(mContext, mSupportedLanguages);
|
||||
Logger.d("doInBackground", "Characters added.");
|
||||
|
||||
Log.d("doInBackground", "Adding dict(s)...");
|
||||
Logger.d("doInBackground", "Adding dict(s)...");
|
||||
|
||||
InputStream dictstream = null;
|
||||
|
||||
|
|
@ -229,7 +217,7 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
if (internal) {
|
||||
try {
|
||||
dictstream = getAssets().open(dicts[x]);
|
||||
reply = processFile(dictstream, reply, db, mSupportedLanguages[x], dicts[x]);
|
||||
reply = processFile(mContext, dictstream, reply, mSupportedLanguages.get(x), dicts[x]);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
reply.status = false;
|
||||
|
|
@ -239,18 +227,16 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
try {
|
||||
dictstream = new FileInputStream(new File(
|
||||
new File(Environment.getExternalStorageDirectory(), sddir), dicts[x]));
|
||||
reply = processFile(dictstream, reply, db, mSupportedLanguages[x], dicts[x]);
|
||||
reply = processFile(mContext, dictstream, reply, mSupportedLanguages.get(x), dicts[x]);
|
||||
} catch (FileNotFoundException e) {
|
||||
reply.status = false;
|
||||
reply.forceMsg("File not found: " + e.getMessage());
|
||||
final String msg = mContext.getString(R.string.pref_loaduser_notfound, dicts[x]);
|
||||
//Log.d("T9Setting.load", "Built string. Calling Toast.");
|
||||
final String msg = mContext.getString(R.string.dictionary_not_found, dicts[x]);
|
||||
//Logger.d("T9Setting.load", "Built string. Calling Toast.");
|
||||
((Activity) mContext).runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Toast.makeText(mContext,
|
||||
msg,
|
||||
Toast.LENGTH_SHORT).show();
|
||||
UI.toast(mContext, msg);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -270,49 +256,42 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
closeStream(dictstream, reply);
|
||||
}
|
||||
endnow = SystemClock.uptimeMillis();
|
||||
Log.d("TIMING", "Execution time: " + (endnow - startnow) + " ms");
|
||||
Logger.d("TIMING", "Execution time: " + (endnow - startnow) + " ms");
|
||||
return reply;
|
||||
}
|
||||
|
||||
private void processChars(SQLiteDatabase db, LANGUAGE lang) {
|
||||
InsertHelper wordhelp = new InsertHelper(db, T9DB.WORD_TABLE_NAME);
|
||||
|
||||
final int wordColumn = wordhelp.getColumnIndex(T9DB.COLUMN_WORD);
|
||||
final int langColumn = wordhelp.getColumnIndex(T9DB.COLUMN_LANG);
|
||||
final int freqColumn = wordhelp.getColumnIndex(T9DB.COLUMN_FREQUENCY);
|
||||
final int seqColumn = wordhelp.getColumnIndex(T9DB.COLUMN_SEQ);
|
||||
/**
|
||||
* processChars
|
||||
* Inserts single characters.
|
||||
*/
|
||||
private void processChars(Context context, List<Language> allLanguages) {
|
||||
ArrayList<Word> list = new ArrayList<>();
|
||||
|
||||
try {
|
||||
// load CHARTABLE and then load T9table, just to cover all bases.
|
||||
for (Map.Entry<Character, Integer> entry : CharMap.CHARTABLE.get(lang.index).entrySet()) {
|
||||
wordhelp.prepareForReplace();
|
||||
wordhelp.bind(langColumn, lang.id);
|
||||
wordhelp.bind(seqColumn, Integer.toString(entry.getValue()));
|
||||
wordhelp.bind(wordColumn, Character.toString(entry.getKey()));
|
||||
wordhelp.bind(freqColumn, 0);
|
||||
wordhelp.execute();
|
||||
// upper case
|
||||
wordhelp.prepareForReplace();
|
||||
wordhelp.bind(langColumn, lang.id);
|
||||
wordhelp.bind(seqColumn, Integer.toString(entry.getValue()));
|
||||
wordhelp.bind(wordColumn, Character.toString(Character.toUpperCase(entry.getKey())));
|
||||
wordhelp.bind(freqColumn, 0);
|
||||
wordhelp.execute();
|
||||
}
|
||||
char[][] chartable = CharMap.T9TABLE[lang.index];
|
||||
for (int numkey = 0; numkey < chartable.length; numkey++) {
|
||||
char[] chars = chartable[numkey];
|
||||
for (int charindex = 0; charindex < chars.length; charindex++) {
|
||||
wordhelp.prepareForReplace();
|
||||
wordhelp.bind(langColumn, lang.id);
|
||||
wordhelp.bind(seqColumn, Integer.toString(numkey));
|
||||
wordhelp.bind(wordColumn, Character.toString(chars[charindex]));
|
||||
wordhelp.bind(freqColumn, 0);
|
||||
wordhelp.execute();
|
||||
for (Language lang : allLanguages) {
|
||||
for (int key = 0; key <= 9; key++) {
|
||||
for (String langChar : lang.getKeyCharacters(key, true)) {
|
||||
if (langChar.length() == 1 && langChar.charAt(0) >= '0' && langChar.charAt(0) <= '9') {
|
||||
// We do not want 0-9 as "word suggestions" in Predictive mode. It looks confusing
|
||||
// when trying to type a word and also, one can type them by holding the respective
|
||||
// key.
|
||||
continue;
|
||||
}
|
||||
|
||||
Word word = new Word();
|
||||
word.langId = lang.getId();
|
||||
word.sequence = String.valueOf(key);
|
||||
word.word = langChar;
|
||||
word.frequency = 0;
|
||||
|
||||
list.add(word);
|
||||
}
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
wordhelp.close();
|
||||
|
||||
DictionaryDb.insertWordsSync(context, list);
|
||||
} catch (Exception e) {
|
||||
Logger.e("processChars", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -327,114 +306,91 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
return null;
|
||||
}
|
||||
|
||||
private Reply processFile(InputStream is, Reply rpl, SQLiteDatabase db, LANGUAGE lang, String fname)
|
||||
private Reply processFile(Context context, InputStream is, Reply rpl, Language lang, String fname)
|
||||
throws LoadException, IOException {
|
||||
long last = 0;
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
UnicodeBOMInputStream ubis = new UnicodeBOMInputStream(is);
|
||||
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(ubis));
|
||||
ubis.skipBOM();
|
||||
|
||||
InsertHelper wordhelp = new InsertHelper(db, T9DB.WORD_TABLE_NAME);
|
||||
final int langColumn = wordhelp.getColumnIndex(T9DB.COLUMN_LANG);
|
||||
final int wordColumn = wordhelp.getColumnIndex(T9DB.COLUMN_WORD);
|
||||
final int freqColumn = wordhelp.getColumnIndex(T9DB.COLUMN_FREQUENCY);
|
||||
final int seqColumn = wordhelp.getColumnIndex(T9DB.COLUMN_SEQ);
|
||||
|
||||
String[] ws;
|
||||
int freq;
|
||||
String seq;
|
||||
int linecount = 1;
|
||||
int wordlen;
|
||||
String word = getLine(br, rpl, fname);
|
||||
|
||||
db.beginTransaction();
|
||||
String fileWord = getLine(br, rpl, fname);
|
||||
ArrayList<Word> dbWords = new ArrayList<>();
|
||||
int insertChunkSize = 1000;
|
||||
int progressUpdateInterval = 100; // ms
|
||||
long lastProgressUpdate = 0;
|
||||
|
||||
try {
|
||||
while (word != null) {
|
||||
|
||||
DictionaryDb.beginTransaction(context);
|
||||
|
||||
while (fileWord != null) {
|
||||
if (isCancelled()) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("User cancelled.");
|
||||
break;
|
||||
}
|
||||
if (word.contains(" ")) {
|
||||
ws = word.split(" ");
|
||||
word = ws[0];
|
||||
try {
|
||||
freq = Integer.parseInt(ws[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Number error ("+fname+") at line " + linecount+". Using 0 for frequency.");
|
||||
freq = 0;
|
||||
}
|
||||
if (lang == LANGUAGE.NONE && ws.length == 3) {
|
||||
try {
|
||||
lang = LANGUAGE.get(Integer.parseInt(ws[2]));
|
||||
} catch (NumberFormatException e) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Number error ("+fname+") at line " + linecount+". Using 1 (en) for language.");
|
||||
lang = LANGUAGE.EN;
|
||||
}
|
||||
if (lang == null) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Unsupported language ("+fname+") at line " + linecount+". Trying 1 (en) for language.");
|
||||
lang = LANGUAGE.EN;
|
||||
}
|
||||
} else if (lang == LANGUAGE.NONE) {
|
||||
lang = LANGUAGE.EN;
|
||||
}
|
||||
} else {
|
||||
freq = 0;
|
||||
if (fileWord.contains(" ")) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Cannot parse word with spaces: " + fileWord);
|
||||
break;
|
||||
}
|
||||
|
||||
try {
|
||||
wordlen = word.getBytes("UTF-8").length;
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Encoding Error("+fname+") line "+linecount+": " + e.getMessage());
|
||||
wordlen = word.length();
|
||||
}
|
||||
freq = 0;
|
||||
wordlen = fileWord.getBytes(StandardCharsets.UTF_8).length;
|
||||
pos += wordlen;
|
||||
// replace junk characters:
|
||||
word = word.replace("\uFEFF", "");
|
||||
fileWord = fileWord.replace("\uFEFF", "");
|
||||
try {
|
||||
seq = CharMap.getStringSequence(word, lang);
|
||||
} catch (NullPointerException e) {
|
||||
seq = lang.getDigitSequenceForWord(fileWord);
|
||||
} catch (Exception e) {
|
||||
rpl.status = false;
|
||||
rpl.addMsg("Error on word ("+word+") line "+
|
||||
rpl.addMsg("Error on word ("+fileWord+") line "+
|
||||
linecount+" in (" + fname+"): "+
|
||||
getResources().getString(R.string.add_word_badchar, lang.name(), word));
|
||||
getResources().getString(R.string.add_word_badchar, lang.getName(), fileWord));
|
||||
break;
|
||||
}
|
||||
linecount++;
|
||||
wordhelp.prepareForReplace();
|
||||
wordhelp.bind(seqColumn, seq);
|
||||
wordhelp.bind(langColumn, lang.id);
|
||||
wordhelp.bind(wordColumn, word);
|
||||
wordhelp.bind(freqColumn, freq);
|
||||
wordhelp.execute();
|
||||
|
||||
// System.out.println("Progress: " + pos + " Last: " + last
|
||||
// + " fsize: " + fsize);
|
||||
if ((pos - last) > 4096) {
|
||||
// Log.d("doInBackground", "line: " + linecount);
|
||||
// Log.d("doInBackground", "word: " + word);
|
||||
if (size >= 0) { publishProgress((int) ((float) pos / size * 10000)); }
|
||||
last = pos;
|
||||
Word word = new Word();
|
||||
word.sequence = seq;
|
||||
word.langId = lang.getId();
|
||||
word.word = fileWord;
|
||||
word.frequency = freq;
|
||||
dbWords.add(word);
|
||||
|
||||
if (linecount % insertChunkSize == 0) {
|
||||
DictionaryDb.insertWordsSync(context, dbWords);
|
||||
dbWords.clear();
|
||||
}
|
||||
word = getLine(br, rpl, fname);
|
||||
|
||||
if (size >= 0 && System.currentTimeMillis() - lastProgressUpdate > progressUpdateInterval) {
|
||||
publishProgress((int) ((float) pos / size * 10000));
|
||||
lastProgressUpdate = System.currentTimeMillis();
|
||||
}
|
||||
fileWord = getLine(br, rpl, fname);
|
||||
}
|
||||
|
||||
DictionaryDb.insertWordsSync(context, dbWords);
|
||||
DictionaryDb.endTransaction(context, true);
|
||||
dbWords.clear();
|
||||
|
||||
publishProgress((int) ((float) pos / size * 10000));
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.setLockingEnabled(true);
|
||||
db.endTransaction();
|
||||
} catch (Exception e) {
|
||||
DictionaryDb.endTransaction(context, false);
|
||||
Logger.e("processFile", e.getMessage());
|
||||
} finally {
|
||||
br.close();
|
||||
is.close();
|
||||
ubis.close();
|
||||
is.close();
|
||||
wordhelp.close();
|
||||
|
||||
Logger.d("processFile", "Inserted: " + fname + " in: " + (System.currentTimeMillis() - start) + "ms");
|
||||
}
|
||||
return rpl;
|
||||
}
|
||||
|
|
@ -448,7 +404,7 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
|
||||
@Override
|
||||
protected void onPostExecute(Reply result) {
|
||||
finishAndShowError(pd, result, R.string.pref_load_title);
|
||||
finishAndShowError(pd, result, R.string.dictionary_load_title);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -463,7 +419,7 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
T9Preferences prefs = new T9Preferences(this);
|
||||
Object[] settings = {
|
||||
prefs.getInputMode(),
|
||||
prefs.getEnabledLanguages(),
|
||||
0, // input languages; not used, remove in #29
|
||||
null, // MODE_NOTIFY; not used, remove in #29
|
||||
false, // KEY_REMAP; not used, remove in #29
|
||||
true, // SPACE_ZERO; not used, remove in #29
|
||||
|
|
@ -487,9 +443,12 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
if (s.id.equals("help"))
|
||||
openHelp();
|
||||
else if (s.id.equals("loaddict"))
|
||||
preloader(R.string.pref_loadingdict, true);
|
||||
preloader(R.string.dictionary_loading, true);
|
||||
else if (s.id.equals("truncatedict")) {
|
||||
truncateWords();
|
||||
}
|
||||
else if (s.id.equals("loaduserdict"))
|
||||
preloader(R.string.pref_loadinguserdict, false);
|
||||
preloader(R.string.dictionary_loading_user_dict, false);
|
||||
else
|
||||
s.clicked(mContext);
|
||||
}
|
||||
|
|
@ -500,13 +459,24 @@ public class TraditionalT9Settings extends ListActivity implements DialogInterfa
|
|||
startActivity(i);
|
||||
}
|
||||
|
||||
private void truncateWords() {
|
||||
Handler afterTruncate = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
UI.toast(mContext, R.string.dictionary_truncated);
|
||||
}
|
||||
};
|
||||
DictionaryDb.truncateWords(mContext, afterTruncate);
|
||||
}
|
||||
|
||||
|
||||
private void preloader(int msgid, boolean internal) {
|
||||
|
||||
|
||||
task = new LoadDictTask(
|
||||
msgid,
|
||||
internal,
|
||||
LangHelper.buildLangs(T9Preferences.getInstance(mContext).getEnabledLanguages())
|
||||
LanguageCollection.getAll(T9Preferences.getInstance(mContext).getEnabledLanguages())
|
||||
);
|
||||
task.execute();
|
||||
}
|
||||
70
src/io/github/sspanak/tt9/ui/UI.java
Normal file
70
src/io/github/sspanak/tt9/ui/UI.java
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
package io.github.sspanak.tt9.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.widget.Toast;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
import io.github.sspanak.tt9.R;
|
||||
import io.github.sspanak.tt9.ime.TraditionalT9;
|
||||
import io.github.sspanak.tt9.languages.Language;
|
||||
|
||||
public class UI {
|
||||
public static void showAddWordDialog(TraditionalT9 tt9, int language, String currentWord) {
|
||||
Intent awIntent = new Intent(tt9, AddWordAct.class);
|
||||
awIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
awIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
awIntent.putExtra("io.github.sspanak.tt9.word", currentWord);
|
||||
awIntent.putExtra("io.github.sspanak.tt9.lang", language);
|
||||
tt9.startActivity(awIntent);
|
||||
}
|
||||
|
||||
|
||||
public static void showPreferencesScreen(TraditionalT9 tt9) {
|
||||
Intent prefIntent = new Intent(tt9, TraditionalT9Settings.class);
|
||||
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
prefIntent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
|
||||
tt9.hideWindow();
|
||||
tt9.startActivity(prefIntent);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* updateStatusIcon
|
||||
* Set the status icon that is appropriate in current mode (based on
|
||||
* openwmm-legacy)
|
||||
*/
|
||||
public static void updateStatusIcon(TraditionalT9 tt9, Language inputLanguage, int inputMode, int textCase) {
|
||||
switch (inputMode) {
|
||||
case TraditionalT9.MODE_ABC:
|
||||
tt9.showStatusIcon(inputLanguage.getAbcIcon(textCase == TraditionalT9.CASE_LOWER));
|
||||
break;
|
||||
case TraditionalT9.MODE_PREDICTIVE:
|
||||
tt9.showStatusIcon(inputLanguage.getIcon());
|
||||
break;
|
||||
case TraditionalT9.MODE_123:
|
||||
tt9.showStatusIcon(R.drawable.ime_number);
|
||||
break;
|
||||
default:
|
||||
Logger.w("tt9.UI", "Unknown inputMode mode: " + inputMode + ". Hiding status icon.");
|
||||
tt9.hideStatusIcon();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static void toast(Context context, CharSequence msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public static void toast(Context context, int resourceId) {
|
||||
Toast.makeText(context, resourceId, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
|
||||
public static void toastLong(Context context, int resourceId) {
|
||||
Toast.makeText(context, resourceId, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
public static void toastLong(Context context, CharSequence msg) {
|
||||
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
|
@ -10,12 +10,13 @@ import android.content.res.TypedArray;
|
|||
import android.preference.ListPreference;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import io.github.sspanak.tt9.Logger;
|
||||
|
||||
public class MultiSelectListPreference extends ListPreference {
|
||||
|
||||
private String separator;
|
||||
|
|
@ -63,12 +64,12 @@ public class MultiSelectListPreference extends ListPreference {
|
|||
// added method
|
||||
public static int[] defaultunpack2Int(CharSequence val) {
|
||||
if (val == null || "".equals(val)) {
|
||||
//Log.w("MultiSelectPref.defaultunpack", "val is null or empty");
|
||||
//Logger.w("MultiSelectPref.defaultunpack", "val is null or empty");
|
||||
return new int[] {0}; //default pref
|
||||
} else {
|
||||
String[] sa = ((String) val).split("\\"+DEFAULT_SEPARATOR);
|
||||
if (sa.length < 1) {
|
||||
Log.w("MSLPref.defaultunpack", "split is less than 1");
|
||||
Logger.w("MSLPref.defaultunpack", "split is less than 1");
|
||||
return new int[] {0}; //default pref
|
||||
}
|
||||
int[] ia = new int[sa.length];
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue