Language settings screen refinement

This commit is contained in:
jahorton 2019-06-17 11:53:34 +07:00
parent 4ce013f4d5
commit 2e107825a8
7 changed files with 134 additions and 34 deletions

View file

@ -845,7 +845,13 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi
public void onLexicalModelInstalled(List<Map<String, String>> lexicalModelsInstalled) {
for(int i=0; i<lexicalModelsInstalled.size(); i++) {
HashMap<String, String>lexicalModelInfo = new HashMap<>(lexicalModelsInstalled.get(i));
KMManager.addLexicalModel(this, lexicalModelInfo);
try {
KMManager.addLexicalModel(this, lexicalModelInfo);
} catch (RuntimeException e) {
// We error-catch this in case it's called from an AsyncTask;
// this may happen when installing a new keyboard given that we attempt
// to auto-download an appropriate lexical model to match it.
}
}
// It would be nice to register associated lexical model

View file

@ -245,7 +245,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
ret = downloadNonKMPKeyboard(remoteUrl, remoteLexicalModelUrl);
} catch (Exception e) {
ret = -1;
Log.e(TAG, "Error: " + e);
Log.e(TAG, "Error: " + e, e);
}
return ret;

View file

@ -66,6 +66,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
// Lists of installed keyboards and installed lexical models
private static ArrayList<HashMap<String, String>> keyboardsList = null;
private static ArrayList<HashMap<String, String>> lexicalModelsList = null;
private static Dataset storageDataset = null;
private static boolean checkingUpdates = false;
private static int updateCount = 0;
@ -372,6 +373,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
boolean result;
keyboardsList = list;
result = saveList(context, KMManager.KMFilename_KeyboardsList);
notifyKeyboardsUpdate(context);
return result;
}
@ -385,6 +387,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
boolean result;
lexicalModelsList = list;
result = saveList(context, KMManager.KMFilename_LexicalModelsList);
notifyLexicalModelsUpdate(context);
return result;
}
@ -448,6 +451,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
}
notifyKeyboardsUpdate(context);
return result;
}
@ -481,6 +486,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
}
notifyLexicalModelsUpdate(context);
return result;
}
@ -495,13 +502,14 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
result = saveList(context, KMManager.KMFilename_KeyboardsList);
}
notifyKeyboardsUpdate(context);
return result;
}
protected static void deleteKeyboard(Context context, int position) {
int curKbPos = getCurrentKeyboardIndex();
boolean result = removeKeyboard(context, position);
;
if (result) {
Toast.makeText(context, "Keyboard deleted", Toast.LENGTH_SHORT).show();
@ -511,11 +519,13 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
if (position == curKbPos && listView != null) {
switchKeyboard(0);
} else {
} else if(listView != null) { // A bit of a hack, since LanguageSettingsActivity calls this method too.
curKbPos = getCurrentKeyboardIndex();
setSelection(curKbPos);
}
}
notifyKeyboardsUpdate(context);
}
/**
@ -556,6 +566,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
result = saveList(context, KMManager.KMFilename_LexicalModelsList);
}
notifyLexicalModelsUpdate(context);
return result;
}
@ -574,6 +586,8 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
Toast.makeText(context, "Model deleted", Toast.LENGTH_SHORT).show();
KMManager.deregisterLexicalModel(modelID);
}
notifyLexicalModelsUpdate(context);
}
@SuppressWarnings("unchecked")
@ -596,6 +610,10 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
public static Dataset getInstalledDataset(Context context) {
if(storageDataset != null) {
return storageDataset;
}
List<? extends Map<String, String>> kbdMapList = getKeyboardsList(context);
List<Keyboard> kbdsList = new ArrayList<>(kbdMapList.size());
@ -604,17 +622,51 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
List<? extends Map<String, String>> lexMapList = getLexicalModelsList(context);
if(lexMapList == null) {
lexMapList = new ArrayList<>(0);
}
List<LexicalModel> lexList = new ArrayList<>(lexMapList.size());
for(Map<String, String> map: lexMapList) {
lexList.add(new LexicalModel(map));
}
Dataset data = new Dataset(context);
data.keyboards.addAll(kbdsList);
data.lexicalModels.addAll(lexList);
storageDataset = new Dataset(context);
storageDataset.keyboards.addAll(kbdsList);
storageDataset.lexicalModels.addAll(lexList);
return data;
return storageDataset;
}
// While the two following methods aren't exactly ideal, they should be enough to get the job done
// for 12.0 before a more complete refactor of this class is done.
protected static void notifyKeyboardsUpdate(Context context) {
Dataset storage = getInstalledDataset(context);
storage.keyboards.setNotifyOnChange(false);
storage.keyboards.clear();
List<? extends Map<String, String>> mapList = getKeyboardsList(context);
List<Keyboard> kbdList = new ArrayList<>(mapList.size());
for(Map<String, String> map: mapList) {
kbdList.add(new Keyboard(map));
}
storage.keyboards.addAll(kbdList);
storage.keyboards.notifyDataSetChanged();
}
protected static void notifyLexicalModelsUpdate(Context context) {
Dataset storage = getInstalledDataset(context);
storage.lexicalModels.setNotifyOnChange(false);
storage.lexicalModels.clear();
List<? extends Map<String, String>> mapList = getLexicalModelsList(context);
List<LexicalModel> lexList = new ArrayList<>(mapList.size());
for(Map<String, String> map: mapList) {
lexList.add(new LexicalModel(map));
}
storage.lexicalModels.addAll(lexList);
storage.lexicalModels.notifyDataSetChanged();
}
protected static ArrayList<HashMap<String, String>> getKeyboardsList(Context context) {
@ -826,6 +878,7 @@ public final class KeyboardPickerActivity extends AppCompatActivity implements O
}
}
// TODO: Handle within the new CloudRepository class!
private static void checkKeyboardUpdates(final Context context) {
new AsyncTask<Void, Integer, Integer>() {
private final boolean hasConnection = KMManager.hasConnection(context);

View file

@ -6,7 +6,6 @@ package com.tavultesoft.kmea;
import android.content.Context;
import android.content.Intent;
import android.graphics.Typeface;
import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
@ -16,8 +15,6 @@ import android.view.Window;
import android.widget.AdapterView;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
@ -33,7 +30,6 @@ import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.data.adapters.NestedAdapter;
import com.tavultesoft.kmea.util.MapCompat;
import java.util.ArrayList;
import java.util.HashMap;
/**
@ -126,10 +122,6 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
* imageView.setImageResource(R.drawable.ic_arrow_forward);
*/
// String[] from = new String[]{KMManager.KMKey_KeyboardName, KMManager.KMKey_Icon};
// int[] to = new int[]{R.id.text1, R.id.image1};
// ListAdapter listAdapter = new KMListAdapter(context, associatedKeyboardList, R.layout.list_row_layout1, from, to);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
@ -190,6 +182,14 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
@Override
public void onResume() {
super.onResume();
FilteredKeyboardsAdapter adapter = ((FilteredKeyboardsAdapter) listView.getAdapter());
if(adapter != null) {
// Despite the fact that updates should have been auto-triggered anyway, it seems that we
// need a manual call here for things to happen in a timely fashion.
adapter.notifyDataSetChanged();
}
}
@Override
@ -211,13 +211,10 @@ public final class LanguageSettingsActivity extends AppCompatActivity {
// Fully details the building of this Activity's list view items.
static private class FilteredKeyboardsAdapter extends NestedAdapter<com.tavultesoft.kmea.data.Keyboard, Dataset.Keyboards, String> {
static final int RESOURCE = R.layout.list_row_layout1;
private final Context context;
public FilteredKeyboardsAdapter(@NonNull Context context, final Dataset storage, final String languageCode) {
// Goal: to not need a custom filter here, instead relying on LanguageDataset's built-in filters.
super(context, RESOURCE, storage.keyboards, storage.keyboardFilter, languageCode);
this.context = context;
}
@Override

View file

@ -160,6 +160,16 @@ public class CloudRepository {
JSONObject kbdData = new JSONObject();
JSONArray lexData = new JSONArray();
if(loadKeyboardsFromCache) {
kbdData = getCachedJSONObject(getKeyboardCacheFile(context));
// In case something went wrong with the last cache attempt, which can cause a null return.
if(kbdData == null) {
kbdData = new JSONObject();
loadKeyboardsFromCache = false;
}
}
if(!loadKeyboardsFromCache) {
String deviceType = context.getString(R.string.device_type);
if (deviceType.equals("AndroidTablet")) {
@ -174,8 +184,15 @@ public class CloudRepository {
//cloudQueries[cloudQueryEntries++] = new CloudApiParam(ApiTarget.Keyboards, keyboardURL, JSONType.Object);
cloudQueries.add(new CloudApiParam(ApiTarget.Keyboards, keyboardURL, JSONType.Object));
} else {
kbdData = getCachedJSONObject(getKeyboardCacheFile(context));
}
if(loadLexicalModelsFromCache) {
lexData = getCachedJSONArray(getLexicalModelCacheFile(context));
if(lexData == null) {
lexData = new JSONArray();
loadLexicalModelsFromCache = false;
}
}
if(!loadLexicalModelsFromCache) {
@ -184,7 +201,6 @@ public class CloudRepository {
// query is ready!
String lexicalURL = String.format("%s?q", KMKeyboardDownloaderActivity.kKeymanApiModelURL);
//cloudQueries[cloudQueryEntries++] = new CloudApiParam(ApiTarget.LexicalModels, lexicalURL, JSONType.Array);
cloudQueries.add(new CloudApiParam(ApiTarget.LexicalModels, lexicalURL, JSONType.Array));
@ -198,8 +214,6 @@ public class CloudRepository {
// lexicalURL = lexicalURL.substring(0, lexicalURL.lastIndexOf(','));
/* do what's possible here, rather than in the Task */
} else {
lexData = getCachedJSONArray(getLexicalModelCacheFile(context));
}
boolean executeCallbacks = true;
@ -214,7 +228,7 @@ public class CloudRepository {
}
// Reuse any valid parts of the cache.
if(kbdData != null || lexData != null) {
if(loadKeyboardsFromCache || loadLexicalModelsFromCache) {
CloudDownloadReturns jsonData = new CloudDownloadReturns(kbdData, lexData);
// Call the processor method directly with the cached API data.
@ -651,6 +665,7 @@ public class CloudRepository {
}
public void processCloudReturns(CloudDownloadReturns jsonTuple, boolean executeCallbacks) {
// TODO: Need proper offline check again.
List<Keyboard> keyboardsArrayList = processKeyboardJSON(jsonTuple.keyboardJSON, false);
List<LexicalModel> lexicalModelsArrayList = processLexicalModelJSON(jsonTuple.lexicalModelJSON);

View file

@ -99,7 +99,17 @@ public class Dataset extends ArrayAdapter<Dataset.LanguageDataset> {
super.add(object);
getMetadataFor(object); // We can ignore the return; this makes sure the needed object is constructed.
LanguageDataset data = getMetadataFor(object); // We can ignore the return; this makes sure the needed object is constructed.
if(object instanceof Keyboard) {
if(!data.keyboards.contains(object)) {
data.keyboards.add((Keyboard) object);
}
} else if(object instanceof LexicalModel) {
if(!data.lexicalModels.contains(object)) {
data.lexicalModels.add((LexicalModel) object);
}
}
if(notify) {
Dataset.this.notifyDataSetChanged();
@ -154,6 +164,14 @@ public class Dataset extends ArrayAdapter<Dataset.LanguageDataset> {
super.remove(object);
LanguageDataset data = getMetadataFor(object);
if(object instanceof Keyboard) {
data.keyboards.remove(object);
} else if(object instanceof LexicalModel) {
data.lexicalModels.remove(object);
}
handleLanguageItemRemoval(object);
if(notify) {
@ -168,12 +186,21 @@ public class Dataset extends ArrayAdapter<Dataset.LanguageDataset> {
Dataset.this.setNotifyOnChange(false);
}
List<Type> clearedItems = this.asList();
// this.asList returns an unmodifiable reference to the internal list -
// but that list itself may be modified by this adapter's functions!
List<Type> clearedItems = new ArrayList<>(this.asList());
super.clear();
for(Type kbd: clearedItems) {
handleLanguageItemRemoval(kbd);
for(Type object: clearedItems) {
LanguageDataset data = getMetadataFor(object);
if(object instanceof Keyboard) {
data.keyboards.remove(object);
} else if(object instanceof LexicalModel) {
data.lexicalModels.remove(object);
}
handleLanguageItemRemoval(object);
}
if(notify) {
@ -195,11 +222,11 @@ public class Dataset extends ArrayAdapter<Dataset.LanguageDataset> {
@Override
public void notifyDataSetChanged() {
doNotify = true;
super.notifyDataSetChanged();
Dataset.this.notifyDataSetChanged();
}
void _notifyDataSetChanged() {
doNotify = true;
super.notifyDataSetChanged();
}
@ -292,8 +319,8 @@ public class Dataset extends ArrayAdapter<Dataset.LanguageDataset> {
@Override
public void notifyDataSetChanged() {
super.notifyDataSetChanged();
keyboards.notifyDataSetChanged();
lexicalModels.notifyDataSetChanged();
keyboards._notifyDataSetChanged();
lexicalModels._notifyDataSetChanged();
}
void _notifyDataSetChanged() {

View file

@ -81,7 +81,9 @@ public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked
this(context, resource, adapter, new AdapterFilter<Element, A, FilterArg>() {
@Override
public List<Element> selectFrom(A adapter, FilterArg dummy) {
return adapter.asList();
// Make sure to duplicate the list so that we don't accidentally try to modify our
// wrapped adapter's (unmodifiable) contents.
return new ArrayList<>(adapter.asList());
}
}, null);
}