Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Small fixes 40.3 #657

Merged
merged 12 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Compatibility has been verified only on the following devices:
## 🤔 How to Use Traditional T9?
Before using Traditional T9 for the first time you need to configure it and load a dictionary. After that, you can start typing right away in one of the three modes: Predictive, ABC, or Numeric (123). And even if you have mastered the keypad back in the day, you will still find the Predictive mode now offers more powerful and smart new ways of typing with even fewer key presses.

So make sure to read the initial setup and the hotkey tips in the [user manual](docs/user-manual.md). Also, don't miss the convenient [compatibility options](docs/user-manual.md#compatibility-options--troubleshooting) aimed to improve the experience in some applications.
So make sure to read the initial setup and the hotkey tips in the [user manual](docs/help/help.en.md). Also, don't miss the convenient [compatibility options](docs/user-manual.md#compatibility-options--troubleshooting) aimed to improve the experience in some applications.

## ⌨ Contributing
As with many other open-source projects, this one is also maintained by its author in his free time. Any help in making Traditional T9 better will be highly appreciated. Here is how:
Expand Down
58 changes: 48 additions & 10 deletions app/src/main/java/io/github/sspanak/tt9/db/DataStore.java
Original file line number Diff line number Diff line change
@@ -1,22 +1,35 @@
package io.github.sspanak.tt9.db;

import android.content.Context;
import android.os.CancellationSignal;
import android.os.Handler;

import androidx.annotation.NonNull;

import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import io.github.sspanak.tt9.db.entities.AddWordResult;
import io.github.sspanak.tt9.db.wordPairs.WordPairStore;
import io.github.sspanak.tt9.db.words.DictionaryLoader;
import io.github.sspanak.tt9.db.words.WordStore;
import io.github.sspanak.tt9.languages.Language;
import io.github.sspanak.tt9.preferences.settings.SettingsStore;
import io.github.sspanak.tt9.util.ConsumerCompat;
import io.github.sspanak.tt9.util.Logger;

