diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
index a4710604dd..5fb78313ed 100644
--- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
+++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java
@@ -14,13 +14,14 @@ import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
+import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
-
import com.tavultesoft.kmea.KMManager.KeyboardType;
import com.tavultesoft.kmea.KMTextView;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
+import android.app.FragmentManager;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -179,10 +180,43 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
}
KMManager.addKeyboardEventListener(this);
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
- checkUrl();
- getIntent().setData(null);
+ // If URL provided, start KMKeyboardDownloaderActivity
+ Intent intent = getIntent();
+ Uri data = intent.getData();
+ if (data != null) {
+ String directStr = data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Direct);
+ boolean isDirect = false;
+ if (directStr != null && directStr.equals("true")) {
+ isDirect = true;
+ }
+
+ String url = data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_URL);
+ if (url != null) {
+ boolean isCustom = KMKeyboardDownloaderActivity.isCustom(url);
+
+ int index = url.lastIndexOf("/") + 1;
+ String filename = "unknown:";
+ if (index >= 0 && index <= url.length()) {
+ filename = url.substring(index);
+ }
+
+ Bundle bundle = new Bundle();
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_KEYBOARD,
+ data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Keyboard));
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_LANGUAGE,
+ data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Language));
+ bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom);
+ bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_DIRECT, isDirect);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, url);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_FILENAME, filename);
+ Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class);
+ i.putExtras(bundle);
+ startActivity(i);
+ }
+ }
+ intent.setData(null);
}
@Override
@@ -190,7 +224,10 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
super.onPause();
KMManager.onPause();
KMManager.removeKeyboardEventListener(this);
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
+
SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString(userTextKey, textView.getText().toString());
@@ -470,102 +507,6 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
startActivity(getStartedIntent);
}
- private boolean checkUrl() {
- // return true if requires user action (e.g. Download dialog)
- Intent i = getIntent();
- Uri data = i.getData();
- if (data == null)
- return false;
-
- final Context context = this;
- final String keyboard = data.getQueryParameter("keyboard");
- final String language = data.getQueryParameter("language");
- final String url = data.getQueryParameter("url");
-
- if (url != null) {
- /*
- int kbIndex = -1;
- if (keyboard != null && language != null)
- kbIndex = KMManager.getKeyboardIndex(this, keyboard, language);
-
- if (kbIndex >= 0) {
- KMManager.setKeyboard(context, kbIndex);
- return false;
- }*/
-
- String direct = data.getQueryParameter("direct");
- final boolean isDirect;
- if (direct != null && direct.equals("true")) {
- isDirect = true;
- } else {
- isDirect = false;
- }
-
- int index = url.lastIndexOf("/") + 1;
- String jsonFilename = "unknown";
- if (index >= 0 && index <= url.length()) {
- jsonFilename = url.substring(index);
- }
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
- dialogBuilder.setTitle("Custom Keyboard: " + jsonFilename);
- dialogBuilder.setMessage("Would you like to download this keyboard?");
- dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Download custom keyboard
- if (KMManager.hasConnection(context)) {
- KMManager.KMCustomKeyboardDownloader.download(context, url, isDirect, true);
- } else {
- Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show();
- }
- }
- });
- dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Cancel
- }
- });
-
- AlertDialog dialog = dialogBuilder.create();
- dialog.show();
-
- return true;
- } else if (keyboard != null && language != null && !keyboard.trim().isEmpty() && !language.trim().isEmpty()) {
- int kbIndex = KMManager.getKeyboardIndex(this, keyboard, language);
-
- if (kbIndex >= 0) {
- KMManager.setKeyboard(context, kbIndex);
- return false;
- }
-
- String kbKey = String.format("%s_%s", language, keyboard);
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
- dialogBuilder.setTitle("Keyboard: " + kbKey);
- dialogBuilder.setMessage("Would you like to download this keyboard?");
- dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Download keyboard
- if (KMManager.hasConnection(context)) {
- KMManager.KMKeyboardDownloader.download(context, KMManager.KMDefault_LegacyPackageID, keyboard, language, true);
- } else {
- Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show();
- }
- }
- });
- dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Cancel
- }
- });
-
- AlertDialog dialog = dialogBuilder.create();
- dialog.show();
-
- return true;
- }
-
- return false;
- }
-
public static Drawable getActionBarDrawable(Context context) {
Point size = new Point();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml
index decccf06cb..b94832507d 100644
--- a/android/KMEA/app/src/main/AndroidManifest.xml
+++ b/android/KMEA/app/src/main/AndroidManifest.xml
@@ -28,6 +28,11 @@
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light.Dialog" >
+
+
\ No newline at end of file
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java
new file mode 100644
index 0000000000..4871bc5aed
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java
@@ -0,0 +1,42 @@
+package com.tavultesoft.kmea;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.content.DialogInterface;
+import android.os.Bundle;
+import android.widget.Toast;
+
+/**
+ * Confirmation dialog for downloading a Keyman keyboard
+ */
+public class ConfirmDialogFragment extends DialogFragment {
+ public static String ARG_TITLE = "ConfirmDialogFragment.title";
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ Bundle args = getArguments();
+ final String title = args.getString(ARG_TITLE);
+
+ return new AlertDialog.Builder(getActivity())
+ .setTitle(title)
+ .setMessage("Would you like to download this keyboard?")
+ .setPositiveButton("Download", new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which) {
+ // Download keyboard
+ if (KMManager.hasConnection(getActivity())) {
+ KMKeyboardDownloaderActivity.download(getActivity(), true);
+ } else {
+ Toast.makeText(getActivity(), "No internet connection", Toast.LENGTH_SHORT).show();
+ }
+ }
+ })
+ .setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
+ public void onClick(DialogInterface dialog, int which) {
+ // Cancel
+ }
+ })
+ .create();
+ }
+}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java
new file mode 100644
index 0000000000..cc110691ed
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java
@@ -0,0 +1,447 @@
+package com.tavultesoft.kmea;
+
+import android.app.Activity;
+import android.app.DialogFragment;
+import android.app.ProgressDialog;
+import android.content.Context;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.util.Log;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+
+import com.tavultesoft.kmea.util.FileDownloader;
+
+import static com.tavultesoft.kmea.KMManager.KMDefault_AssetPackages;
+
+public class KMKeyboardDownloaderActivity extends Activity {
+ // Bundle Keys
+ // Cloud
+ public static final String ARG_PKG_ID = "KMKeyboardActivity.pkgID";
+ public static final String ARG_KB_ID = "KMKeyboardActivity.kbID";
+ public static final String ARG_LANG_ID = "KMKeyboardActivity.langID";
+ public static final String ARG_KB_NAME = "KMKeyboardActivity.kbName";
+ public static final String ARG_LANG_NAME = "KMKeyboardActivity.langName";
+ public static final String ARG_IS_CUSTOM = "KMKeyboardActivity.isCustom";
+
+ // custom keyboard
+ public static final String ARG_KEYBOARD = "KMKeyboardActivity.keyboard";
+ public static final String ARG_LANGUAGE = "KMKeyboardActivity.language";
+ public static final String ARG_IS_DIRECT = "KMKeyboardActivity.isDirect";
+ public static final String ARG_URL = "KMKeyboardActivity.url";
+ public static final String ARG_FILENAME = "KMKeyboardActivity.filename";
+
+ public static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/";
+ public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url=";
+
+ // Keyman public keys
+ public static final String KMKey_Direct = "direct";
+ public static final String KMKey_URL = "url";
+ public static final String KMKey_Keyboard = "keyboard";
+ public static final String KMKey_LanguageKeyboards = "keyboards";
+ public static final String KMKey_Options = "options";
+ public static final String KMKey_Language = "language";
+ public static final String KMKey_Languages = "languages";
+ public static final String KMKey_Filename = "filename";
+
+ // Keyman internal keys
+ public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri";
+ public static final String KMKey_FontBaseURI = "fontBaseUri";
+
+ private static String pkgID;
+ private static String kbID;
+ private static String langID;
+ private static String kbName;
+ private static String langName;
+ private static Boolean isCustom;
+
+ private static String customKeyboard;
+ private static String customLanguage;
+ private static Boolean isDirect;
+ private static String url;
+ private static String filename;
+
+ private static ArrayList kbDownloadEventListeners = null;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Bundle bundle = getIntent().getExtras();
+ if (bundle != null) {
+ pkgID = bundle.getString(ARG_PKG_ID);
+ if (pkgID == null || pkgID.isEmpty()) {
+ pkgID = KMManager.KMDefault_LegacyPackageID;
+ }
+ kbID = bundle.getString(ARG_KB_ID);
+ langID = bundle.getString(ARG_LANG_ID);
+ kbName = bundle.getString(ARG_KB_NAME);
+ langName = bundle.getString(ARG_LANG_NAME);
+ isCustom = bundle.getBoolean(ARG_IS_CUSTOM);
+
+ // URL parameters for custom keyboard (if they exist)
+ customKeyboard = bundle.getString(ARG_KEYBOARD);
+ customLanguage = bundle.getString(ARG_LANGUAGE);
+ isDirect = bundle.getBoolean(ARG_IS_DIRECT);
+ url = bundle.getString(ARG_URL);
+ filename = bundle.getString(ARG_FILENAME);
+ if (filename == null || filename.isEmpty()) {
+ filename = "unknown";
+ }
+ } else {
+ return;
+ }
+
+ Bundle args = new Bundle();
+ String title = "";
+ if (url != null) {
+ title = "Custom Keyboard: " + filename;
+ } else if (customKeyboard != null && customLanguage != null &&
+ !customKeyboard.trim().isEmpty() && !customLanguage.trim().isEmpty()) {
+ int kbIndex = KMManager.getKeyboardIndex(getApplicationContext(), customKeyboard, customLanguage);
+
+ if (kbIndex >= 0) {
+ KMManager.setKeyboard(getApplicationContext(), kbIndex);
+ // No interaction needed
+ return; // false
+ }
+
+ title = customLanguage + "_" + customKeyboard;
+ } else {
+ // Download keyboard from cloud server
+ title = langName + ": " + kbName;
+ }
+
+ DialogFragment dialog = new ConfirmDialogFragment();
+ args.putString(ConfirmDialogFragment.ARG_TITLE, title);
+ dialog.setArguments(args);
+ dialog.show(getFragmentManager(), "dialog");
+}
+
+ /**
+ * Async task to download a Keyman keyboard from either Keyman cloud server or custom url
+ * @param context
+ * @param showProgressDialog
+ */
+ public static void download(final Context context, final boolean showProgressDialog) {
+
+ new AsyncTask() {
+ private ProgressDialog progressDialog;
+ //private String languageName = "";
+ //private String keyboardName = "";
+ private String kbVersion = "1.0";
+ private String kbIsCustom = isCustom ? "Y" : "N";
+ private String font = "";
+ private String oskFont = "";
+
+ @Override
+ protected void onPreExecute() {
+ super.onPreExecute();
+ if (showProgressDialog) {
+ progressDialog = new ProgressDialog(context);
+ progressDialog.setMessage("Downloading keyboard...");
+ progressDialog.setCancelable(false);
+ if (!((Activity) context).isFinishing()) {
+ progressDialog.show();
+ } else {
+ cancel(true);
+ progressDialog = null;
+ }
+ }
+ }
+
+ @Override
+ protected Integer doInBackground(Void... voids) {
+ int ret = -1;
+ ArrayList urls = new ArrayList();
+
+ if (isCancelled())
+ return ret;
+
+ try {
+ String exceptionStr = "Invalid keyboard";
+ if (pkgID == null || pkgID.trim().isEmpty() ||
+ (!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) {
+ throw new Exception(exceptionStr);
+ }
+
+ String deviceType = context.getResources().getString(R.string.device_type);
+ if (deviceType.equals("AndroidTablet")) {
+ deviceType = "androidtablet";
+ } else {
+ deviceType = "androidphone";
+ }
+
+ // Placeholder here to attempt to process .kmp keyboard first:
+
+ ////
+
+ JSONParser jsonParser = new JSONParser();
+ JSONObject kbData = null;
+ String remoteUrl = "";
+ if (isCustom) {
+ if (isDirect) {
+ remoteUrl = url;
+ } else {
+ String encodedUrl = URLEncoder.encode(filename, "utf-8");
+ remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType);
+ }
+ } else {
+ // Keyman cloud
+ remoteUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, langID, kbID, deviceType);
+ }
+
+ kbData = jsonParser.getJSONObjectFromUrl(remoteUrl);
+
+ exceptionStr = "Could not reach server";
+ if (kbData == null) {
+ throw new Exception(exceptionStr);
+ }
+
+ exceptionStr = "The keyboard could not be installed";
+ JSONObject options = kbData.optJSONObject(KMKey_Options);
+ if (options == null) {
+ throw new Exception(exceptionStr);
+ }
+ String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, "");
+ if (kbBaseUri.isEmpty()) {
+ throw new Exception(exceptionStr);
+ }
+
+ String fontBaseUri = options.optString(KMKey_FontBaseURI, "");
+ JSONObject language, keyboard;
+ if (!isCustom) {
+ // JSON from Keyman cloud server
+ language = kbData.optJSONObject(KMKey_Language);
+ if (language == null) {
+ throw new Exception(exceptionStr);
+ }
+
+ langName = language.optString(KMManager.KMKey_Name, "");
+
+ JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards);
+ if (keyboards == null ) {
+ throw new Exception(exceptionStr);
+ }
+
+ keyboard = keyboards.getJSONObject(0);
+ if (keyboard == null) {
+ throw new Exception(exceptionStr);
+ }
+
+
+ } else {
+ // JSON from custom URL
+ keyboard = kbData.optJSONObject(KMKey_Keyboard);
+ if (keyboard == null) {
+ throw new Exception(exceptionStr);
+ }
+
+ JSONArray languages = keyboard.optJSONArray(KMKey_Languages);
+ if (languages == null) {
+ throw new Exception(exceptionStr);
+ }
+
+ // Concatenate langID and langName
+ langID = "";
+ langName = "";
+ int langCount = languages.length();
+ for (int i=0; i < langCount; i++) {
+ langID += languages.getJSONObject(i).getString(KMManager.KMKey_ID);
+ langName += languages.getJSONObject(i).getString(KMManager.KMKey_Name);
+ if (i < langCount -1) {
+ langID += ";";
+ langName += ";";
+ }
+ }
+ pkgID = keyboard.optString(KMManager.KMKey_PackageID, KMManager.KMDefault_LegacyPackageID);
+ kbID = keyboard.optString(KMManager.KMKey_ID, "");
+
+ }
+
+ kbName = keyboard.optString(KMManager.KMKey_Name, "");
+ kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0");
+ font = keyboard.optString(KMManager.KMKey_Font, "").replace(
+ "\"" + KMKeyboardDownloaderActivity.KMKey_Filename + "\"",
+ "\"" + KMManager.KMKey_FontSource + "\"");
+ oskFont = keyboard.optString(KMManager.KMKey_OskFont, "").replace(
+ "\"" + KMKeyboardDownloaderActivity.KMKey_Filename + "\"",
+ "\"" + KMManager.KMKey_FontSource + "\"");
+ String kbFilename = keyboard.optString(KMKey_Filename, "");
+
+ if (kbName.isEmpty() || langName.isEmpty() || kbFilename.isEmpty())
+ throw new Exception(exceptionStr);
+
+ String kbUrl = kbBaseUri + kbFilename;
+ urls.add(kbUrl);
+ JSONObject jsonFont = keyboard.optJSONObject(KMManager.KMKey_Font);
+ JSONObject jsonOskFont = keyboard.optJSONObject(KMManager.KMKey_OskFont);
+ ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true);
+ ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true);
+ if (fontUrls != null)
+ urls.addAll(fontUrls);
+ if (oskFontUrls != null) {
+ for (String url : oskFontUrls) {
+ if (!urls.contains(url))
+ urls.add(url);
+ }
+ }
+
+ notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_STARTED, 0);
+
+ ret = 1;
+ int result = 0;
+ for (String url : urls) {
+ String directory = KMDefault_AssetPackages + File.separator + pkgID;
+ File dir = new File(directory);
+ String filename = "";
+ if (url.endsWith(".js")) {
+
+ int start = kbFilename.lastIndexOf("/");
+ if (start < 0) {
+ start = 0;
+ } else {
+ start++;
+ }
+ if (!kbFilename.contains("-")) {
+ filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js";
+ } else {
+ filename = kbFilename.substring(start);
+ }
+ }
+
+ result = FileDownloader.download(context, url, directory, filename);
+ if (result < 0) {
+ ret = -1;
+ break;
+ }
+ }
+ } catch (Exception e) {
+ ret = -1;
+ Log.e("Keyboard download", "Error: " + e);
+ }
+
+ return ret;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... progress) {
+ // Do nothing
+ }
+
+ @Override
+ protected void onPostExecute(Integer result) {
+ if (showProgressDialog) {
+ if (progressDialog != null && progressDialog.isShowing()) {
+ try {
+ progressDialog.dismiss();
+ progressDialog = null;
+ } catch (Exception e) {
+ progressDialog = null;
+ }
+ }
+ }
+
+ notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result);
+ super.onPostExecute(result);
+ }
+
+ /**
+ * Notify listeners when an event happens
+ * @param eventType
+ * @param result
+ */
+ protected void notifyListeners(KeyboardEventHandler.EventType eventType, int result) {
+ if (kbDownloadEventListeners != null) {
+ HashMap keyboardInfo = new HashMap();
+ keyboardInfo.put(KMManager.KMKey_PackageID, pkgID);
+ keyboardInfo.put(KMManager.KMKey_KeyboardID, kbID);
+ keyboardInfo.put(KMManager.KMKey_LanguageID, langID);
+ keyboardInfo.put(KMManager.KMKey_KeyboardName, kbName);
+ keyboardInfo.put(KMManager.KMKey_LanguageName, langName);
+ keyboardInfo.put(KMManager.KMKey_KeyboardVersion, kbVersion);
+ keyboardInfo.put(KMManager.KMKey_CustomKeyboard, kbIsCustom);
+ keyboardInfo.put(KMManager.KMKey_Font, font);
+ if (oskFont != null)
+ keyboardInfo.put(KMManager.KMKey_OskFont, oskFont);
+ KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, keyboardInfo, result);
+ }
+ }
+ }.execute();
+ }
+
+ public static boolean isCustom(String u) {
+ boolean ret = false;
+ if (u != null && !u.contains(KMKeyboardDownloaderActivity.kKeymanApiBaseURL) &&
+ !u.contains(KMKeyboardDownloaderActivity.kKeymanApiRemoteURL)) {
+ ret = true;
+ }
+ return ret;
+ }
+
+ private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
+ if (jsonFont == null)
+ return null;
+
+ ArrayList urls = new ArrayList();
+ JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource);
+ if (fontSource != null) {
+ int fcCount = fontSource.length();
+ for (int i = 0; i < fcCount; i++) {
+ String fontSourceString;
+ try {
+ fontSourceString = fontSource.getString(i);
+ if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && fontSourceString.contains(".svg#")) {
+ String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
+ urls.add(baseUri + fontFilename);
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+ } else {
+ String fontSourceString;
+ try {
+ fontSourceString = jsonFont.getString(KMManager.KMKey_FontSource);
+ if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
+ urls.add(baseUri + fontSourceString);
+ } else if (isOskFont && fontSourceString.contains(".svg#")) {
+ String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
+ urls.add(baseUri + fontFilename);
+ }
+ } catch (JSONException e) {
+ return null;
+ }
+ }
+
+ return urls;
+ }
+
+ public static void addKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
+ if (kbDownloadEventListeners == null) {
+ kbDownloadEventListeners = new ArrayList();
+ }
+
+ if (listener != null && !kbDownloadEventListeners.contains(listener)) {
+ kbDownloadEventListeners.add(listener);
+ }
+ }
+
+ public static void removeKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
+ if (kbDownloadEventListeners != null) {
+ kbDownloadEventListeners.remove(listener);
+ }
+ }
+}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
index 953d4dc488..ff3082d58e 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
@@ -4,24 +4,15 @@
package com.tavultesoft.kmea;
-import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.URL;
-import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import android.annotation.SuppressLint;
-import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -33,7 +24,6 @@ import android.graphics.Typeface;
import android.inputmethodservice.InputMethodService;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.text.InputType;
@@ -53,9 +43,7 @@ import android.widget.FrameLayout;
import android.widget.RelativeLayout;
import com.tavultesoft.kmea.KeyboardEventHandler.EventType;
-import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
-import com.tavultesoft.kmea.util.Connection;
public final class KMManager {
@@ -86,7 +74,6 @@ public final class KMManager {
private static InputMethodService IMService;
private static boolean debugMode = false;
private static boolean shouldAllowSetKeyboard = true;
- private static ArrayList kbDownloadEventListeners = null;
private static boolean didCopyAssets = false;
private static GlobeKeyAction inappKbGlobeKeyAction = GlobeKeyAction.GLOBE_KEY_ACTION_SHOW_MENU;
private static GlobeKeyAction sysKbGlobeKeyAction = GlobeKeyAction.GLOBE_KEY_ACTION_SHOW_MENU;
@@ -100,9 +87,6 @@ public final class KMManager {
protected static KMKeyboard InAppKeyboard = null;
protected static KMKeyboard SystemKeyboard = null;
- protected static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/";
- private static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url=";
-
// Keyman public keys
public static final String KMKey_ID = "id";
public static final String KMKey_Name = "name";
@@ -112,18 +96,12 @@ public final class KMManager {
public static final String KMKey_KeyboardID = "kbId";
public static final String KMKey_KeyboardName = "kbName";
public static final String KMKey_KeyboardVersion = "version";
- public static final String KMKey_Keyboard = "keyboard";
- public static final String KMKey_LanguageKeyboards = "keyboards";
public static final String KMKey_KeyboardFileSize = "fileSize";
public static final String KMKey_Font = "font";
public static final String KMKey_OskFont = "oskFont";
public static final String KMKey_FontFamily = "family";
public static final String KMKey_FontSource = "source";
public static final String KMKey_FontFiles = "files";
- public static final String KMKey_Options = "options";
- public static final String KMKey_Language = "language";
- public static final String KMKey_Languages = "languages";
- public static final String KMKey_Filename = "filename";
public static final String KMKey_KeyboardModified = "lastModified";
public static final String KMKey_KeyboardRTL = "rtl";
public static final String KMKey_CustomKeyboard = "CustomKeyboard";
@@ -131,8 +109,6 @@ public final class KMManager {
public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex";
// Keyman internal keys
- protected static final String KMKey_KeyboardBaseURI = "keyboardBaseUri";
- protected static final String KMKey_FontBaseURI = "fontBaseUri";
protected static final String KMKey_ShouldShowHelpBubble = "ShouldShowHelpBubble";
// Default Asset Paths
@@ -465,47 +441,53 @@ public final class KMManager {
}
String[] files = legacyLanguagesDir.list();
- for (String filename : files) {
- // Handle keyboards missing version number
- if (filename.lastIndexOf("-") < 0) {
- if (filename.equals("us.js")) {
- File kbFile = new File(legacyLanguagesPath, filename);
- kbFile.delete();
- } else if (filename.endsWith(".js")) {
- // Append default version number to keyboard and move
- String newFilename = filename.substring(0, filename.lastIndexOf(".js")) + "-1.0.js";
- File kbFile = new File(legacyLanguagesPath, filename);
- kbFile.renameTo(new File(migratedDir, newFilename));
- }
- // Handle keyboards with version number
- } else {
- // Remove legacy default keyboards
- if (filename.startsWith(KMDefault_KeyboardID + "-")) {
- File legacyKbFile = new File(legacyLanguagesDir, filename);
- if (legacyKbFile.exists()) {
- legacyKbFile.delete();
- continue;
+ if (files != null) {
+ for (String filename : files) {
+ // Handle keyboards missing version number
+ if (filename.lastIndexOf("-") < 0) {
+ if (filename.equals("us.js")) {
+ File kbFile = new File(legacyLanguagesPath, filename);
+ kbFile.delete();
+ } else if (filename.endsWith(".js")) {
+ // Append default version number to keyboard and move
+ String newFilename = filename.substring(0, filename.lastIndexOf(".js")) + "-1.0.js";
+ File kbFile = new File(legacyLanguagesPath, filename);
+ kbFile.renameTo(new File(migratedDir, newFilename));
+ }
+ // Handle keyboards with version number
+ } else {
+ // Remove legacy default keyboards
+ if (filename.startsWith(KMDefault_KeyboardID + "-")) {
+ File legacyKbFile = new File(legacyLanguagesDir, filename);
+ if (legacyKbFile.exists()) {
+ legacyKbFile.delete();
+ continue;
+ }
}
- }
- // Migrate /languages/*.js keyboards
- File kbFile = new File(legacyLanguagesPath, filename);
- kbFile.renameTo(new File(migratedDir, filename));
+ // Migrate /languages/*.js keyboards
+ File kbFile = new File(legacyLanguagesPath, filename);
+ kbFile.renameTo(new File(migratedDir, filename));
+ }
+ }
+
+ // Cleanup empty directory
+ if (legacyLanguagesDir.list().length == 0) {
+ legacyLanguagesDir.delete();
}
}
files = legacyFontsDir.list();
- for (String filename : files) {
- File fontFile = new File(legacyFontsPath, filename);
- fontFile.renameTo(new File(migratedDir, filename));
- }
+ if (files != null) {
+ for (String filename : files) {
+ File fontFile = new File(legacyFontsPath, filename);
+ fontFile.renameTo(new File(migratedDir, filename));
+ }
- // Cleanup empty directories
- if (legacyLanguagesDir.list().length == 0) {
- legacyLanguagesDir.delete();
- }
- if (legacyFontsDir.list().length == 0) {
- legacyFontsDir.delete();
+ // Cleanup empty directory
+ if (legacyFontsDir.list().length == 0) {
+ legacyFontsDir.delete();
+ }
}
}
@@ -636,783 +618,6 @@ public final class KMManager {
return KeyboardPickerActivity.getKeyboardsList(context);
}
- public static final class KMKeyboardDownloader {
-
- public static void download(final Context context, final int languageIndex, final int keyboardIndex, final boolean showProgressDialog) {
- new AsyncTask() {
- private ProgressDialog progressDialog;
- private String packageID = "";
- private String languageID = "";
- private String keyboardID = "";
- private String languageName = "";
- private String keyboardName = "";
- private String kbVersion = "1.0";
- private String isCustom = "N";
- private String font = "";
- private String oskFont = null;
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (showProgressDialog) {
- progressDialog = new ProgressDialog(context);
- progressDialog.setMessage("Downloading keyboard...");
- progressDialog.setCancelable(false);
- if (!((Activity) context).isFinishing()) {
- progressDialog.show();
- } else {
- cancel(true);
- progressDialog = null;
- }
- }
- }
-
- @Override
- protected Integer doInBackground(Void... voids) {
- int ret = -1;
- ArrayList urls = new ArrayList();
-
- if (isCancelled())
- return ret;
-
- try {
- JSONArray languages = LanguageListActivity.languages();
- JSONObject options = LanguageListActivity.options();
-
- if (languages == null || options == null) {
- throw new Exception("Language list is empty");
- }
-
- JSONObject language = languages.getJSONObject(languageIndex);
- JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards);
- JSONObject keyboard = keyboards.getJSONObject(keyboardIndex);
-
- if (keyboard.has(KMKey_PackageID)) {
- packageID = keyboard.getString(KMKey_PackageID);
- } else {
- packageID = KMDefault_LegacyPackageID;
- }
- keyboardID = keyboard.getString(KMKey_ID);
- languageID = language.getString(KMKey_ID);
- keyboardName = keyboard.getString(KMKey_Name);
- languageName = language.getString(KMKey_Name);
- kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0");
- font = keyboard.optString(KMKey_Font, "");
- oskFont = keyboard.optString(KMKey_OskFont, null);
- String kbFilename = keyboard.optString(KMKey_Filename, "");
-
- String kbUrl = options.getString(KMKey_KeyboardBaseURI) + kbFilename;
- urls.add(kbUrl);
- JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font);
- JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont);
- String fontBaseUri = options.getString(KMKey_FontBaseURI);
- ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true);
- ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true);
- if (fontUrls != null)
- urls.addAll(fontUrls);
- if (oskFontUrls != null) {
- for (String url : oskFontUrls) {
- if (!urls.contains(url))
- urls.add(url);
- }
- }
-
- // Notify listeners: onDownloadStarted
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_PackageID, packageID);
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMKey_OskFont, oskFont);
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0);
- }
-
- ret = 1;
- int result = 0;
- for (String url : urls) {
- String directory = KMDefault_AssetPackages + File.separator + packageID;
- File dir = new File(directory);
- String filename = "";
- if (url.endsWith(".js")) {
- int start = kbFilename.lastIndexOf("/");
- if (start < 0) {
- start = 0;
- } else {
- start++;
- }
- if (!kbFilename.contains("-")) {
- filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js";
- } else {
- filename = kbFilename.substring(start);
- }
- }
-
- result = FileDownloader.download(context, url, directory, filename);
- if (result < 0) {
- ret = -1;
- break;
- }
- }
- } catch (Exception e) {
- ret = -1;
- Log.e("Keyboard download", "Error: " + e);
- }
-
- return ret;
- }
-
- @Override
- protected void onProgressUpdate(Integer... progress) {
- // Do nothing
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (showProgressDialog) {
- if (progressDialog != null && progressDialog.isShowing()) {
- try {
- progressDialog.dismiss();
- progressDialog = null;
- } catch (Exception e) {
- progressDialog = null;
- }
- }
- }
-
- // Notify listeners: onDownloadFinished
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_PackageID, packageID);
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMKey_OskFont, oskFont);
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result);
- }
- }
- }.execute();
- }
-
- public static void download(final Context context, final String packageID, final String keyboardID, final String languageID, final boolean showProgressDialog) {
- new AsyncTask() {
- private ProgressDialog progressDialog;
- private String languageName = "";
- private String keyboardName = "";
- private String kbVersion = "1.0";
- private String isCustom = "N";
- private String font = "";
- private String oskFont = "";
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (showProgressDialog) {
- progressDialog = new ProgressDialog(context);
- progressDialog.setMessage("Downloading keyboard...");
- progressDialog.setCancelable(false);
- if (!((Activity) context).isFinishing()) {
- progressDialog.show();
- } else {
- cancel(true);
- progressDialog = null;
- }
- }
- }
-
- @Override
- protected Integer doInBackground(Void... voids) {
- int ret = -1;
- ArrayList urls = new ArrayList();
-
- if (isCancelled())
- return ret;
-
- try {
- String exceptionStr = "Invalid keyboard";
- if (packageID == null || packageID.trim().isEmpty() ||
- languageID == null || languageID.trim().isEmpty() ||
- keyboardID == null || keyboardID.trim().isEmpty())
- throw new Exception(exceptionStr);
-
- String deviceType = context.getResources().getString(R.string.device_type);
- if (deviceType.equals("AndroidTablet")) {
- deviceType = "androidtablet";
- } else {
- deviceType = "androidphone";
- }
-
- String jsonUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, languageID, keyboardID, deviceType);
- JSONParser jsonParser = new JSONParser();
- JSONObject kbData = jsonParser.getJSONObjectFromUrl(jsonUrl);
-
- exceptionStr = "Could not reach Keyman server";
- if (kbData == null) {
- throw new Exception(exceptionStr);
- }
-
- JSONObject options = kbData.optJSONObject(KMKey_Options);
- JSONObject language = kbData.optJSONObject(KMKey_Language);
- exceptionStr = "The keyboard could not be installed";
- if (options == null || language == null) {
- throw new Exception(exceptionStr);
- }
-
- JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards);
- String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, "");
- String fontBaseUri = options.optString(KMKey_FontBaseURI, "");
-
- if (keyboards == null || kbBaseUri.isEmpty())
- throw new Exception(exceptionStr);
-
- JSONObject keyboard = keyboards.getJSONObject(0);
- if (keyboard == null)
- throw new Exception(exceptionStr);
-
- languageName = language.optString(KMKey_Name, "");
- keyboardName = keyboard.optString(KMKey_Name, "");
- kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0");
- font = keyboard.optString(KMKey_Font, "");
- oskFont = keyboard.optString(KMKey_OskFont, null);
- String kbFilename = keyboard.optString(KMKey_Filename, "");
-
- if (keyboardName.isEmpty() || languageName.isEmpty() || kbFilename.isEmpty())
- throw new Exception(exceptionStr);
-
- String kbUrl = kbBaseUri + kbFilename;
- urls.add(kbUrl);
- JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font);
- JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont);
- ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true);
- ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true);
- if (fontUrls != null)
- urls.addAll(fontUrls);
- if (oskFontUrls != null) {
- for (String url : oskFontUrls) {
- if (!urls.contains(url))
- urls.add(url);
- }
- }
-
- // Notify listeners: onDownloadStarted
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_PackageID, packageID);
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMKey_OskFont, oskFont);
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0);
- }
-
- ret = 1;
- int result = 0;
- for (String url : urls) {
- String directory = KMDefault_AssetPackages + File.separator + packageID;
- File dir = new File(directory);
- String filename = "";
- if (url.endsWith(".js")) {
-
- int start = kbFilename.lastIndexOf("/");
- if (start < 0) {
- start = 0;
- } else {
- start++;
- }
- if (!kbFilename.contains("-")) {
- filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js";
- } else {
- filename = kbFilename.substring(start);
- }
- }
-
- result = FileDownloader.download(context, url, directory, filename);
- if (result < 0) {
- ret = -1;
- break;
- }
- }
- } catch (Exception e) {
- ret = -1;
- Log.e("Keyboard download", "Error: " + e);
- }
-
- return ret;
- }
-
- @Override
- protected void onProgressUpdate(Integer... progress) {
- // Do nothing
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (showProgressDialog) {
- if (progressDialog != null && progressDialog.isShowing()) {
- try {
- progressDialog.dismiss();
- progressDialog = null;
- } catch (Exception e) {
- progressDialog = null;
- }
- }
- }
-
- // Notify listeners: onDownloadFinished
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMKey_OskFont, oskFont);
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result);
- }
- }
- }.execute();
- }
-
- private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
- if (jsonFont == null)
- return null;
-
- ArrayList urls = new ArrayList();
- JSONArray fontSource = jsonFont.optJSONArray(KMKey_FontSource);
- if (fontSource != null) {
- int fcCount = fontSource.length();
- for (int i = 0; i < fcCount; i++) {
- String fontSourceString;
- try {
- fontSourceString = fontSource.getString(i);
- if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && fontSourceString.contains(".svg#")) {
- String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
- } else {
- String fontSourceString;
- try {
- fontSourceString = jsonFont.getString(KMKey_FontSource);
- if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && fontSourceString.contains(".svg#")) {
- String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
-
- return urls;
- }
- }
-
- public static final class KMCustomKeyboardDownloader {
-
- public static void download(final Context context, final String jsonUrl, final boolean isDirect, final boolean showProgressDialog) {
- new AsyncTask() {
- private ProgressDialog progressDialog;
- private String packageID = "";
- private String keyboardID = "";
- private String languageID = "";
- private String keyboardName = "";
- private String languageName = "";
- private String kbVersion = "1.0";
- private String isCustom = "Y";
- private String font = "";
- private String oskFont = "";
-
- @Override
- protected void onPreExecute() {
- super.onPreExecute();
- if (showProgressDialog) {
- progressDialog = new ProgressDialog(context);
- progressDialog.setMessage("Downloading keyboard...");
- progressDialog.setCancelable(false);
- if (!((Activity) context).isFinishing()) {
- progressDialog.show();
- } else {
- cancel(true);
- progressDialog = null;
- }
- }
- }
-
- @Override
- protected Integer doInBackground(Void... voids) {
- int ret = -1;
- ArrayList urls = new ArrayList();
-
- if (isCancelled())
- return ret;
-
- try {
- JSONParser jsonParser = new JSONParser();
- JSONObject customKb = null;
- if (isDirect) {
- customKb = jsonParser.getJSONObjectFromUrl(jsonUrl);
- } else {
- String deviceType = context.getResources().getString(R.string.device_type);
- if (deviceType.equals("AndroidTablet")) {
- deviceType = "androidtablet";
- } else {
- deviceType = "androidphone";
- }
-
- String encodedUrl = URLEncoder.encode(jsonUrl, "utf-8");
- String remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType);
- customKb = jsonParser.getJSONObjectFromUrl(remoteUrl);
- }
-
- String exceptionStr = "Failed to fetch JSON object from the URL";
- if (customKb == null) {
- throw new Exception(exceptionStr);
- }
-
- JSONObject options = customKb.optJSONObject(KMKey_Options);
- exceptionStr = "The keyboard could not be installed";
- if (options == null) {
- throw new Exception(exceptionStr);
- }
-
- String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, "");
- String fontBaseUri = options.optString(KMKey_FontBaseURI, "");
-
- JSONObject keyboard = customKb.getJSONObject(KMKey_Keyboard);
-
- if (keyboard == null || kbBaseUri.isEmpty()) {
- throw new Exception(exceptionStr);
- }
-
- JSONArray languages = keyboard.optJSONArray(KMKey_Languages);
-
- packageID = keyboard.optString(KMKey_PackageID, "");
- if (packageID == null || packageID.isEmpty()) {
- packageID = KMDefault_LegacyPackageID;
- }
- keyboardID = keyboard.optString(KMKey_ID, "");
- keyboardName = keyboard.optString(KMKey_Name, "");
- kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0");
- font = keyboard.optString(KMKey_Font, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\"");
- oskFont = keyboard.optString(KMKey_OskFont, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\"");
- if (oskFont.isEmpty()) {
- oskFont = null;
- }
- String kbFilename = keyboard.optString(KMKey_Filename, "");
-
- if (packageID == null || packageID.isEmpty() ||
- languages == null || keyboardID.isEmpty() ||
- keyboardName.isEmpty() || kbFilename.isEmpty()) {
- throw new Exception(exceptionStr);
- }
-
- String kbUrl = kbBaseUri + kbFilename;
- urls.add(kbUrl);
- JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font);
- JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont);
- ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true);
- ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true);
- if (fontUrls != null) {
- urls.addAll(fontUrls);
- }
- if (oskFontUrls != null) {
- for (String url : oskFontUrls) {
- if (!urls.contains(url)) {
- urls.add(url);
- }
- }
- }
-
- languageID = "";
- languageName = "";
- int langCount = languages.length();
- for (int i = 0; i < langCount; i++) {
- languageID += languages.getJSONObject(i).getString(KMKey_ID);
- languageName += languages.getJSONObject(i).getString(KMKey_Name);
- if (i < langCount - 1) {
- languageID += ";";
- languageName += ";";
- }
- }
-
- // Notify listeners: onDownloadStarted
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_PackageID, packageID);
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null) {
- keyboardInfo.put(KMKey_OskFont, oskFont);
- }
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0);
- }
-
- ret = 1;
- int result = 0;
- for (String url : urls) {
- String directory = KMDefault_AssetPackages + File.separator + packageID;
- File dir = new File(directory);
- String filename = "";
- if (url.endsWith(".js")) {
- int start = kbFilename.lastIndexOf("/");
- if (start < 0) {
- start = 0;
- } else {
- start++;
- }
- if (!kbFilename.contains("-")) {
- filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js";
- } else {
- filename = kbFilename.substring(start);
- }
- }
-
- result = FileDownloader.download(context, url, directory, filename);
- if (result < 0) {
- ret = -1;
- break;
- }
- }
- } catch (Exception e) {
- ret = -1;
- Log.e("Keyboard download", "Error: " + e);
- e.printStackTrace();
- }
-
- return ret;
- }
-
- @Override
- protected void onProgressUpdate(Integer... progress) {
- // Do nothing
- }
-
- @Override
- protected void onPostExecute(Integer result) {
- if (showProgressDialog) {
- if (progressDialog != null && progressDialog.isShowing()) {
- try {
- progressDialog.dismiss();
- progressDialog = null;
- } catch (Exception e) {
- progressDialog = null;
- }
- }
- }
-
- // Notify listeners: onDownloadFinished
- if (kbDownloadEventListeners != null) {
- HashMap keyboardInfo = new HashMap();
- keyboardInfo.put(KMKey_PackageID, packageID);
- keyboardInfo.put(KMKey_KeyboardID, keyboardID);
- keyboardInfo.put(KMKey_LanguageID, languageID);
- keyboardInfo.put(KMKey_KeyboardName, keyboardName);
- keyboardInfo.put(KMKey_LanguageName, languageName);
- keyboardInfo.put(KMKey_KeyboardVersion, kbVersion);
- keyboardInfo.put(KMKey_CustomKeyboard, isCustom);
- keyboardInfo.put(KMKey_Font, font);
- if (oskFont != null)
- keyboardInfo.put(KMKey_OskFont, oskFont);
- KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result);
- }
-
- if (result > 0) {
- if (KMManager.InAppKeyboard != null) {
- InAppKeyboard.loadKeyboard();
- }
- if ( KMManager.SystemKeyboard != null) {
- SystemKeyboard.loadKeyboard();
- }
- }
- }
- }.execute();
- }
-
- private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
- if (jsonFont == null)
- return null;
-
- ArrayList urls = new ArrayList();
- JSONArray fontSource = jsonFont.optJSONArray(KMKey_FontSource);
- if (fontSource == null)
- fontSource = jsonFont.optJSONArray(KMKey_Filename); // Font filename is deprecated
-
- if (fontSource != null) {
- int fcCount = fontSource.length();
- for (int i = 0; i < fcCount; i++) {
- String fontSourceString;
- try {
- fontSourceString = fontSource.getString(i);
- if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && fontSourceString.contains(".svg#")) {
- String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
- } else {
- String fontSourceString;
- try {
- fontSourceString = jsonFont.optString(KMKey_FontSource, null);
- if (fontSourceString == null)
- fontSourceString = jsonFont.getString(KMKey_Filename); // Font filename is deprecated
-
- if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) {
- urls.add(baseUri + fontSourceString);
- } else if (isOskFont && fontSourceString.contains(".svg#")) {
- String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5);
- urls.add(baseUri + fontFilename);
- }
- } catch (JSONException e) {
- return null;
- }
- }
-
- return urls;
- }
- }
-
- private static final class FileDownloader {
-
- /**
- * Utility to download a file from urlStr and store it at directory/filename
- * @param context
- * @param urlStr URL of the file to download
- * @param directory Path to store the file. If directory does not exist, it will be created
- * @param filename Destination filename. If blank, it will use the filename from the URL
- * @return
- */
- public static int download(Context context, String urlStr, String directory, String filename) {
- int ret = -1;
- String fileName = "";
- String tmpFileName = "";
- File tmpFile = null;
- File file = null;
-
- try {
- if (directory == null) {
- directory = "";
- }
- directory = directory.trim();
-
- String dirPath;
- if (directory.length() != 0) {
- directory = directory + "/";
- dirPath = context.getDir("data", Context.MODE_PRIVATE) + "/" + directory;
- } else {
- dirPath = context.getDir("data", Context.MODE_PRIVATE).toString();
- }
- File dir = new File(dirPath);
- if (!dir.exists()) {
- dir.mkdir();
- }
-
- if (Connection.initialize(urlStr)) {
- InputStream binStream = new BufferedInputStream(Connection.getInputStream(), 4096);
- byte[] buff = new byte[4096];
-
- filename = filename.trim();
- if (filename == null || filename.isEmpty()) {
- fileName = Connection.getFile().substring(Connection.getFile().lastIndexOf('/') + 1);
- if (fileName.lastIndexOf(".js") > 0 && !fileName.contains("-")) {
- fileName = fileName.substring(0, filename.lastIndexOf(".js")) + "-1.0.js";
- }
- } else {
- fileName = filename;
- }
- tmpFileName = String.format("%s.tmp", fileName);
- file = new File(dirPath, fileName);
- tmpFile = new File(dirPath, tmpFileName);
- FileOutputStream fos = new FileOutputStream(tmpFile);
-
- int len;
- while ((len = binStream.read(buff)) != -1) {
- fos.write(buff, 0, len);
- }
-
- fos.flush();
- fos.close();
- binStream.close();
-
- ret = 1;
- }
- } catch (Exception e) {
- ret = -1;
- Log.e("FD: Download failed!", "Error: " + e);
- } finally {
- if (ret > 0) {
- if (tmpFile.exists() && tmpFile.length() > 0) {
- if (file.exists()) {
- file.delete();
- }
- if (!tmpFile.renameTo(file)) {
- ret = -1;
- } else if (isDebugMode()) {
- Log.d("FD: Download finished", "Filename = " + file.toString());
- }
- } else {
- ret = -1;
- }
- } else {
- if (file.exists()) {
- file.delete();
- }
- if (tmpFile.exists()) {
- tmpFile.delete();
- }
- if (isDebugMode()) {
- Log.d("FD: Could not download", "Filename = " + file.toString());
- }
- }
-
- Connection.disconnect();
- }
-
- return ret;
- }
- }
-
public static boolean addKeyboard(Context context, HashMap keyboardInfo) {
return KeyboardPickerActivity.addKeyboard(context, keyboardInfo);
}
@@ -1731,22 +936,6 @@ public final class KMManager {
KMKeyboard.removeOnKeyboardEventListener(listener);
}
- public static void addKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener) {
- if (kbDownloadEventListeners == null) {
- kbDownloadEventListeners = new ArrayList();
- }
-
- if (listener != null && !kbDownloadEventListeners.contains(listener)) {
- kbDownloadEventListeners.add(listener);
- }
- }
-
- public static void removeKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener) {
- if (kbDownloadEventListeners != null) {
- kbDownloadEventListeners.remove(listener);
- }
- }
-
public static int getKeyboardHeight(Context context) {
return (int) context.getResources().getDimension(R.dimen.keyboard_height);
}
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java
index 90a0685c99..1540f7adaa 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java
@@ -14,9 +14,8 @@ import org.json.JSONObject;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener;
import android.app.Activity;
-import android.app.AlertDialog;
+import android.app.DialogFragment;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
@@ -73,7 +72,7 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo
final String langName = language.getString(KMManager.KMKey_Name);
textView.setText(langName);
- keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards);
+ keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards);
keyboardsArrayList = new ArrayList>();
int length = keyboards.length();
@@ -103,45 +102,23 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo
@Override
public void onItemClick(AdapterView> parent, View view, final int position, long id) {
HashMap kbInfo = LanguageListActivity.getKeyboardInfo(langIndex, position);
- String pkgID = kbInfo.get(KMManager.KMKey_PackageID);
- String kbID = kbInfo.get(KMManager.KMKey_KeyboardID);
- String langID = kbInfo.get(KMManager.KMKey_LanguageID);
+ final String pkgID = kbInfo.get(KMManager.KMKey_PackageID);
+ final String kbID = kbInfo.get(KMManager.KMKey_KeyboardID);
+ final String langID = kbInfo.get(KMManager.KMKey_LanguageID);
String kbName = kbInfo.get(KMManager.KMKey_KeyboardName);
String langName = kbInfo.get(KMManager.KMKey_LanguageName);
- String kFont = kbInfo.get(KMManager.KMKey_Font);
- String kOskFont = kbInfo.get(KMManager.KMKey_OskFont);
- KMManager.KeyboardState kbState = KMManager.getKeyboardState(context, pkgID, kbID, langID);
- //if (kbState == KMManager.KeyboardState.KEYBOARD_STATE_NEEDS_DOWNLOAD) {
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
- dialogBuilder.setTitle(langName + ": " + kbName);
- dialogBuilder.setMessage("Would you like to download this keyboard?");
- dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Download keyboard
- if (KMManager.hasConnection(context)) {
- KMManager.KMKeyboardDownloader.download(context, langIndex, position, true);
- } else {
- Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show();
- }
- }
- });
- dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Cancel
- }
- });
+ Bundle args = new Bundle();
+ args.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID);
+ args.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID);
+ args.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID);
+ args.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName);
+ args.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName);
+ args.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false);
+ Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class);
+ i.putExtras(args);
+ startActivity(i);
- AlertDialog dialog = dialogBuilder.create();
- dialog.show();
- /*} else {
- KeyboardPickerActivity.addKeyboard(context, kbInfo);
- if (KMManager.InAppKeyboard != null)
- KMManager.InAppKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont);
- if (KMManager.SystemKeyboard != null)
- KMManager.SystemKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont);
- finish();
- }*/
}
});
} catch (JSONException e) {
@@ -152,13 +129,15 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo
@Override
protected void onResume() {
super.onResume();
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
}
@Override
protected void onPause() {
super.onPause();
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
}
@Override
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java
index 164f46c8e5..28eb27ad09 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java
@@ -180,7 +180,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
@Override
protected void onResume() {
super.onResume();
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
BaseAdapter adapter = (BaseAdapter) listAdapter;
adapter.notifyDataSetChanged();
int curKbPos = getCurrentKeyboardIndex();
@@ -234,7 +234,10 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
@Override
protected void onPause() {
super.onPause();
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
+
if (didUpdate) {
if (KMManager.InAppKeyboard != null) {
KMManager.InAppKeyboard.loadKeyboard();
@@ -400,7 +403,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
list = (ArrayList>) inputStream.readObject();
inputStream.close();
} catch (Exception e) {
- Log.e("Failed to read keyboards list", "Error: " + e);
+ Log.e("KeyboardPickerActivity", "Failed to read keyboards list. Error: " + e);
list = null;
}
}
@@ -563,10 +566,10 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
String languageID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID);
String keyboardID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID);
String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion);
- String url = String.format("%slanguages/%s/%s?device=%s", KMManager.kKeymanApiBaseURL, languageID, keyboardID, deviceType);
+ String url = String.format("%slanguages/%s/%s?device=%s", KMKeyboardDownloaderActivity.kKeymanApiBaseURL, languageID, keyboardID, deviceType);
JSONObject kbData = jsonParser.getJSONObjectFromUrl(url);
- JSONObject language = kbData.optJSONObject(KMManager.KMKey_Language);
- JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards);
+ JSONObject language = kbData.optJSONObject(KMKeyboardDownloaderActivity.KMKey_Language);
+ JSONArray keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards);
JSONObject keyboard = keyboards.getJSONObject(0);
String newKbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0");
String kbKey = String.format("%s_%s", languageID, keyboardID);
@@ -615,10 +618,12 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
if (KMManager.hasConnection(context)) {
int len = keyboardsList.size();
for (int i = 0; i < len; i++) {
- String packageID = keyboardsList.get(i).get(KMManager.KMKey_PackageID);
- String languageID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID);
- String keyboardID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID);
- String kbKey = String.format("%s_%s", languageID, keyboardID);
+ String pkgID = keyboardsList.get(i).get(KMManager.KMKey_PackageID);
+ String kbID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID);
+ String langID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID);
+ String kbKey = String.format("%s_%s", langID, kbID);
+ String langName = keyboardsList.get(i).get(KMManager.KMKey_LanguageName);
+ String kbName = keyboardsList.get(i).get(KMManager.KMKey_KeyboardName);
String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion);
String newKbVersion = keyboardVersions.get(kbKey);
if (newKbVersion != null) {
@@ -631,7 +636,16 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
updateProgress.show();
}
- KMManager.KMKeyboardDownloader.download(context, packageID, keyboardID, languageID, false);
+ Bundle bundle = new Bundle();
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName);
+ bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false);
+ Intent intent = new Intent(context, KMKeyboardDownloaderActivity.class);
+ intent.putExtras(bundle);
+ context.startActivity(intent);
}
}
}
@@ -702,6 +716,11 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
String languageID = keyboardInfo.get(KMManager.KMKey_LanguageID);
String kbKey = String.format("%s_%s", languageID, keyboardID);
int index = getKeyboardIndex(this, kbKey);
+ if (index == -1) {
+ // Add the downloaded keyboard if not found
+ addKeyboard(this, keyboardInfo);
+ index = getKeyboardIndex(this, kbKey);
+ }
keyboardsList.set(index, keyboardInfo);
saveKeyboardsList(this);
} else if (result < 0) {
@@ -738,5 +757,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard
checkingUpdates = false;
}
}
+ if (updateProgress != null && updateProgress.isShowing()) {
+ updateProgress.dismiss();
+ }
}
}
\ No newline at end of file
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
index 076da058b8..1d7c15c588 100644
--- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java
@@ -24,10 +24,8 @@ import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener
import android.annotation.SuppressLint;
import android.app.Activity;
-import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -97,7 +95,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
@Override
protected void onResume() {
super.onResume();
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
if (!didExecuteParser) {
didExecuteParser = true;
new JSONParse().execute();
@@ -107,7 +105,9 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
@Override
protected void onPause() {
super.onPause();
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
}
@Override
@@ -148,7 +148,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
String langName = language.getString(KMManager.KMKey_Name);
String pkgID;
- JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards);
+ JSONArray keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards);
if (keyboards.getJSONObject(keyboardIndex).has(KMManager.KMKey_PackageID)) {
pkgID = keyboards.getJSONObject(keyboardIndex).getString(KMManager.KMKey_PackageID);
} else {
@@ -187,8 +187,8 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
return null;
}
- languages = jsonObj.getJSONObject(KMManager.KMKey_Languages).getJSONArray(KMManager.KMKey_Languages);
- options = jsonObj.getJSONObject(KMManager.KMKey_Options);
+ languages = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Languages).getJSONArray(KMKeyboardDownloaderActivity.KMKey_Languages);
+ options = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Options);
keyboardsInfo = new HashMap>();
keyboardModifiedDates = new HashMap();
@@ -204,7 +204,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
String kbVersion = "1.0";
String isCustom = "N";
String kbFont = "";
- JSONArray langKeyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards);
+ JSONArray langKeyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards);
JSONObject keyboard = null;
int kbLength = langKeyboards.length();
@@ -272,7 +272,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
jsonObj = new JSONObject(objInput.readObject().toString());
objInput.close();
} catch (Exception e) {
- Log.e("Failed to read from cache file", "Error: " + e);
+ Log.e("LanguageListActivity", "Failed to read from cache file. Error: " + e);
jsonObj = null;
}
@@ -287,7 +287,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
objOutput.writeObject(jsonObj.toString());
objOutput.close();
} catch (Exception e) {
- Log.e("Failed to save to cache file", "Error: " + e);
+ Log.e("LanguageListActivity", "Failed to save to cache file. Error: " + e);
}
}
@@ -361,7 +361,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
deviceType = "androidphone";
}
- jsonObj = jsonParser.getJSONObjectFromUrl(KMManager.kKeymanApiBaseURL + "languages?device=" + deviceType);
+ jsonObj = jsonParser.getJSONObjectFromUrl(KMKeyboardDownloaderActivity.kKeymanApiBaseURL + "languages?device=" + deviceType);
} catch (Exception e) {
jsonObj = null;
}
@@ -392,8 +392,8 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
}
try {
- languages = jsonObj.getJSONObject(KMManager.KMKey_Languages).getJSONArray(KMManager.KMKey_Languages);
- options = jsonObj.getJSONObject(KMManager.KMKey_Options);
+ languages = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Languages).getJSONArray(KMKeyboardDownloaderActivity.KMKey_Languages);
+ options = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Options);
keyboardsInfo = new HashMap>();
keyboardModifiedDates = new HashMap();
@@ -411,7 +411,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
String kbFont = "";
String icon = "0";
String isEnabled = "true";
- JSONArray langKeyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards);
+ JSONArray langKeyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards);
JSONObject keyboard = null;
int kbLength = langKeyboards.length();
@@ -494,44 +494,20 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo
startActivity(i);
} else {
HashMap kbInfo = getKeyboardInfo(selectedIndex, 0);
- String pkgID = kbInfo.get(KMManager.KMKey_PackageID);
- String kbID = kbInfo.get(KMManager.KMKey_KeyboardID);
- String langID = kbInfo.get(KMManager.KMKey_LanguageID);
- String kFont = kbInfo.get(KMManager.KMKey_Font);
- String kOskFont = kbInfo.get(KMManager.KMKey_OskFont);
- KMManager.KeyboardState kbState = KMManager.getKeyboardState(context, pkgID, kbID, langID);
- //if (kbState == KMManager.KeyboardState.KEYBOARD_STATE_NEEDS_DOWNLOAD) {
- AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
- dialogBuilder.setTitle(langName + ": " + kbName);
- dialogBuilder.setMessage("Would you like to download this keyboard?");
- dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Download keyboard
- if (KMManager.hasConnection(context)) {
- KMManager.KMKeyboardDownloader.download(context, position, 0, true);
- } else {
- Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show();
- }
- }
- });
+ final String pkgID = kbInfo.get(KMManager.KMKey_PackageID);
+ final String kbID = kbInfo.get(KMManager.KMKey_KeyboardID);
+ final String langID = kbInfo.get(KMManager.KMKey_LanguageID);
- dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- // Cancel
- }
- });
-
- AlertDialog dialog = dialogBuilder.create();
- dialog.show();
- /*}
- else {
- KeyboardPickerActivity.addKeyboard(context, kbInfo);
- if (KMManager.InAppKeyboard != null)
- KMManager.InAppKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont);
- if (KMManager.SystemKeyboard != null)
- KMManager.SystemKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont);
- finish();
- }*/
+ Bundle bundle = new Bundle();
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName);
+ bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName);
+ bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false);
+ Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class);
+ i.putExtras(bundle);
+ startActivity(i);
}
}
});
diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java
new file mode 100644
index 0000000000..a2769cbc3f
--- /dev/null
+++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java
@@ -0,0 +1,113 @@
+package com.tavultesoft.kmea.util;
+
+import android.content.Context;
+import android.util.Log;
+
+import com.tavultesoft.kmea.KMManager;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+
+public final class FileDownloader {
+
+ /**
+ * Utility to download a file from urlStr and store it at directory/filename.
+ * If the directory does not exist, it will be created.
+ * @param context
+ * @param urlStr Source
+ * @param directory Destination path.
+ * @param filename Destination filename. If blank, it will use the filename from the URL
+ * @return
+ */
+ public static int download(Context context, String urlStr, String directory, String filename) {
+ final int BUFFER_SIZE = 4096;
+ int ret = -1;
+ String fileName = "";
+ String tmpFileName = "";
+ File tmpFile = null;
+ File file = null;
+
+ try {
+ if (directory == null) {
+ directory = "";
+ }
+ directory = directory.trim();
+
+ String dirPath;
+ if (directory.length() != 0) {
+ directory = directory + "/";
+ dirPath = context.getDir("data", Context.MODE_PRIVATE) + "/" + directory;
+ } else {
+ dirPath = context.getDir("data", Context.MODE_PRIVATE).toString();
+ }
+ File dir = new File(dirPath);
+ if (!dir.exists()) {
+ dir.mkdir();
+ }
+
+ if (Connection.initialize(urlStr)) {
+ InputStream binStream = new BufferedInputStream(Connection.getInputStream(), BUFFER_SIZE);
+ byte[] buff = new byte[BUFFER_SIZE];
+
+ filename = filename.trim();
+ if (filename == null || filename.isEmpty()) {
+ fileName = Connection.getFile().substring(Connection.getFile().lastIndexOf('/') + 1);
+ if (fileName.lastIndexOf(".js") > 0 && !fileName.contains("-")) {
+ fileName = fileName.substring(0, filename.lastIndexOf(".js")) + "-1.0.js";
+ }
+ } else {
+ fileName = filename;
+ }
+ tmpFileName = String.format("%s.tmp", fileName);
+ file = new File(dirPath, fileName);
+ tmpFile = new File(dirPath, tmpFileName);
+ FileOutputStream fos = new FileOutputStream(tmpFile);
+
+ int len;
+ while ((len = binStream.read(buff)) != -1) {
+ fos.write(buff, 0, len);
+ }
+
+ fos.flush();
+ fos.close();
+ binStream.close();
+
+ ret = 1;
+ }
+ } catch (Exception e) {
+ ret = -1;
+ Log.e("FileDownloader", "Download failed! Error: " + e);
+ } finally {
+ if (ret > 0) {
+ if (tmpFile.exists() && tmpFile.length() > 0) {
+ if (file.exists()) {
+ file.delete();
+ }
+ if (!tmpFile.renameTo(file)) {
+ ret = -1;
+ } else if (KMManager.isDebugMode()) {
+ Log.d("FileDownloader", "Download finished for filename " + file.toString());
+ }
+ } else {
+ ret = -1;
+ }
+ } else {
+ if (file.exists()) {
+ file.delete();
+ }
+ if (tmpFile.exists()) {
+ tmpFile.delete();
+ }
+ if (KMManager.isDebugMode()) {
+ Log.d("FileDownloader", "Could not download filename " + file.toString());
+ }
+ }
+
+ Connection.disconnect();
+ }
+
+ return ret;
+ }
+}
diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java
index e7d89850f5..ed77cc9ac2 100644
--- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java
+++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java
@@ -7,6 +7,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMTextView;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
@@ -38,6 +39,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
kbInfo.put(KMManager.KMKey_LanguageName, "Tamil");
kbInfo.put(KMManager.KMKey_KeyboardVersion, "1.1");
kbInfo.put(KMManager.KMKey_Font, "aava1.ttf");
+ //kbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); // Use the default font
KMManager.addKeyboard(this, kbInfo);
}
@@ -68,7 +70,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
super.onResume();
KMManager.onResume();
KMManager.addKeyboardEventListener(this);
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
}
@Override
@@ -76,7 +78,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
super.onPause();
KMManager.onPause();
KMManager.removeKeyboardEventListener(this);
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
}
@Override
diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
index 9e38d1f01b..2f245c6750 100644
--- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
+++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java
@@ -45,6 +45,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
kbInfo.put(KMManager.KMKey_LanguageName, "Tamil");
kbInfo.put(KMManager.KMKey_KeyboardVersion, "1.1");
kbInfo.put(KMManager.KMKey_Font, "aava1.ttf");
+ //kbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); // Use the default font
KMManager.addKeyboard(this, kbInfo);
}
diff --git a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java
index f79e7835b4..bb0da07e7b 100644
--- a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java
+++ b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java
@@ -7,6 +7,7 @@ import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
+import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMTextView;
import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener;
@@ -82,7 +83,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
super.onResume();
KMManager.onResume();
KMManager.addKeyboardEventListener(this);
- KMManager.addKeyboardDownloadEventListener(this);
+ KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this);
}
@Override
@@ -90,7 +91,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O
super.onPause();
KMManager.onPause();
KMManager.removeKeyboardEventListener(this);
- KMManager.removeKeyboardDownloadEventListener(this);
+
+ // Intentionally not removing KeyboardDownloadEventListener to
+ // ensure onKeyboardDownloadFinished() gets called
}
@Override
diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
index 9bccbdf568..1f77fa2f1a 100644
--- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
+++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj
@@ -44,6 +44,7 @@
C040E5101F8606E300901EE4 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E50F1F8606E300901EE4 /* TextField.swift */; };
C040E5121F86107E00901EE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5111F86107E00901EE4 /* AppDelegate.swift */; };
C040E5141F86108900901EE4 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5131F86108900901EE4 /* MainViewController.swift */; };
+ C042ED5D1FC6A65A001D82F4 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = C042ED5C1FC6A65A001D82F4 /* Version.swift */; };
C04514881F85D7F500D88416 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04514871F85D7F500D88416 /* KeyboardViewController.swift */; };
C045148A1F85DF9100D88416 /* InputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04514891F85DF9000D88416 /* InputViewController.swift */; };
C0452BAB1F9F1FE10064431A /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0452BAA1F9F1FE10064431A /* Language.swift */; };
@@ -87,6 +88,8 @@
C07A9D8C1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8B1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift */; };
C07A9D8E1FD1798900828ADD /* APIKeyboardRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8D1FD1798900828ADD /* APIKeyboardRepository.swift */; };
C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = C082CE141F90AFD400860F02 /* Collection+SafeAccess.swift */; };
+ C08E698D1FDA392D0026056B /* Migrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E698C1FDA392D0026056B /* Migrations.swift */; };
+ C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */; };
C0959CD41F99C44E00B616BC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0959CD31F99C44E00B616BC /* Constants.swift */; };
C0A93A541F8B21240079948B /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A93A531F8B21240079948B /* Manager.swift */; };
C0B09EAE1FCFD10F002F39AF /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B09EAD1FCFD10F002F39AF /* FontManager.swift */; };
@@ -196,6 +199,7 @@
C040E50F1F8606E300901EE4 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; };
C040E5111F86107E00901EE4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
C040E5131F86108900901EE4 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
+ C042ED5C1FC6A65A001D82F4 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; };
C04514861F85D7F500D88416 /* SystemKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SystemKeyboard-Bridging-Header.h"; sourceTree = ""; };
C04514871F85D7F500D88416 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = ""; };
C04514891F85DF9000D88416 /* InputViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputViewController.swift; sourceTree = ""; };
@@ -222,6 +226,8 @@
C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNameTableViewCell.swift; sourceTree = ""; };
C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInfoViewController.swift; sourceTree = ""; };
C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardPickerViewController.swift; sourceTree = ""; };
+ C08E698C1FDA392D0026056B /* Migrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migrations.swift; sourceTree = ""; };
+ C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullKeyboardID.swift; sourceTree = ""; };
C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageDetailViewController.swift; sourceTree = ""; };
C0959CD31F99C44E00B616BC /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; };
C0A5FF361F6682EB00BE740C /* PopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = ""; };
@@ -442,6 +448,8 @@
C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */,
C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */,
C05F432C1FBD5A4C0058CBD4 /* KeyboardAPICall.swift */,
+ C042ED5C1FC6A65A001D82F4 /* Version.swift */,
+ C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */,
);
path = Model;
sourceTree = "";
@@ -588,6 +596,7 @@
C0E30C8B1FC40D0400C80416 /* Storage.swift */,
C0EF3E7A1F95B65300CE9BD4 /* KeymanWebDelegate.swift */,
C05B14321FD914870082A316 /* Log.swift */,
+ C08E698C1FDA392D0026056B /* Migrations.swift */,
);
path = Classes;
sourceTree = "";
@@ -833,9 +842,11 @@
C06D37341F81F5C300F61AE0 /* HTTPDownloader.swift in Sources */,
C0452BAF1F9F22A80064431A /* Font.swift in Sources */,
C0D3F3601F9F3AD80055C7CF /* InstallableKeyboard.swift in Sources */,
+ C042ED5D1FC6A65A001D82F4 /* Version.swift in Sources */,
C0D3F35E1F9F33490055C7CF /* Options.swift in Sources */,
C06D37351F81F5C300F61AE0 /* HTTPDownloadRequest.swift in Sources */,
C0324B8F1F8750B700AF3785 /* TextView.swift in Sources */,
+ C08E698D1FDA392D0026056B /* Migrations.swift in Sources */,
C07A9D8C1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift in Sources */,
C024C9961FA6EC650060583B /* NotificationCenter+Typed.swift in Sources */,
C06D37361F81F5C300F61AE0 /* KeyboardMenuView.swift in Sources */,
@@ -862,6 +873,7 @@
C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */,
C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */,
C07A9D8A1FD1762700828ADD /* KeyboardRepository.swift in Sources */,
+ C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */,
C040E5101F8606E300901EE4 /* TextField.swift in Sources */,
C0324B8D1F87480700AF3785 /* TextFieldDelegateProxy.swift in Sources */,
C075EB061F8EFF870041F4BD /* String+Helpers.swift in Sources */,
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift
index 6445343ff4..ff25d5357b 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift
@@ -19,6 +19,7 @@ public enum Key {
static let engineVersion = "KeymanEngineVersion"
static let keyboardPickerDisplayed = "KeyboardPickerDisplayed"
static let synchronizeSWKeyboard = "KeymanSynchronizeSWKeyboard"
+ static let migrationLevel = "KeymanEngineMigrationLevel"
// JSON keys for language REST calls
static let options = "options"
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift
index 21682a9de2..7f50b4df9d 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift
@@ -17,7 +17,7 @@ public extension UserDefaults {
do {
return try array.map { try decoder.decode(InstallableKeyboard.self, from: $0) }
} catch {
- log.error("UserDefaults: Error decoding keyboards: \(error)")
+ log.error("Error decoding keyboards: \(error)")
return nil
}
}
@@ -32,32 +32,32 @@ public extension UserDefaults {
let array = try keyboards.map { try encoder.encode($0) }
set(array, forKey: key)
} catch {
- log.error("UserDefaults: Error encoding keyboards: \(error)")
+ log.error("Error encoding keyboards: \(error)")
}
}
- public func installableKeyboard(forKey key: String) -> InstallableKeyboard? {
+ public func fullKeyboardID(forKey key: String) -> FullKeyboardID? {
guard let data = data(forKey: key) else {
return nil
}
do {
- return try PropertyListDecoder().decode(InstallableKeyboard.self, from: data)
+ return try PropertyListDecoder().decode(FullKeyboardID.self, from: data)
} catch {
- log.error("UserDefaults: Error decoding keyboard: \(error)")
+ log.error("Error decoding FullKeyboardID: \(error)")
return nil
}
}
- public func set(_ keyboard: InstallableKeyboard?, forKey key: String) {
- guard let keyboard = keyboard else {
+ public func set(_ fullKeyboardID: FullKeyboardID?, forKey key: String) {
+ guard let id = fullKeyboardID else {
removeObject(forKey: key)
return
}
do {
- let data = try PropertyListEncoder().encode(keyboard)
+ let data = try PropertyListEncoder().encode(id)
set(data, forKey: key)
} catch {
- log.error("UserDefaults: Error encoding keyboard: \(error)")
+ log.error("Error encoding FullKeyboardID: \(error)")
}
}
@@ -71,17 +71,27 @@ public extension UserDefaults {
}
}
- public var currentKeyboard: InstallableKeyboard? {
+ public var currentKeyboardID: FullKeyboardID? {
get {
- return installableKeyboard(forKey: Key.userCurrentKeyboard)
+ return fullKeyboardID(forKey: Key.userCurrentKeyboard)
}
- set(keyboard) {
- set(keyboard, forKey: Key.userCurrentKeyboard)
+ set(fullKeyboardID) {
+ set(fullKeyboardID, forKey: Key.userCurrentKeyboard)
}
}
- public func userKeyboard(withID keyboardID: String, languageID: String) -> InstallableKeyboard? {
- return userKeyboards?.first { $0.id == keyboardID && $0.languageID == languageID }
+ public func userKeyboard(withFullID fullID: FullKeyboardID) -> InstallableKeyboard? {
+ return userKeyboards?.first { $0.fullID == fullID }
+ }
+
+ var migrationLevel: Int {
+ get {
+ return integer(forKey: Key.migrationLevel)
+ }
+
+ set(level) {
+ set(level, forKey: Key.migrationLevel)
+ }
}
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift
index 399bbb7b82..ceaca51a23 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift
@@ -28,38 +28,24 @@ public class FontManager {
/// Registers all new fonts found in the font path. Call this after you have preloaded all your font files
/// with `preloadFontFile(atPath:shouldOverwrite:)`
public func registerCustomFonts() {
- guard let customFonts = customFonts() else {
+ guard let keyboardDirs = Storage.active.keyboardDirs else {
return
}
-
- for fontURL in customFonts {
- _ = registerFont(at: fontURL)
+ for dir in keyboardDirs {
+ registerFonts(in: dir)
}
}
/// Unregisters all registered fonts in the font path.
public func unregisterCustomFonts() {
- guard let customFonts = customFonts() else {
+ guard let keyboardDirs = Storage.active.keyboardDirs else {
return
}
-
- for fontURL in customFonts {
- _ = unregisterFont(at: fontURL)
+ for dir in keyboardDirs {
+ unregisterFonts(in: dir)
}
}
- private func customFonts() -> [URL]? {
- let urls: [URL]
- do {
- urls = try FileManager.default.contentsOfDirectory(at: Storage.active.fontDir,
- includingPropertiesForKeys: nil)
- } catch {
- log.error("Failed to list font dir contents: \(error)")
- return nil
- }
- return urls.filter { $0.lastPathComponent.hasFontExtension }
- }
-
private func readFontName(at url: URL) -> String? {
guard let provider = CGDataProvider(url: url as CFURL) else {
log.error("Failed to open \(url)")
@@ -139,6 +125,26 @@ public class FontManager {
return font.isRegistered
}
+ public func registerFonts(in directory: URL) {
+ guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
+ log.error("Could not list contents of directory \(directory)")
+ return
+ }
+ for url in urls where url.lastPathComponent.hasFontExtension {
+ _ = registerFont(at: url)
+ }
+ }
+
+ public func unregisterFonts(in directory: URL, fromSystemOnly: Bool = true) {
+ guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else {
+ log.error("Could not list contents of directory \(directory)")
+ return
+ }
+ for url in urls where url.lastPathComponent.hasFontExtension {
+ _ = unregisterFont(at: url, fromSystemOnly: fromSystemOnly)
+ }
+ }
+
private func fontExists(_ fontName: String) -> Bool {
return UIFont.familyNames.contains { familyName in
UIFont.fontNames(forFamilyName: familyName).contains(fontName)
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift
index caa0def4f0..8e51c0f74a 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift
@@ -49,11 +49,7 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe
_tableList = keyboardList
_tableList!.append(titleCloseButton)
} else {
- var keyboard = Defaults.keyboard
- if let version = Manager.shared.latestKeyboardFileVersion(withID: Defaults.keyboard.id) {
- keyboard.version = version
- }
- _tableList = [keyboard]
+ _tableList = [Defaults.keyboard]
_tableList!.append(titleCloseButton)
}
return _tableList!
@@ -284,7 +280,7 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe
let keyboard = tableList[indexPath.row] as! InstallableKeyboard
cell.textLabel?.text = keyboard.name
cell.tag = indexPath.row
- if (Manager.shared.languageID == keyboard.languageID) && (Manager.shared.keyboardID == keyboard.id) {
+ if Manager.shared.currentKeyboardID == keyboard.fullID {
cell.selectionStyle = .none
cell.isSelected = true
cell.accessoryType = .checkmark
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift
index ab65eed202..13e24a2101 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift
@@ -26,7 +26,7 @@ public extension KeyboardRepository {
guard let keyboard = language.keyboards?.first(where: { $0.id == keyboardID }) else {
return nil
}
- return InstallableKeyboard(keyboard: keyboard, language: language)
+ return InstallableKeyboard(keyboard: keyboard, language: language, isCustom: false)
}
public func fetch(completionHandler: CompletionHandler? = nil) {
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift
index f363c443d2..5ebf8d786c 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift
@@ -11,6 +11,7 @@ import WebKit
// MARK: - UIViewController
class KeymanWebViewController: UIViewController {
+ let storage: Storage
weak var delegate: KeymanWebDelegate?
var webView: WKWebView!
@@ -22,6 +23,15 @@ class KeymanWebViewController: UIViewController {
}
}
+ init(storage: Storage) {
+ self.storage = storage
+ super.init(nibName: nil, bundle: nil)
+ }
+
+ required init?(coder aDecoder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
override func loadView() {
let config = WKWebViewConfiguration()
let prefs = WKPreferences()
@@ -62,10 +72,7 @@ extension KeymanWebViewController {
let y = CGFloat(Float(components[1])!)
let w = CGFloat(Float(components[2])!)
let h = CGFloat(Float(components[3])!)
- let isPad = UIDevice.current.userInterfaceIdiom == .pad
- let adjY: CGFloat = isPad ? -0.5 : -1.0
- let frame = CGRect(x: x - w / 2.0, y: y - adjY, width: w, height: h)
- completion(frame)
+ completion(KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h))
}
}
@@ -121,26 +128,20 @@ extension KeymanWebViewController {
// family does not have to match the name in the font file. It only has to be unique.
return [
"family": "\(keyboardID)__\(isOsk ? "osk" : "display")",
- "files": font.source
+ "files": font.source.map { storage.fontURL(forKeyboardID: keyboardID, filename: $0).absoluteString }
]
}
- func setKeyboard(id: String,
- name: String,
- languageID: String,
- languageName: String,
- fileURL: URL,
- font: Font?,
- oskFont: Font?) {
+ func setKeyboard(_ keyboard: InstallableKeyboard) {
var stub: [String: Any] = [
- "KI": "Keyboard_\(id)",
- "KN": name,
- "KLC": languageID,
- "KL": languageName,
- "KF": fileURL.absoluteString
+ "KI": "Keyboard_\(keyboard.id)",
+ "KN": keyboard.name,
+ "KLC": keyboard.languageID,
+ "KL": keyboard.languageName,
+ "KF": storage.keyboardURL(for: keyboard).absoluteString
]
- let displayFont = fontObject(from: font, keyboardID: id, isOsk: false)
- let oskFont = fontObject(from: oskFont, keyboardID: id, isOsk: true) ?? displayFont
+ let displayFont = fontObject(from: keyboard.font, keyboardID: keyboard.id, isOsk: false)
+ let oskFont = fontObject(from: keyboard.oskFont, keyboardID: keyboard.id, isOsk: true) ?? displayFont
if let displayFont = displayFont {
stub["KFont"] = displayFont
}
@@ -160,6 +161,7 @@ extension KeymanWebViewController {
return
}
+ log.debug("Keyboard stub: \(stubString)")
webView.evaluateJavaScript("setKeymanLanguage(\(stubString));", completionHandler: nil)
}
}
@@ -198,7 +200,7 @@ extension KeymanWebViewController: WKScriptMessageHandler {
let h = CGFloat(Float(fragment[hKey.upperBound.. CGRect {
- let isPad = UIDevice.current.userInterfaceIdiom == .pad
- let adjY: CGFloat = isPad ? -0.5 : -1.0
- return CGRect(x: x - w / 2.0, y: y - adjY, width: w, height: h)
+ private static func keyFrame(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) -> CGRect {
+ // kmw adds w/2 to x.
+ return CGRect(x: x - w / 2.0, y: y, width: w, height: h)
}
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift
index 3ff1f82b3b..8e0967c5d3 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift
@@ -164,7 +164,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
cell.detailTextLabel?.text = kb.name
cell.tag = indexPath.row
- if isCurrentKeyboard(languageID: kb.languageID, keyboardID: kb.id) {
+ if Manager.shared.currentKeyboardID == kb.fullID {
cell.selectionStyle = .blue
cell.isSelected = true
cell.accessoryType = .detailDisclosureButton
@@ -273,16 +273,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
}
private func loadUserKeyboards() {
- let userData = Storage.active.userDefaults
-
- if let userKeyboards = userData.userKeyboards {
- self.userKeyboards = userKeyboards
- } else {
- userKeyboards = [Defaults.keyboard]
- userData.userKeyboards = userKeyboards
- userData.synchronize()
- }
-
+ userKeyboards = Storage.active.userDefaults.userKeyboards ?? []
tableView.reloadData()
}
@@ -365,7 +356,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
private func scroll(toSelectedKeyboard animated: Bool) {
let index = userKeyboards.index { kb in
- return isCurrentKeyboard(languageID: kb.languageID, keyboardID: kb.id)
+ return Manager.shared.currentKeyboardID == kb.fullID
}
if let index = index {
@@ -388,11 +379,6 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
}
}
- private func isCurrentKeyboard(languageID: String?, keyboardID: String?) -> Bool {
- return Manager.shared.keyboardID == keyboardID &&
- Manager.shared.languageID == languageID
- }
-
@objc func hideToolbarDelayed(_ timer: Timer) {
navigationController?.setToolbarHidden(true, animated: true)
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
index be001782e5..73fd483250 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift
@@ -91,8 +91,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
/// Set this to `UIApplication.shared.openURL` in your application.
public var openURL: ((URL) -> Bool)?
- var keyboardID: String?
- var languageID: String?
+ var currentKeyboardID: FullKeyboardID?
weak var keymanWebDelegate: KeymanWebDelegate?
var currentRequest: HTTPDownloadRequest?
var shouldReloadKeyboard = false
@@ -139,24 +138,12 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
URLProtocol.registerClass(KeymanURLProtocol.self)
- if !Util.isSystemKeyboard {
- if let shared = Storage.shared,
- let nonShared = Storage.nonShared {
- let keysToCopy = [Key.userKeyboardsList, Key.userCurrentKeyboard,
- Key.engineVersion, Key.keyboardPickerDisplayed]
- nonShared.copyUserDefaults(to: shared, withKeys: keysToCopy, shouldOverwrite: false)
- do {
- try nonShared.copyFiles(to: shared)
- } catch {
- log.error("Failed to copy files to shared container: \(error)")
- }
- }
- let userData = Storage.active.userDefaults
- let isKPDisplayed = userData.bool(forKey: Key.keyboardPickerDisplayed)
- if isKPDisplayed {
- isKeymanHelpOn = false
- }
- } else {
+ Migrations.migrate(storage: Storage.active)
+ if Storage.active.userDefaults.userKeyboards?.isEmpty ?? true {
+ Storage.active.userDefaults.userKeyboards = [Defaults.keyboard]
+ }
+
+ if Util.isSystemKeyboard || Storage.active.userDefaults.bool(forKey: Key.keyboardPickerDisplayed) {
isKeymanHelpOn = false
}
@@ -173,7 +160,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
updateUserKeyboards(with: Defaults.keyboard)
- keymanWeb = KeymanWebViewController(nibName: nil, bundle: nil)
+ keymanWeb = KeymanWebViewController(storage: Storage.active)
keymanWeb.frame = CGRect(origin: .zero, size: keyboardSize)
keymanWeb.delegate = self
reloadKeyboard(in: keymanWeb)
@@ -206,8 +193,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
/// - SeeAlso:
/// - addKeyboard()
/// - Returns: Whether the keyboard was set successfully
- public func setKeyboard(withID keyboardID: String, languageID: String) -> Bool {
- if let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) {
+ public func setKeyboard(withFullID fullID: FullKeyboardID) -> Bool {
+ if let keyboard = Storage.active.userDefaults.userKeyboard(withFullID: fullID) {
return setKeyboard(keyboard)
}
return false
@@ -217,29 +204,26 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
///
/// - Returns: Whether the keyboard was set successfully
public func setKeyboard(_ kb: InstallableKeyboard) -> Bool {
- if kb.languageID == self.languageID && kb.id == self.keyboardID {
- log.info("Keyboard unchanged: \(kb.languageID)_\(kb.id)")
+ if kb.fullID == currentKeyboardID {
+ log.info("Keyboard unchanged: \(kb.fullID)")
return false
}
- log.info("Setting language: \(kb.languageID)_\(kb.id)")
+ log.info("Setting language: \(kb.fullID)")
- self.languageID = kb.languageID
- self.keyboardID = kb.id
+ currentKeyboardID = kb.fullID
if let fontFilename = kb.font?.source.first(where: { $0.hasFontExtension }) {
- _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forFilename: fontFilename))
+ _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forKeyboardID: kb.id, filename: fontFilename))
}
if let oskFontFilename = kb.oskFont?.source.first(where: { $0.hasFontExtension }) {
- _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forFilename: oskFontFilename))
+ _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forKeyboardID: kb.id, filename: oskFontFilename))
}
- keymanWeb.setKeyboard(id: kb.id, name: kb.name, languageID: kb.languageID, languageName: kb.languageName,
- fileURL: Storage.active.keyboardURL(for: kb), font: kb.font, oskFont: kb.oskFont)
+ keymanWeb.setKeyboard(kb)
let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
-
- userData.currentKeyboard = kb
+ userData.currentKeyboardID = kb.fullID
userData.synchronize()
if isKeymanHelpOn {
@@ -269,7 +253,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
var userKeyboards = userDefaults.userKeyboards ?? []
// Update keyboard if it exists
- if let index = userKeyboards.index(where: { $0.id == keyboard.id && $0.languageID == keyboard.languageID }) {
+ if let index = userKeyboards.index(where: { $0.fullID == keyboard.fullID }) {
userKeyboards[index] = keyboard
} else {
userKeyboards.append(keyboard)
@@ -282,9 +266,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
/// Removes a keyboard from the list in the keyboard picker if it exists.
/// - Returns: The keyboard exists and was removed
- public func removeKeyboard(withID keyboardID: String, languageID: String) -> Bool {
+ public func removeKeyboard(withFullID fullID: FullKeyboardID) -> Bool {
// Remove keyboard from the list if it exists
- let index = Storage.active.userDefaults.userKeyboards?.index { $0.id == keyboardID && $0.languageID == languageID }
+ let index = Storage.active.userDefaults.userKeyboards?.index { $0.fullID == fullID }
if let index = index {
return removeKeyboard(at: index)
}
@@ -311,28 +295,42 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
userData.set([Date()], forKey: Key.synchronizeSWKeyboard)
userData.synchronize()
+ log.info("Removing keyboard with ID \(kb.id) and languageID \(kb.languageID)")
+
// Set a new keyboard if deleting the current one
- if kb.id == keyboardID && kb.languageID == languageID {
+ if kb.fullID == currentKeyboardID {
setKeyboard(userKeyboards[0])
}
+ if !userKeyboards.contains(where: { $0.id == kb.id }) {
+ let keyboardDir = Storage.active.keyboardDir(forID: kb.id)
+ FontManager.shared.unregisterFonts(in: keyboardDir, fromSystemOnly: false)
+ log.info("Deleting directory \(keyboardDir)")
+ if (try? FileManager.default.removeItem(at: keyboardDir)) == nil {
+ log.error("Failed to delete \(keyboardDir)")
+ }
+ } else {
+ log.info("User has another language installed. Skipping delete of keyboard files.")
+ }
+
NotificationCenter.default.post(name: Notifications.keyboardRemoved, object: self, value: kb)
return true
}
/// - Returns: Info for the current keyboard, if a keyboard is set
- public var currentKeyboardInfo: InstallableKeyboard? {
- guard let keyboardID = keyboardID, let languageID = languageID else {
+ public var currentKeyboard: InstallableKeyboard? {
+ guard let fullID = currentKeyboardID else {
return nil
}
- return Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID)
+ return Storage.active.userDefaults.userKeyboard(withFullID: fullID)
}
/// Switch to the next keyboard.
/// - Returns: Index of the newly selected keyboard.
public func switchToNextKeyboard() -> Int? {
guard let userKeyboards = Storage.active.userDefaults.userKeyboards,
- let index = userKeyboards.index(where: { isCurrentKeyboard($0) }) else {
+ let index = userKeyboards.index(where: { self.currentKeyboardID == $0.fullID })
+ else {
return nil
}
let newIndex = (index + 1) % userKeyboards.count
@@ -340,21 +338,13 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return newIndex
}
- func isCurrentKeyboard(withID keyboardID: String?, languageID: String?) -> Bool {
- return self.keyboardID == keyboardID && self.languageID == languageID
- }
-
- func isCurrentKeyboard(_ keyboard: InstallableKeyboard) -> Bool {
- return keyboard.id == self.keyboardID && keyboard.languageID == self.languageID
- }
-
/// - Returns: The font name for the given keyboard ID and languageID, or returns nil if
/// - The keyboard doesn't have a font
/// - The keyboard info is not available in the user keyboards list
- public func fontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? {
- let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID)
+ public func fontNameForKeyboard(withFullID fullID: FullKeyboardID) -> String? {
+ let kb = Storage.active.userDefaults.userKeyboard(withFullID: fullID)
if let filename = kb?.font?.source.first(where: { $0.hasFontExtension }) {
- let fontURL = Storage.active.fontURL(forFilename: filename)
+ let fontURL = Storage.active.fontURL(forKeyboardID: fullID.keyboardID, filename: filename)
return FontManager.shared.fontName(at: fontURL)
}
return nil
@@ -363,10 +353,10 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
/// - Returns: the OSK font name for the given keyboard ID and languageID, or returns nil if
/// - The keyboard doesn't have an OSK font
/// - The keyboard info is not available in the user keyboards list
- func oskFontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? {
- let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID)
+ func oskFontNameForKeyboard(withFullID fullID: FullKeyboardID) -> String? {
+ let kb = Storage.active.userDefaults.userKeyboard(withFullID: fullID)
if let filename = kb?.oskFont?.source.first(where: { $0.hasFontExtension }) {
- let fontURL = Storage.active.fontURL(forFilename: filename)
+ let fontURL = Storage.active.fontURL(forKeyboardID: fullID.keyboardID, filename: filename)
return FontManager.shared.fontName(at: fontURL)
}
return nil
@@ -431,8 +421,15 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return
}
- let keyboardURL = options.keyboardBaseURL.appendingPathComponent(filename)
+ do {
+ try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboardID),
+ withIntermediateDirectories: true)
+ } catch {
+ log.error("Could not create dir for download: \(error)")
+ return
+ }
+ let keyboardURL = options.keyboardBaseURL.appendingPathComponent(filename)
let fontURLs = Array(Set(keyboardFontURLs(forFont: keyboard.font, options: options) +
keyboardFontURLs(forFont: keyboard.oskFont, options: options)))
@@ -451,7 +448,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
for (i, url) in fontURLs.enumerated() {
request = HTTPDownloadRequest(url: url, userInfo: commonUserData)
- request.destinationFile = Storage.active.fontURL(forFilename: url.lastPathComponent).path
+ request.destinationFile = Storage.active.fontURL(forKeyboardID: keyboardID, filename: url.lastPathComponent).path
request.tag = i + 1
downloadQueue!.addRequest(request)
}
@@ -499,7 +496,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
private func downloadKeyboard(_ keyboardAPI: KeyboardAPICall) {
let keyboard = keyboardAPI.keyboard
let installableKeyboards = keyboard.languages!.map { language in
- InstallableKeyboard(keyboard: keyboard, language: language)
+ InstallableKeyboard(keyboard: keyboard, language: language, isCustom: true)
}
let filename = keyboard.filename
@@ -522,9 +519,17 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return
}
- let isUpdate = latestKeyboardFileVersion(withID: keyboard.id) != nil
+ do {
+ try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboard.id),
+ withIntermediateDirectories: true)
+ } catch {
+ log.error("Could not create dir for download: \(error)")
+ return
+ }
- downloadQueue = HTTPDownloader.init(self)
+ let isUpdate = Storage.active.userDefaults.userKeyboards?.contains { $0.id == keyboard.id } ?? false
+
+ downloadQueue = HTTPDownloader(self)
let commonUserData: [String: Any] = [
Key.keyboardInfo: installableKeyboards,
Key.update: isUpdate
@@ -538,7 +543,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
downloadQueue!.addRequest(request)
for (i, url) in fontURLs.enumerated() {
request = HTTPDownloadRequest(url: url, userInfo: commonUserData)
- request.destinationFile = Storage.active.fontURL(forFilename: url.lastPathComponent).path
+ request.destinationFile = Storage.active.fontURL(forKeyboardID: keyboard.id, filename: url.lastPathComponent).path
request.tag = i + 1
downloadQueue!.addRequest(request)
}
@@ -550,14 +555,18 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
if keyboardIdForCurrentRequest() == keyboardID {
return .downloading
}
- guard let latestDownloadedVersion = latestKeyboardFileVersion(withID: keyboardID) else {
+ let userKeyboards = Storage.active.userDefaults.userKeyboards
+ guard let userKeyboard = userKeyboards?.first(where: { $0.id == keyboardID }) else {
return .needsDownload
}
// Check version
- if let latestRepositoryVersion = apiKeyboardRepository.keyboards?[keyboardID]?.version,
- compareVersions(latestDownloadedVersion, latestRepositoryVersion) == .orderedAscending {
- return .needsUpdate
+ if let repositoryVersionString = apiKeyboardRepository.keyboards?[keyboardID]?.version {
+ let downloadedVersion = Version(userKeyboard.version) ?? Version.fallback
+ let repositoryVersion = Version(repositoryVersionString) ?? Version.fallback
+ if downloadedVersion < repositoryVersion {
+ return .needsUpdate
+ }
}
return .upToDate
}
@@ -594,16 +603,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
// MARK: - HTTPDownloadDelegate methods
- func downloadQueueFinished(_ queue: HTTPDownloader) {
- log.debug {
- let fontContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.fontDir.path)
- return "Font Directory contents: \(fontContents ?? [])"
- }
- log.debug {
- let langContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.languageDir.path)
- return "Language Directory contents: \(langContents ?? [])"
- }
- }
+ func downloadQueueFinished(_ queue: HTTPDownloader) { }
func downloadRequestStarted(_ request: HTTPDownloadRequest) {
// If we're downloading a new keyboard.
@@ -688,79 +688,18 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
// MARK: - Loading custom keyboards
-
- /// Preloads a .js file for a language so that the keyboard is available without downloading.
- /// - Precondition:
- /// - The .js filename must remain the same as when obtained from Keyman.
- /// - The .js file must be bundled in your application.
- public func preloadKeyboardFile(at url: URL, shouldOverwrite: Bool) throws {
- try Storage.copyAndExcludeFromBackup(at: url,
- to: Storage.active.languageDir.appendingPathComponent(url.lastPathComponent),
- shouldOverwrite: shouldOverwrite)
- }
-
- /// Preloads a .ttf or .otf file to be available without downloading.
- /// - Precondition:
- /// - The font file must be bundled in your application.
- /// - SeeAlso: `registerCustomFonts()`
- public func preloadFontFile(at url: URL, shouldOverwrite: Bool) throws {
- try Storage.copyAndExcludeFromBackup(at: url,
- to: Storage.active.fontDir.appendingPathComponent(url.lastPathComponent),
- shouldOverwrite: shouldOverwrite)
+ /// Preloads the JS and font files required for a keyboard.
+ public func preloadFiles(forKeyboardID keyboardID: String, at urls: [URL], shouldOverwrite: Bool) throws {
+ let keyboardDir = Storage.active.keyboardDir(forID: keyboardID)
+ try FileManager.default.createDirectory(at: keyboardDir, withIntermediateDirectories: true)
+ for url in urls {
+ try Storage.copyAndExcludeFromBackup(at: url,
+ to: keyboardDir.appendingPathComponent(url.lastPathComponent),
+ shouldOverwrite: shouldOverwrite)
+ }
}
// MARK: - File system and UserData management
- func latestKeyboardFileVersion(withID keyboardID: String) -> String? {
- guard let dirContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.languageDir.path) else {
- return nil
- }
-
- var latestVersion: String?
- for filename in dirContents where filename.hasPrefix("\(keyboardID)-") && filename.hasJavaScriptExtension {
- let dashRange = filename.range(of: "-", options: .backwards)!
- let extensionRange = filename.range(of: ".js", options: .backwards)!
- let version = String(filename[dashRange.upperBound.. ComparisonResult? {
- if v1.isEmpty || v2.isEmpty {
- return nil
- }
- let components1 = v1.components(separatedBy: ".")
- let components2 = v2.components(separatedBy: ".")
-
- let len = max(components1.count, components2.count)
- for i in 0..= 0 else {
- return nil
- }
- guard let val2 = Int(component2), val2 >= 0 else {
- return nil
- }
- if val1 < val2 {
- return .orderedAscending
- }
- if val1 > val2 {
- return .orderedDescending
- }
- }
- return .orderedSame
- }
/// Updates the user's installed keyboards and current keyboard with information in newKeyboard.
/// - Parameter newKeyboard: Info for updated keyboard.
@@ -777,27 +716,13 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
if kb.languageID == newKeyboard.languageID {
kb = newKeyboard
} else {
- kb.version = newKeyboard.id
+ kb.version = newKeyboard.version
}
userKeyboards[i] = kb
}
}
userData.userKeyboards = userKeyboards
userData.synchronize()
-
- // Set version for current keyboard
- let currentUserData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
- if var kb = currentUserData.currentKeyboard {
- if kb.id == newKeyboard.id {
- if kb.languageID == newKeyboard.languageID {
- kb = newKeyboard
- } else {
- kb.version = newKeyboard.id
- }
- currentUserData.currentKeyboard = kb
- currentUserData.synchronize()
- }
- }
}
func synchronizeSWKeyboard() {
@@ -964,9 +889,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
@objc func resetKeyboard() {
- let keyboard = currentKeyboardInfo
- keyboardID = nil
- languageID = nil
+ let keyboard = currentKeyboard
+ currentKeyboardID = nil
if let keyboard = keyboard {
setKeyboard(keyboard)
@@ -984,12 +908,12 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
keymanWeb.languageMenuPosition { keyFrame in
- self.showHelpBubble(at: keyFrame.origin)
+ self.showHelpBubble(for: keyFrame)
}
}
// TODO: The bulk of this should be moved to PopoverView
- func showHelpBubble(at point: CGPoint) {
+ func showHelpBubble(for keyFrame: CGRect) {
self.helpBubbleView?.removeFromSuperview()
let helpBubbleView = PopoverView(frame: CGRect.zero)
self.helpBubbleView = helpBubbleView
@@ -1001,48 +925,24 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
let isPad = UIDevice.current.userInterfaceIdiom == .pad
let sizeMultiplier = CGFloat(isPad ? 1.5 : 1.0)
- let frameWidth = 90.0 * sizeMultiplier
- let frameHeight = (40.0 + helpBubbleView.arrowHeight) * sizeMultiplier
+ let popupWidth = 90.0 * sizeMultiplier
+ let popupHeight = 40.0 * sizeMultiplier + helpBubbleView.arrowHeight
let fontSize = 10.0 * sizeMultiplier
let inputViewFrame = keymanWeb.view.frame
let screenWidth = inputViewFrame.size.width
- // TODO: Refactor this out
- let isPortrait: Bool
- if Util.isSystemKeyboard {
- isPortrait = InputViewController.isPortrait
- } else {
- isPortrait = UIDevice.current.orientation.isPortrait
- }
+ let x = CGFloat.maximum(0, CGFloat.minimum(screenWidth - popupWidth, keyFrame.midX - popupWidth / 2))
+ let adjY = CGFloat(3.0) // Tweak the positioning of the popup
+ let y = keyFrame.minY - popupHeight + adjY
- let adjY: CGFloat
- if isPortrait {
- adjY = Util.isSystemKeyboard ? 9.0 : 4.0
- } else {
- adjY = Util.isSystemKeyboard ? 3.0 : 4.0
- }
- let px = point.x
- let py = point.y + adjY + (isPad ? 2.0 : 1.0)
- var x = px - frameWidth / 2
- let y = py - frameHeight
- if x < 0 {
- x = 0
- } else if x + frameWidth > screenWidth {
- x = screenWidth - frameWidth
- }
+ helpBubbleView.frame = CGRect(x: x, y: y, width: popupWidth, height: popupHeight)
+ helpBubbleView.arrowPosX = keyFrame.midX - x
- helpBubbleView.frame = CGRect(x: x, y: y, width: frameWidth, height: frameHeight)
- if x == 0 {
- helpBubbleView.arrowPosX = px
- } else if x == screenWidth - frameWidth {
- helpBubbleView.arrowPosX = (px - x)
- } else {
- helpBubbleView.arrowPosX = frameWidth / 2
- }
-
- let helpText = UILabel(frame: CGRect(x: 5, y: 0,
- width: frameWidth - 10, height: frameHeight - helpBubbleView.arrowHeight))
+ let helpText = UILabel(frame: CGRect(x: 5,
+ y: 0,
+ width: popupWidth - 10,
+ height: popupHeight - helpBubbleView.arrowHeight))
helpText.backgroundColor = UIColor.clear
helpText.font = helpText.font.withSize(fontSize)
helpText.textAlignment = .center
@@ -1134,13 +1034,11 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
keymanWeb.setDeviceType(UIDevice.current.userInterfaceIdiom)
var newKb = Defaults.keyboard
- if (keyboardID == nil || languageID == nil) && !shouldReloadKeyboard {
+ if currentKeyboardID == nil && !shouldReloadKeyboard {
let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
- if let currentKb = userData.currentKeyboard {
- let kbID = currentKb.id
- let langID = currentKb.languageID
- if Storage.active.userDefaults.userKeyboard(withID: kbID, languageID: langID) != nil {
- newKb = currentKb
+ if let id = userData.currentKeyboardID {
+ if let kb = Storage.active.userDefaults.userKeyboard(withFullID: id) {
+ newKb = kb
}
} else if let userKbs = Storage.active.userDefaults.userKeyboards, !userKbs.isEmpty {
newKb = userKbs[0]
@@ -1178,8 +1076,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
keyPreviewView = KeyPreviewView(frame: keyFrame)
keyPreviewView!.setLabelText(preview)
- var oskFontName = oskFontNameForKeyboard(withID: keyboardID!, languageID: languageID!)
- oskFontName = oskFontName ?? fontNameForKeyboard(withID: keyboardID!, languageID: languageID!)
+ var oskFontName = oskFontNameForKeyboard(withFullID: currentKeyboardID!)
+ oskFontName = oskFontName ?? fontNameForKeyboard(withFullID: currentKeyboardID!)
keyPreviewView!.setLabelFont(oskFontName)
keymanWeb.view.addSubview(keyPreviewView!)
}
@@ -1308,9 +1206,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
let isPad = UIDevice.current.userInterfaceIdiom == .pad
let fontSize = isPad ? UIFont.buttonFontSize * 2 : UIFont.buttonFontSize
- var oskFontName = oskFontNameForKeyboard(withID: keyboardID!, languageID: languageID!)
+ var oskFontName = oskFontNameForKeyboard(withFullID: currentKeyboardID!)
if oskFontName == nil {
- oskFontName = fontNameForKeyboard(withID: keyboardID!, languageID: languageID!)
+ oskFontName = fontNameForKeyboard(withFullID: currentKeyboardID!)
}
if subKeyIDs.isEmpty {
@@ -1397,7 +1295,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
if (!didSynchronize || shouldSynchronize) && Storage.shared != nil {
synchronizeSWKeyboard()
- if keyboardID != nil && languageID != nil {
+ if currentKeyboardID != nil {
shouldReloadKeyboard = true
reloadKeyboard(in: keymanWeb)
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift
new file mode 100644
index 0000000000..39de5c6123
--- /dev/null
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift
@@ -0,0 +1,224 @@
+//
+// Migrations.swift
+// KeymanEngine
+//
+// Created by Gabriel Wong on 2017-12-08.
+// Copyright © 2017 SIL International. All rights reserved.
+//
+
+import Foundation
+
+private enum MigrationLevel {
+ static let initial = 0
+ static let migratedUserDefaultsToStructs = 10
+ static let migratedForKMP = 20
+}
+
+enum Migrations {
+ static func migrate(storage: Storage) {
+ if storage.userDefaults.migrationLevel < MigrationLevel.migratedUserDefaultsToStructs {
+ migrateUserDefaultsToStructs(storage: storage)
+ storage.userDefaults.migrationLevel = MigrationLevel.migratedUserDefaultsToStructs
+ } else {
+ log.info("UserDefaults migration to structs already performed. Skipping.")
+ }
+ if storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP {
+ migrateForKMP(storage: storage)
+ storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP
+ } else {
+ log.info("KMP directory migration already performed. Skipping.")
+ }
+ storage.userDefaults.synchronize()
+ }
+
+ static func migrateUserDefaultsToStructs(storage: Storage) {
+ guard let userKeyboardObject = storage.userDefaults.object(forKey: Key.userKeyboardsList),
+ let currentKeyboardObject = storage.userDefaults.object(forKey: Key.userCurrentKeyboard)
+ else {
+ log.info("User keyboard list or current keyboard missing. Skipping migration.")
+ return
+ }
+ guard let oldUserKeyboards = userKeyboardObject as? [[String: String]],
+ let oldCurrentKeyboard = currentKeyboardObject as? [String: String]
+ else {
+ log.error("User keyboard list or current keyboard has an unexpected type")
+ return
+ }
+
+ let userKeyboards = oldUserKeyboards.flatMap { installableKeyboard(from: $0) }
+ let currentKeyboardID = fullKeyboardID(from: oldCurrentKeyboard)
+
+ storage.userDefaults.userKeyboards = userKeyboards
+ if userKeyboards.contains(where: { $0.fullID == currentKeyboardID }) {
+ storage.userDefaults.currentKeyboardID = currentKeyboardID
+ } else {
+ storage.userDefaults.currentKeyboardID = nil
+ }
+ }
+
+ private static func installableKeyboard(from kbDict: [String: String]) -> InstallableKeyboard? {
+ log.debug("Migrating keyboard dictionary: \(kbDict)")
+ guard let id = kbDict["kbId"],
+ let name = kbDict["kbName"],
+ let languageID = kbDict["langId"],
+ let languageName = kbDict["langName"],
+ let version = kbDict["version"]
+ else {
+ log.error("Missing required fields in keyboard dictionary: \(kbDict)")
+ return nil
+ }
+ let rtl = kbDict["rtl"] == "Y"
+ let isCustom = kbDict["CustomKeyboard"] == "Y"
+ let displayFont = font(from: kbDict["font"])
+ let oskFont = font(from: kbDict["oskFont"])
+ let kb = InstallableKeyboard(id: id,
+ name: name,
+ languageID: languageID,
+ languageName: languageName,
+ version: version,
+ isRTL: rtl,
+ font: displayFont,
+ oskFont: oskFont,
+ isCustom: isCustom)
+ log.debug("Migrated keyboard dictionary to keyboard \(kb)")
+ return kb
+ }
+
+ private static func fullKeyboardID(from kbDict: [String: String]) -> FullKeyboardID? {
+ log.debug("Migrating keyboard dictionary to FullKeyboardID: \(kbDict)")
+ guard let keyboardID = kbDict["kbId"],
+ let languageID = kbDict["langId"]
+ else {
+ log.error("Missing required fields in keyboard dictionary for FullKeyboardID: \(kbDict)")
+ return nil
+ }
+ let id = FullKeyboardID(keyboardID: keyboardID, languageID: languageID)
+ log.debug("Migrated keyboard dictionary to \(id)")
+ return id
+ }
+
+ private static func font(from jsonString: String?) -> Font? {
+ guard let jsonString = jsonString else {
+ return nil
+ }
+ guard let data = jsonString.data(using: .utf8) else {
+ log.error("Failed to encode string: \(jsonString)")
+ return nil
+ }
+ guard let fontDict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else {
+ log.error("Error parsing String as JSON: \(jsonString)")
+ return nil
+ }
+ guard let family = fontDict["family"] as? String else {
+ log.error("Missing 'family' String: \(fontDict)")
+ return nil
+ }
+ let files: [String]
+ if let filesString = fontDict["files"] as? String {
+ files = [filesString]
+ } else if let filesArray = fontDict["files"] as? [String] {
+ files = filesArray
+ } else {
+ log.error("Missing 'files': \(fontDict)")
+ return nil
+ }
+ return Font(family: family, source: files)
+ }
+
+ static func migrateForKMP(storage: Storage) {
+ let languageDir = storage.baseDir.appendingPathComponent("languages")
+ let fontDir = storage.baseDir.appendingPathComponent("fonts")
+
+ log.info("Migrating from base directory: \(storage.baseDir)")
+
+ guard var userKeyboards = storage.userDefaults.userKeyboards else {
+ log.info("No user keyboards to migrate")
+ return
+ }
+
+ var urlsForKeyboard: [String: Set] = [:]
+ for i in userKeyboards.indices {
+ let keyboard = userKeyboards[i]
+ guard let version = latestKeyboardFileVersion(withID: keyboard.id, dirPath: languageDir.path) else {
+ log.warning("Could not find JS file for keyboard \(keyboard.id) in \(languageDir.path)")
+ continue
+ }
+ var urls = urlsForKeyboard[keyboard.id] ?? Set()
+ urls.insert(languageDir.appendingPathComponent("\(keyboard.id)-\(version.string).js"))
+
+ let fontFiles = (keyboard.font?.source ?? []) + (keyboard.oskFont?.source ?? [])
+ for file in fontFiles {
+ guard file.hasFontExtension else {
+ log.info("Skipping copy of \(file) for keyboard \(keyboard.id) since it is not a font file.")
+ continue
+ }
+ let url = fontDir.appendingPathComponent(file)
+ guard FileManager.default.fileExists(atPath: url.path) else {
+ log.warning("Font file \(url) for keyboard \(keyboard.id) does not exist")
+ continue
+ }
+ urls.insert(url)
+ }
+ urlsForKeyboard[keyboard.id] = urls
+ userKeyboards[i].version = version.string
+ }
+
+ var successfulKeyboards: [String] = []
+
+ // Copy files
+ for (keyboardID, urls) in urlsForKeyboard {
+ let keyboardDir = storage.keyboardDir(forID: keyboardID)
+ do {
+ try FileManager.default.createDirectory(at: keyboardDir,
+ withIntermediateDirectories: true,
+ attributes: nil)
+ } catch {
+ log.error("Failed to create keyboard directory at \(keyboardDir)")
+ continue
+ }
+
+ var successful = true
+ for srcURL in urls {
+ let dstURL = keyboardDir.appendingPathComponent(srcURL.lastPathComponent)
+ do {
+ try FileManager.default.copyItem(at: srcURL, to: dstURL)
+ } catch {
+ log.error("Failed to copy from \(srcURL) to \(dstURL) for keyboard \(keyboardID)")
+ successful = false
+ }
+ }
+ if successful {
+ successfulKeyboards.append(keyboardID)
+ log.info("Succesfully copied keyboard files for keyboard \(keyboardID)")
+ }
+ }
+
+ // Remove keyboards that were not copied successfully
+ let filteredUserKeyboards = userKeyboards.filter { successfulKeyboards.contains($0.id) }
+ storage.userDefaults.userKeyboards = filteredUserKeyboards
+
+ // TODO: Remove old directory
+ }
+
+ private static func latestKeyboardFileVersion(withID keyboardID: String, dirPath: String) -> Version? {
+ guard let dirContents = try? FileManager.default.contentsOfDirectory(atPath: dirPath) else {
+ return nil
+ }
+
+ var latestVersion: Version?
+ for filename in dirContents where filename.hasPrefix("\(keyboardID)-") && filename.hasJavaScriptExtension {
+ let dashRange = filename.range(of: "-", options: .backwards)!
+ let extensionRange = filename.range(of: ".js", options: .backwards)!
+ guard let version = Version(String(filename[dashRange.upperBound.. Bool {
+ return lhs.keyboardID == rhs.keyboardID && lhs.languageID == rhs.languageID
+ }
+}
+
+// MARK: - CustomStringConvertible
+extension FullKeyboardID: CustomStringConvertible {
+ public var description: String {
+ return ""
+ }
+}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift
index d98681108e..3161136e47 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift
@@ -20,6 +20,10 @@ public struct InstallableKeyboard: Codable {
public var oskFont: Font?
public var isCustom: Bool
+ public var fullID: FullKeyboardID {
+ return FullKeyboardID(keyboardID: id, languageID: languageID)
+ }
+
public init(id: String,
name: String,
languageID: String,
@@ -40,7 +44,7 @@ public struct InstallableKeyboard: Codable {
self.isCustom = isCustom
}
- public init(keyboard: Keyboard, language: Language) {
+ public init(keyboard: Keyboard, language: Language, isCustom: Bool) {
self.id = keyboard.id
self.name = keyboard.name
self.languageID = language.id
@@ -49,6 +53,6 @@ public struct InstallableKeyboard: Codable {
self.isRTL = keyboard.isRTL
self.font = keyboard.font
self.oskFont = keyboard.oskFont
- self.isCustom = false
+ self.isCustom = isCustom
}
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift
new file mode 100644
index 0000000000..c60f6bbf05
--- /dev/null
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift
@@ -0,0 +1,50 @@
+//
+// Version.swift
+// KeymanEngine
+//
+// Created by Gabriel Wong on 2017-11-23.
+// Copyright © 2017 SIL International. All rights reserved.
+//
+
+import Foundation
+
+/// Dotted-decimal version.
+public struct Version: Comparable {
+ public static let fallback = Version("1.0")!
+
+ private let components: [Int]
+ public let string: String
+
+ public init?(_ string: String) {
+ let stringComponents = string.components(separatedBy: ".")
+ var components: [Int] = []
+ for s in stringComponents {
+ guard let i = Int(s), i >= 0 else {
+ return nil
+ }
+ components.append(i)
+ }
+
+ self.string = string
+ self.components = components
+ }
+
+ public static func <(lhs: Version, rhs: Version) -> Bool {
+ let len = max(lhs.components.count, rhs.components.count)
+ for i in 0.. rightComponent {
+ return false
+ }
+ }
+ return false
+ }
+
+ public static func ==(lhs: Version, rhs: Version) -> Bool {
+ return lhs.components == rhs.components
+ }
+}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift b/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift
index e83da84199..209eb30538 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift
@@ -47,8 +47,7 @@ extension Storage {
class Storage {
let baseDir: URL
- let languageDir: URL
- let fontDir: URL
+ let keyboardDir: URL
let userDefaults: UserDefaults
let kmwURL: URL
@@ -57,23 +56,19 @@ class Storage {
private init?(baseURL: URL, userDefaults: UserDefaults) {
guard
let baseDir = Storage.createSubdirectory(baseDir: baseURL, name: "keyman"),
- let languageDir = Storage.createSubdirectory(baseDir: baseDir, name: "languages"),
- let fontDir = Storage.createSubdirectory(baseDir: baseDir, name: "fonts") else {
+ let keyboardDir = Storage.createSubdirectory(baseDir: baseDir, name: "keyboards")
+ else {
return nil
}
self.baseDir = baseDir
- self.languageDir = languageDir
- self.fontDir = fontDir
+ self.keyboardDir = keyboardDir
self.userDefaults = userDefaults
kmwURL = baseDir.appendingPathComponent(Resources.kmwFilename)
specialOSKFontURL = baseDir.appendingPathComponent(Resources.oskFontFilename)
}
- private static func createSubdirectory(baseDir: URL?, name: String) -> URL? {
- guard let baseDir = baseDir else {
- return nil
- }
+ private static func createSubdirectory(baseDir: URL, name: String) -> URL? {
let newDir = baseDir.appendingPathComponent(name)
do {
try FileManager.default.createDirectory(at: newDir,
@@ -81,20 +76,45 @@ class Storage {
attributes: nil)
return newDir
} catch {
+ log.error("Failed to create subdirectory at \(newDir)")
return nil
}
}
+ func keyboardDir(forID keyboardID: String) -> URL {
+ return keyboardDir.appendingPathComponent(keyboardID)
+ }
+
func keyboardURL(for keyboard: InstallableKeyboard) -> URL {
return keyboardURL(forID: keyboard.id, version: keyboard.version)
}
func keyboardURL(forID keyboardID: String, version: String) -> URL {
- return languageDir.appendingPathComponent("\(keyboardID)-\(version).js")
+ return keyboardDir(forID: keyboardID).appendingPathComponent("\(keyboardID)-\(version).js")
}
- func fontURL(forFilename filename: String) -> URL {
- return fontDir.appendingPathComponent(filename)
+ func fontURL(forKeyboardID keyboardID: String, filename: String) -> URL {
+ return keyboardDir(forID: keyboardID).appendingPathComponent(filename)
+ }
+
+ var keyboardDirs: [URL]? {
+ let contents: [URL]
+ do {
+ contents = try FileManager.default.contentsOfDirectory(at: keyboardDir,
+ includingPropertiesForKeys: [.isDirectoryKey])
+ } catch {
+ log.error("Failed to list contents at \(keyboardDir) with error \(error)")
+ return nil
+ }
+ return contents.filter { url in
+ do {
+ let values = try url.resourceValues(forKeys: [.isDirectoryKey])
+ return values.isDirectory ?? false
+ } catch {
+ log.error(error)
+ return false
+ }
+ }
}
}
@@ -103,28 +123,32 @@ extension Storage {
func copyKMWFiles(from bundle: Bundle) throws {
try Storage.copy(from: bundle,
resourceName: Resources.kmwFilename,
- dstDir: Storage.active.baseDir)
+ dstDir: baseDir)
try Storage.copy(from: bundle,
resourceName: "keymanios.js",
- dstDir: Storage.active.baseDir)
- try Storage.copy(from: bundle,
- resourceName: "\(Defaults.keyboard.id)-\(Defaults.keyboard.version).js",
- dstDir: Storage.active.languageDir)
- try Storage.copy(from: bundle,
- resourceName: "DejaVuSans.ttf",
- dstDir: Storage.active.fontDir)
+ dstDir: baseDir)
try Storage.copy(from: bundle,
resourceName: "kmwosk.css",
- dstDir: Storage.active.baseDir)
+ dstDir: baseDir)
try Storage.copy(from: bundle,
- resourceName: Resources.oskFontFilename,
- dstDir: Storage.active.baseDir)
+ resourceName: "keymanweb-osk.ttf",
+ dstDir: baseDir)
+ let defaultKeyboardDir = self.keyboardDir(forID: Defaults.keyboard.id)
+ try FileManager.default.createDirectory(at: defaultKeyboardDir, withIntermediateDirectories: true)
+ try Storage.copy(from: bundle,
+ resourceName: "\(Defaults.keyboard.id)-\(Defaults.keyboard.version).js",
+ dstDir: defaultKeyboardDir)
+ try Storage.copy(from: bundle,
+ resourceName: "DejaVuSans.ttf",
+ dstDir: defaultKeyboardDir)
}
func copyFiles(to dst: Storage) throws {
- try Storage.copyDirectoryContents(at: baseDir, to: dst.baseDir)
- try Storage.copyDirectoryContents(at: languageDir, to: dst.languageDir)
- try Storage.copyDirectoryContents(at: fontDir, to: dst.fontDir)
+ if FileManager.default.fileExists(atPath: dst.baseDir.path) {
+ log.info("Deleting \(dst.baseDir) for copy from \(baseDir) to \(dst.baseDir)")
+ try FileManager.default.removeItem(at: dst.baseDir)
+ }
+ try FileManager.default.copyItem(at: baseDir, to: dst.baseDir)
}
func copyUserDefaults(to dst: Storage, withKeys keys: [String], shouldOverwrite: Bool) {
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift b/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift
index 38b245fea4..0e656d55f4 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift
@@ -144,8 +144,7 @@ public class TextField: UITextField {
return
}
- // TODO: Get font name directly from keyboard object
- let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID)
+ let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID)
let fontSize = font?.pointSize ?? UIFont.systemFontSize
if let fontName = fontName {
font = UIFont(name: fontName, size: fontSize)
@@ -240,10 +239,8 @@ extension TextField: UITextFieldDelegate {
let textWD = baseWritingDirection(for: beginningOfDocument, in: .forward)
let isRTL: Bool
- if let keyboardID = Manager.shared.keyboardID,
- let languageID = Manager.shared.languageID {
- let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID)
- isRTL = keyboard?.isRTL ?? false
+ if let keyboard = Manager.shared.currentKeyboard {
+ isRTL = keyboard.isRTL
} else {
isRTL = false
}
@@ -278,9 +275,8 @@ extension TextField: UITextFieldDelegate {
Manager.shared.keymanWebDelegate = self
let fontName: String?
- if let keyboardID = Manager.shared.keyboardID,
- let languageID = Manager.shared.languageID {
- fontName = Manager.shared.fontNameForKeyboard(withID: keyboardID, languageID: languageID)
+ if let id = Manager.shared.currentKeyboardID {
+ fontName = Manager.shared.fontNameForKeyboard(withFullID: id)
} else {
fontName = nil
}
diff --git a/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift b/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift
index 800ace08d9..df52e8a896 100644
--- a/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift
+++ b/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift
@@ -133,8 +133,7 @@ public class TextView: UITextView {
return
}
- // TODO: Get font name directly from keyboard
- let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID)
+ let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID)
let fontSize = font?.pointSize ?? UIFont.systemFontSize
if let fontName = fontName {
font = UIFont(name: fontName, size: fontSize)
@@ -239,10 +238,8 @@ extension TextView: UITextViewDelegate {
let textWD = baseWritingDirection(for: beginningOfDocument, in: .forward)
let isRTL: Bool
- if let keyboardID = Manager.shared.keyboardID,
- let languageID = Manager.shared.languageID {
- let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID)
- isRTL = keyboard?.isRTL ?? false
+ if let keyboard = Manager.shared.currentKeyboard {
+ isRTL = keyboard.isRTL
} else {
isRTL = false
}
@@ -279,9 +276,8 @@ extension TextView: UITextViewDelegate {
Manager.shared.keymanWebDelegate = self
let fontName: String?
- if let keyboardID = Manager.shared.keyboardID,
- let languageID = Manager.shared.languageID {
- fontName = Manager.shared.fontNameForKeyboard(withID: keyboardID, languageID: languageID)
+ if let id = Manager.shared.currentKeyboardID {
+ fontName = Manager.shared.fontNameForKeyboard(withFullID: id)
} else {
fontName = nil
}
diff --git a/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift b/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift
index a34fa82789..de3d37e88a 100644
--- a/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift
+++ b/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift
@@ -52,7 +52,7 @@ class InfoViewController: UIViewController, UIWebViewDelegate {
}
private func loadFromServer() {
- let keyboardInfo = Manager.shared.currentKeyboardInfo
+ let keyboardInfo = Manager.shared.currentKeyboard
let currentKeyboardId = keyboardInfo?.id ?? Defaults.keyboard.id
let userData = AppDelegate.activeUserDefaults()
let keyboards = userData.userKeyboards
diff --git a/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift b/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift
index 4fdab8df1c..2e14f2799e 100644
--- a/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift
+++ b/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift
@@ -273,7 +273,7 @@ class WebBrowserViewController: UIViewController, UIWebViewDelegate, UIAlertView
}
private func keyboardChanged(_ kb: InstallableKeyboard) {
- if let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID) {
+ if let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID) {
newFontFamily = fontName
} else {
newFontFamily = UIFont.systemFont(ofSize: UIFont.systemFontSize).fontName
diff --git a/ios/keyman/Keyman/Keyman/MainViewController.swift b/ios/keyman/Keyman/Keyman/MainViewController.swift
index 2a0fe05355..185559c2bd 100644
--- a/ios/keyman/Keyman/Keyman/MainViewController.swift
+++ b/ios/keyman/Keyman/Keyman/MainViewController.swift
@@ -470,7 +470,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
didDownload = false
}
- checkProfile(forKeyboardID: kb.id, languageID: kb.languageID, doListCheck: listCheck)
+ checkProfile(forFullID: kb.fullID, doListCheck: listCheck)
}
private func keyboardDownloadStarted() {
@@ -869,8 +869,8 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
}
}
- private func profileName(withKeyboardID kbID: String, languageID langID: String) -> String? {
- guard let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withID: kbID, languageID: langID),
+ private func profileName(withFullID fullID: FullKeyboardID) -> String? {
+ guard let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withFullID: fullID),
let font = keyboard.font else {
return nil
}
@@ -882,15 +882,15 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
return font.source.first { $0.lowercased().hasSuffix(FileExtensions.configurationProfile) }
}
- private func checkProfile(forKeyboardID kbID: String, languageID langID: String, doListCheck: Bool) {
- if kbID == Defaults.keyboard.id && langID == Defaults.keyboard.languageID {
+ private func checkProfile(forFullID fullID: FullKeyboardID, doListCheck: Bool) {
+ if fullID == Defaults.keyboard.fullID {
return
}
if profileName != nil {
return // already installing a profile
}
- guard let profile = profileName(withKeyboardID: kbID, languageID: langID) else {
+ guard let profile = profileName(withFullID: fullID) else {
return
}
@@ -906,7 +906,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
if doInstall {
profileName = profile
- let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withID: kbID, languageID: langID)!
+ let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withFullID: fullID)!
let languageName = keyboard.languageName
let title = "\(languageName) Font"
let msg = "Touch Install to make \(languageName) display correctly in all your apps"
diff --git a/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift b/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift
index a58ad75a11..464d21d91e 100644
--- a/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift
+++ b/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift
@@ -57,7 +57,7 @@ class SetUpViewController: UIViewController, UIWebViewDelegate {
}
private func loadFromServer() {
- let keyboardInfo = Manager.shared.currentKeyboardInfo
+ let keyboardInfo = Manager.shared.currentKeyboard
let currentKeyboardId = keyboardInfo?.id ?? Defaults.keyboard.id
let userData = AppDelegate.activeUserDefaults()
let keyboards = userData.userKeyboards
diff --git a/ios/samples/KMSample1/KMSample1/ViewController.swift b/ios/samples/KMSample1/KMSample1/ViewController.swift
index 7172846fe2..b6b9b64667 100644
--- a/ios/samples/KMSample1/KMSample1/ViewController.swift
+++ b/ios/samples/KMSample1/KMSample1/ViewController.swift
@@ -15,17 +15,7 @@ class ViewController: UIViewController, TextViewDelegate {
super.viewDidLoad()
Manager.shared.openURL = UIApplication.shared.openURL
Manager.shared.isKeymanHelpOn = false
- do {
- try Manager.shared.preloadKeyboardFile(at: Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!,
- shouldOverwrite: true)
- try Manager.shared.preloadFontFile(at: Bundle.main.url(forResource: "aava1", withExtension: "ttf")!,
- shouldOverwrite: true)
- } catch {
- print("Error preloading: \(error)")
- }
- FontManager.shared.registerCustomFonts()
- Manager.shared.addKeyboard(Defaults.keyboard)
let kb = InstallableKeyboard(id: "tamil99m",
name: "Tamil 99M",
languageID: "tam",
@@ -35,6 +25,18 @@ class ViewController: UIViewController, TextViewDelegate {
font: Font(filename: "aava1.ttf"),
oskFont: nil,
isCustom: true)
+ let urls = [
+ Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!,
+ Bundle.main.url(forResource: "aava1", withExtension: "ttf")!
+ ]
+ do {
+ try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: urls, shouldOverwrite: true)
+ } catch {
+ print("Error preloading: \(error)")
+ }
+ FontManager.shared.registerCustomFonts()
+
+ Manager.shared.addKeyboard(Defaults.keyboard)
Manager.shared.addKeyboard(kb)
textView.setKeymanDelegate(self)
diff --git a/ios/samples/KMSample2/KMSample2/ViewController.swift b/ios/samples/KMSample2/KMSample2/ViewController.swift
index c67e75cba9..5cdb44657f 100644
--- a/ios/samples/KMSample2/KMSample2/ViewController.swift
+++ b/ios/samples/KMSample2/KMSample2/ViewController.swift
@@ -16,19 +16,8 @@ class ViewController: UIViewController, TextViewDelegate {
super.viewDidLoad()
Manager.shared.openURL = UIApplication.shared.openURL
-
Manager.shared.isKeymanHelpOn = false
- do {
- try Manager.shared.preloadKeyboardFile(at: Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!,
- shouldOverwrite: true)
- try Manager.shared.preloadFontFile(at: Bundle.main.url(forResource: "aava1", withExtension: "ttf")!,
- shouldOverwrite: true)
- } catch {
- print("Error preloading: \(error)")
- }
- FontManager.shared.registerCustomFonts()
- Manager.shared.addKeyboard(Defaults.keyboard)
let kb = InstallableKeyboard(id: "tamil99m",
name: "Tamil 99M",
languageID: "tam",
@@ -38,6 +27,18 @@ class ViewController: UIViewController, TextViewDelegate {
font: Font(filename: "aava1.ttf"),
oskFont: nil,
isCustom: true)
+ let urls = [
+ Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!,
+ Bundle.main.url(forResource: "aava1", withExtension: "ttf")!
+ ]
+ do {
+ try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: urls, shouldOverwrite: true)
+ } catch {
+ print("Error preloading: \(error)")
+ }
+ FontManager.shared.registerCustomFonts()
+
+ Manager.shared.addKeyboard(Defaults.keyboard)
Manager.shared.addKeyboard(kb)
textView.setKeymanDelegate(self)