1
0
Fork 0

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:
Dimo Karaivanov 2022-10-03 15:28:24 +03:00 committed by GitHub
parent af172b79c9
commit 78b6681812
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
225 changed files with 2723 additions and 4986 deletions

View file

@ -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()));
}
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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.");
}
}

View file

@ -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.");
}
}

View 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();
}
}

View file

@ -0,0 +1,7 @@
package io.github.sspanak.tt9.db;
public class InsertBlankWordException extends Exception {
protected InsertBlankWordException() {
super("Cannot insert a blank word.");
}
}

View file

@ -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()));
}
}

View 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();
}

View 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;
}

View 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);
}

View 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();
}
}

View 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;
}
}

View 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)
}*/
}

View 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;
}
}

View 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);
}
}

View file

@ -0,0 +1,7 @@
package io.github.sspanak.tt9.languages;
public class InvalidLanguageException extends Exception {
public InvalidLanguageException(String msg) {
super(msg);
}
}

View 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();
}
}

View 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;
}
}

View 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"
));
}

View 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 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
));
}
}

View 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
));
}
}

View 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("ÿ");
}
}

View 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("ü");
}
}

View 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("ù", "ú"));
}
}

View 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
));
}
}

View 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 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
));
}
}

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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;

View file

@ -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

View file

@ -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();
}

View file

@ -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() {

View file

@ -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();
}

View file

@ -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()) {

View file

@ -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();
}

View 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();
}
}

View file

@ -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];