public class DataStore {
private static final Handler asyncHandler = new Handler();
private final static String LOG_TAG = DataStore.class.getSimpleName();

private static final Handler asyncReturn = new Handler();
private static final ExecutorService executor = Executors.newCachedThreadPool();

private static Future<?> getWordsTask;
private static CancellationSignal getWordsCancellationSignal = new CancellationSignal();

private static WordPairStore pairs;
private static WordStore words;

Expand All @@ -28,7 +41,7 @@ public static void init(Context context) {


private static void runInThread(@NonNull Runnable action) {
new Thread(action).start();
executor.submit(action);
}


Expand All @@ -40,7 +53,7 @@ private static void runInTransaction(@NonNull Runnable action, @NonNull Runnable
words.finishTransaction();
} catch (Exception e) {
words.failTransaction();
Logger.e(DataStore.class.getSimpleName(), errorMessagePrefix + " " + e.getMessage());
Logger.e(LOG_TAG, errorMessagePrefix + " " + e.getMessage());
}
onFinish.run();
});
Expand Down Expand Up @@ -85,33 +98,58 @@ public static void makeTopWord(@NonNull Language language, @NonNull String word,


public static void getWords(ConsumerCompat<ArrayList<String>> dataHandler, Language language, String sequence, String filter, int minWords, int maxWords) {
runInThread(() -> {
ArrayList<String> data = words.getSimilar(language, sequence, filter, minWords, maxWords);
asyncHandler.post(() -> dataHandler.accept(data));
});
if (getWordsTask != null && !getWordsTask.isDone()) {
dataHandler.accept(new ArrayList<>());
getWordsCancellationSignal.cancel();
return;
}

getWordsCancellationSignal = new CancellationSignal();
getWordsTask = executor.submit(() -> getWordsSync(dataHandler, language, sequence, filter, minWords, maxWords));
executor.submit(DataStore::setGetWordsTimeout);
}


private static void getWordsSync(ConsumerCompat<ArrayList<String>> dataHandler, Language language, String sequence, String filter, int minWords, int maxWords) {
try {
ArrayList<String> data = words.getSimilar(getWordsCancellationSignal, language, sequence, filter, minWords, maxWords);
asyncReturn.post(() -> dataHandler.accept(data));
} catch (Exception e) {
Logger.e(LOG_TAG, "Error fetching words: " + e.getMessage());
}
}


private static void setGetWordsTimeout() {
try {
getWordsTask.get(SettingsStore.SLOW_QUERY_TIMEOUT, TimeUnit.MILLISECONDS);
} catch (Exception e) {
getWordsCancellationSignal.cancel();
Logger.e(LOG_TAG, "Word loading timed out after " + SettingsStore.SLOW_QUERY_TIMEOUT + " ms.");
}
}


public static void getCustomWords(ConsumerCompat<ArrayList<String>> dataHandler, String wordFilter, int maxWords) {
runInThread(() -> {
ArrayList<String> data = words.getSimilarCustom(wordFilter, maxWords);
asyncHandler.post(() -> dataHandler.accept(data));
asyncReturn.post(() -> dataHandler.accept(data));
});
}


public static void countCustomWords(ConsumerCompat<Long> dataHandler) {
runInThread(() -> {
long data = words.countCustom();
asyncHandler.post(() -> dataHandler.accept(data));
asyncReturn.post(() -> dataHandler.accept(data));
});
}


public static void exists(ConsumerCompat<ArrayList<Integer>> dataHandler, ArrayList<Language> languages) {
runInThread(() -> {
ArrayList<Integer> data = words.exists(languages);
asyncHandler.post(() -> dataHandler.accept(data));
asyncReturn.post(() -> dataHandler.accept(data));
});
}

Expand Down
39 changes: 27 additions & 12 deletions app/src/main/java/io/github/sspanak/tt9/db/sqlite/ReadOps.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDoneException;
import android.database.sqlite.SQLiteStatement;
import android.os.CancellationSignal;
import android.os.OperationCanceledException;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.util.ArrayList;

Expand Down Expand Up @@ -118,75 +121,87 @@ public String getWords(@NonNull SQLiteDatabase db, Language language, boolean cu


@NonNull
public WordList getWords(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String positions, String filter, int maximumWords, boolean fullOutput) {
public WordList getWords(@NonNull SQLiteDatabase db, @Nullable CancellationSignal cancel, @NonNull Language language, @NonNull String positions, String filter, int maximumWords, boolean fullOutput) {
if (positions.isEmpty()) {
Logger.d(LOG_TAG, "No word positions. Not searching words.");
return new WordList();
}

String wordsQuery = getWordsQuery(language, positions, filter, maximumWords, fullOutput);
if (wordsQuery.isEmpty()) {
if (wordsQuery.isEmpty() || (cancel != null && cancel.isCanceled())) {
return new WordList();
}

WordList words = new WordList();
try (Cursor cursor = db.rawQuery(wordsQuery, null)) {
try (Cursor cursor = db.rawQuery(wordsQuery, null, cancel)) {
while (cursor.moveToNext()) {
words.add(
cursor.getString(0),
fullOutput ? cursor.getInt(1) : 0,
fullOutput ? cursor.getInt(2) : 0
);
}
} catch (OperationCanceledException e) {
Logger.d(LOG_TAG, "Words query cancelled!");
return words;
}

return words;
}


public String getSimilarWordPositions(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String sequence, String wordFilter, int minPositions) {
public String getSimilarWordPositions(@NonNull SQLiteDatabase db, @NonNull CancellationSignal cancel, @NonNull Language language, @NonNull String sequence, String wordFilter, int minPositions) {
int generations = switch (sequence.length()) {
case 2 -> wordFilter.isEmpty() ? 1 : 10;
case 3, 4 -> wordFilter.isEmpty() ? 2 : 10;
default -> 10;
};

return getWordPositions(db, language, sequence, generations, minPositions, wordFilter);
return getWordPositions(db, cancel, language, sequence, generations, minPositions, wordFilter);
}


@NonNull
public String getWordPositions(@NonNull SQLiteDatabase db, @NonNull Language language, @NonNull String sequence, int generations, int minPositions, String wordFilter) {
if (sequence.length() == 1) {
public String getWordPositions(@NonNull SQLiteDatabase db, @Nullable CancellationSignal cancel, @NonNull Language language, @NonNull String sequence, int generations, int minPositions, String wordFilter) {
if (sequence.length() == 1 || (cancel != null && cancel.isCanceled())) {
return sequence;
}

WordPositionsStringBuilder positions = new WordPositionsStringBuilder();

String cachedFactoryPositions = SlowQueryStats.getCachedIfSlow(SlowQueryStats.generateKey(language, sequence, wordFilter, minPositions));
if (cachedFactoryPositions != null) {
String customWordPositions = getCustomWordPositions(db, language, sequence, generations);
String customWordPositions = getCustomWordPositions(db, cancel, language, sequence, generations);
return customWordPositions.isEmpty() ? cachedFactoryPositions : customWordPositions + "," + cachedFactoryPositions;
}

try (Cursor cursor = db.rawQuery(getPositionsQuery(language, sequence, generations), null)) {
try (Cursor cursor = db.rawQuery(getPositionsQuery(language, sequence, generations), null, cancel)) {
positions.appendFromDbRanges(cursor);
} catch (OperationCanceledException ignored) {
Logger.d(LOG_TAG, "Word positions query cancelled!");
return sequence;
}

if (positions.size < minPositions && generations < Integer.MAX_VALUE) {
Logger.d(LOG_TAG, "Not enough positions: " + positions.size + " < " + minPositions + ". Searching for more.");
try (Cursor cursor = db.rawQuery(getFactoryWordPositionsQuery(language, sequence, Integer.MAX_VALUE), null)) {
try (Cursor cursor = db.rawQuery(getFactoryWordPositionsQuery(language, sequence, Integer.MAX_VALUE), null, cancel)) {
positions.appendFromDbRanges(cursor);
} catch (OperationCanceledException ignored) {
Logger.d(LOG_TAG, "Word positions query cancelled!");
return sequence;
}
}

return positions.toString();
}


@NonNull private String getCustomWordPositions(@NonNull SQLiteDatabase db, Language language, String sequence, int generations) {
try (Cursor cursor = db.rawQuery(getCustomWordPositionsQuery(language, sequence, generations), null)) {
@NonNull private String getCustomWordPositions(@NonNull SQLiteDatabase db, CancellationSignal cancel, Language language, String sequence, int generations) {
try (Cursor cursor = db.rawQuery(getCustomWordPositionsQuery(language, sequence, generations), null, cancel)) {
return new WordPositionsStringBuilder().appendFromDbRanges(cursor).toString();
} catch (OperationCanceledException e) {
Logger.d(LOG_TAG, "Custom word positions query cancelled.");
return "";
}
}

Expand Down
15 changes: 9 additions & 6 deletions app/src/main/java/io/github/sspanak/tt9/db/words/WordStore.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.github.sspanak.tt9.db.words;

import android.content.Context;
import android.os.CancellationSignal;

import androidx.annotation.NonNull;

Expand Down Expand Up @@ -60,7 +61,7 @@ public ArrayList<Integer> exists(ArrayList<Language> languages) {
* For example: "7655" -> "roll" (exact match), but also: "rolled", "roller", "rolling", ...
* and other similar.
*/
public ArrayList<String> getSimilar(Language language, String sequence, String wordFilter, int minimumWords, int maximumWords) {
public ArrayList<String> getSimilar(@NonNull CancellationSignal cancel, Language language, String sequence, String wordFilter, int minimumWords, int maximumWords) {
if (!checkOrNotify()) {
return new ArrayList<>();
}
Expand All @@ -80,15 +81,17 @@ public ArrayList<String> getSimilar(Language language, String sequence, String w
final String filter = wordFilter == null ? "" : wordFilter;

Timer.start("get_positions");
String positions = readOps.getSimilarWordPositions(sqlite.getDb(), language, sequence, filter, minWords);
String positions = readOps.getSimilarWordPositions(sqlite.getDb(), cancel, language, sequence, filter, minWords);
long positionsTime = Timer.stop("get_positions");

Timer.start("get_words");
ArrayList<String> words = readOps.getWords(sqlite.getDb(), language, positions, filter, maxWords, false).toStringList();
ArrayList<String> words = readOps.getWords(sqlite.getDb(), cancel, language, positions, filter, maxWords, false).toStringList();
long wordsTime = Timer.stop("get_words");

printLoadingSummary(sequence, words, positionsTime, wordsTime);
SlowQueryStats.add(SlowQueryStats.generateKey(language, sequence, wordFilter, minWords), (int) (positionsTime + wordsTime), positions);
if (!cancel.isCanceled()) { // do not store empty results from aborted queries in the cache
SlowQueryStats.add(SlowQueryStats.generateKey(language, sequence, wordFilter, minWords), (int) (positionsTime + wordsTime), positions);
}

return words;
}
Expand Down Expand Up @@ -188,8 +191,8 @@ public void makeTopWord(@NonNull Language language, @NonNull String word, @NonNu
try {
Timer.start(LOG_TAG);

String topWordPositions = readOps.getWordPositions(sqlite.getDb(), language, sequence, 0, 0, "");
WordList topWords = readOps.getWords(sqlite.getDb(), language, topWordPositions, "", 9999, true);
String topWordPositions = readOps.getWordPositions(sqlite.getDb(), null, language, sequence, 0, 0, "");
WordList topWords = readOps.getWords(sqlite.getDb(), null, language, topWordPositions, "", 9999, true);
if (topWords.isEmpty()) {
throw new Exception("No such word");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ public int getPaddedWordBeforeCursorLength() {
return 0;
}

int whitespaceShift = Math.max(before.lastWhitespaceBlockIndex(), 0);
int whitespaceShift = Math.max(before.lastBoundaryIndex(), 0);
return Math.min(before.length() - whitespaceShift, (int) (SettingsStore.BACKSPACE_ACCELERATION_MAX_CHARS * 1.5));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,15 @@ private void onItemClick(DropDownPreference item) {
return false;
}

((DropDownPreference) preference).setValue(newKey.toString());
previewCurrentKey((DropDownPreference) preference, newKey.toString());
populateOtherItems((DropDownPreference) preference);
return true;
try {
((DropDownPreference) preference).setValue(newKey.toString());
previewCurrentKey((DropDownPreference) preference, newKey.toString());
populateOtherItems((DropDownPreference) preference);
return true;
} catch (Exception e) {
Logger.e("SectionKeymap.onItemClick", "Failed setting new hotkey. " + e.getMessage());
return false;
}
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package io.github.sspanak.tt9.preferences.screens.main;

import android.content.Intent;
import android.net.Uri;

import androidx.preference.Preference;

import io.github.sspanak.tt9.R;
import io.github.sspanak.tt9.preferences.PreferencesActivity;
import io.github.sspanak.tt9.preferences.items.ItemClickable;
import io.github.sspanak.tt9.util.Logger;

class ItemDonate extends ItemClickable {
static final String NAME = "donate_link";
private final PreferencesActivity activity;

ItemDonate(Preference preference, PreferencesActivity activity) {
super(preference);
this.activity = activity;
}

public ItemDonate populate() {
if (item != null) {
String appName = activity.getString(R.string.app_name_short);
String url = activity.getString(R.string.donate_url_short);
item.setSummary(activity.getString(R.string.donate_summary, appName, url));
}
return this;
}

@Override
protected boolean onClick(Preference p) {
try {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(activity.getString(R.string.donate_url))));
return true;
} catch (Exception e) {
Logger.w(getClass().getSimpleName(), "Cannot navigate to the donation page. " + e.getMessage() + " (do you have a browser?)");
return false;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,8 @@ public void onResume() {


private void createAboutSection() {
Preference donate = findPreference("donate_link");
if (donate != null) {
String appName = getString(R.string.app_name_short);
String url = getString(R.string.donate_url_short);
donate.setSummary(getString(R.string.donate_summary, appName, url));
}

ItemVersionInfo debugOptions = new ItemVersionInfo(findPreference(ItemVersionInfo.NAME), activity);
debugOptions.populate().enableClickHandler();
(new ItemDonate(findPreference(ItemDonate.NAME), activity)).populate().enableClickHandler();
(new ItemVersionInfo(findPreference(ItemVersionInfo.NAME), activity)).populate().enableClickHandler();
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class SettingsStore extends SettingsUI {
public final static int DICTIONARY_IMPORT_PROGRESS_UPDATE_TIME = 250; // ms
public final static int RESIZE_THROTTLING_TIME = 60; // ms
public final static byte SLOW_QUERY_TIME = 50; // ms
public final static int SLOW_QUERY_TIMEOUT = 3000; // ms
public final static int SOFT_KEY_DOUBLE_CLICK_DELAY = 500; // ms
public final static int SOFT_KEY_REPEAT_DELAY = 40; // ms
public final static int SOFT_KEY_TITLE_MAX_CHARS = 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ public boolean onTouch(View view, MotionEvent event) {
} else if (action == MotionEvent.ACTION_UP) {
if (!repeat || hold) {
hold = false;
repeat = false;
boolean result = handleRelease();
lastPressedKey = ignoreLastPressedKey ? -1 : getId();
return result;
Expand Down
Loading
Loading