Merge branch 'master' into ios-special-osk-font

This commit is contained in:
Gabriel Wong 2017-12-18 09:52:37 +07:00
commit 60ccfa2896
34 changed files with 1390 additions and 1432 deletions

View file

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

View file

@ -28,6 +28,11 @@
android:label="@string/app_name"
android:theme="@android:style/Theme.Holo.Light.Dialog" >
</activity>
<activity
android:name="com.tavultesoft.kmea.KMKeyboardDownloaderActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:theme="@android:style/Theme.Holo.Light.Dialog">
</activity>
</application>
</manifest>

View file

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

View file

@ -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<KeyboardEventHandler.OnKeyboardDownloadEventListener> 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<Void, Integer, Integer>() {
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<String> urls = new ArrayList<String>();
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<String> fontUrls = fontUrls(jsonFont, fontBaseUri, true);
ArrayList<String> 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<String, String> keyboardInfo = new HashMap<String, String>();
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<String> fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
if (jsonFont == null)
return null;
ArrayList<String> urls = new ArrayList<String>();
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<KeyboardEventHandler.OnKeyboardDownloadEventListener>();
}
if (listener != null && !kbDownloadEventListeners.contains(listener)) {
kbDownloadEventListeners.add(listener);
}
}
public static void removeKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) {
if (kbDownloadEventListeners != null) {
kbDownloadEventListeners.remove(listener);
}
}
}

View file

@ -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<OnKeyboardDownloadEventListener> 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<Void, Integer, Integer>() {
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<String> urls = new ArrayList<String>();
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<String> fontUrls = fontUrls(jsonFont, fontBaseUri, true);
ArrayList<String> 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<String, String> keyboardInfo = new HashMap<String, String>();
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<String, String> keyboardInfo = new HashMap<String, String>();
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<Void, Integer, Integer>() {
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<String> urls = new ArrayList<String>();
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<String> fontUrls = fontUrls(jsonFont, fontBaseUri, true);
ArrayList<String> 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<String, String> keyboardInfo = new HashMap<String, String>();
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<String, String> keyboardInfo = new HashMap<String, String>();
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<String> fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
if (jsonFont == null)
return null;
ArrayList<String> urls = new ArrayList<String>();
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<Void, Integer, Integer>() {
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<String> urls = new ArrayList<String>();
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<String> fontUrls = fontUrls(jsonFont, fontBaseUri, true);
ArrayList<String> 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<String, String> keyboardInfo = new HashMap<String, String>();
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<String, String> keyboardInfo = new HashMap<String, String>();
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<String> fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) {
if (jsonFont == null)
return null;
ArrayList<String> urls = new ArrayList<String>();
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<String, String> 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<OnKeyboardDownloadEventListener>();
}
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);
}

View file

@ -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<HashMap<String, String>>();
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<String, String> 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

View file

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

View file

@ -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<String, HashMap<String, String>>();
keyboardModifiedDates = new HashMap<String, String>();
@ -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<String, HashMap<String, String>>();
keyboardModifiedDates = new HashMap<String, String>();
@ -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<String, String> 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);
}
}
});

View file

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

View file

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

View file

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

View file

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

View file

@ -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 = "<group>"; };
C040E5111F86107E00901EE4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
C040E5131F86108900901EE4 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = "<group>"; };
C042ED5C1FC6A65A001D82F4 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = "<group>"; };
C04514861F85D7F500D88416 /* SystemKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SystemKeyboard-Bridging-Header.h"; sourceTree = "<group>"; };
C04514871F85D7F500D88416 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = "<group>"; };
C04514891F85DF9000D88416 /* InputViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputViewController.swift; sourceTree = "<group>"; };
@ -222,6 +226,8 @@
C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNameTableViewCell.swift; sourceTree = "<group>"; };
C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInfoViewController.swift; sourceTree = "<group>"; };
C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardPickerViewController.swift; sourceTree = "<group>"; };
C08E698C1FDA392D0026056B /* Migrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migrations.swift; sourceTree = "<group>"; };
C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullKeyboardID.swift; sourceTree = "<group>"; };
C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageDetailViewController.swift; sourceTree = "<group>"; };
C0959CD31F99C44E00B616BC /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
C0A5FF361F6682EB00BE740C /* PopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = "<group>"; };
@ -442,6 +448,8 @@
C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */,
C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */,
C05F432C1FBD5A4C0058CBD4 /* KeyboardAPICall.swift */,
C042ED5C1FC6A65A001D82F4 /* Version.swift */,
C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */,
);
path = Model;
sourceTree = "<group>";
@ -588,6 +596,7 @@
C0E30C8B1FC40D0400C80416 /* Storage.swift */,
C0EF3E7A1F95B65300CE9BD4 /* KeymanWebDelegate.swift */,
C05B14321FD914870082A316 /* Log.swift */,
C08E698C1FDA392D0026056B /* Migrations.swift */,
);
path = Classes;
sourceTree = "<group>";
@ -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 */,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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..<tKey.lowerBound])!)
let t = String(fragment[tKey.upperBound...])
let frame = keyFrame(x: x, y: y, w: w, h: h)
let frame = KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h)
let preview = t.stringFromUTF16CodeUnits() ?? ""
showKeyPreview(self, keyFrame: frame, preview: preview)
delegate?.showKeyPreview(self, keyFrame: frame, preview: preview)
@ -218,7 +220,7 @@ extension KeymanWebViewController: WKScriptMessageHandler {
let y = CGFloat(Float(frameComponents[1])!)
let w = CGFloat(Float(frameComponents[2])!)
let h = CGFloat(Float(frameComponents[3])!)
let frame = keyFrame(x: x, y: y, w: w, h: h)
let frame = KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h)
let keyArray = keys.components(separatedBy: ";")
var subkeyIDs: [String] = []
@ -273,10 +275,9 @@ extension KeymanWebViewController: WKScriptMessageHandler {
}
}
private func keyFrame(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) -> 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)
}
}

View file

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

View file

@ -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..<extensionRange.lowerBound])
if let previousMax = latestVersion {
if compareVersions(previousMax, version) == .orderedAscending {
latestVersion = version
}
} else if compareVersions(version, version) != nil { // Ensure that the version number is valid
latestVersion = version
}
}
return latestVersion
}
/// Compares version numbers in dotted numberic format.
/// - Returns: ComparisonResult if both version numbers are valid.
func compareVersions(_ v1: String, _ v2: String) -> 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..<len {
// Shorter version number padded with trailing zero components
let component1 = components1[safe: i] ?? "0"
let component2 = components2[safe: i] ?? "0"
guard let val1 = Int(component1), val1 >= 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)
}

View file

@ -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<URL>] = [:]
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..<extensionRange.lowerBound])) else {
continue
}
if let previousMax = latestVersion {
latestVersion = max(version, previousMax)
} else {
latestVersion = version
}
}
return latestVersion
}
}

View file

@ -0,0 +1,29 @@
//
// FullKeyboardID.swift
// KeymanEngine
//
// Created by Gabriel Wong on 2017-12-08.
// Copyright © 2017 SIL International. All rights reserved.
//
import Foundation
/// A complete identifier for an `InstallableKeyboard`. Keyboards must have unique `FullKeyboardID`s.
public struct FullKeyboardID: Codable {
public var keyboardID: String
public var languageID: String
}
// MARK: - Equatable
extension FullKeyboardID: Equatable {
public static func ==(lhs: FullKeyboardID, rhs: FullKeyboardID) -> Bool {
return lhs.keyboardID == rhs.keyboardID && lhs.languageID == rhs.languageID
}
}
// MARK: - CustomStringConvertible
extension FullKeyboardID: CustomStringConvertible {
public var description: String {
return "<keyboardID: \"\(keyboardID)\", languageID: \"\(languageID)\">"
}
}

View file

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

View file

@ -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..<len {
let leftComponent = lhs.components[safe: i] ?? 0
let rightComponent = rhs.components[safe: i] ?? 0
if leftComponent < rightComponent {
return true
}
if leftComponent > rightComponent {
return false
}
}
return false
}
public static func ==(lhs: Version, rhs: Version) -> Bool {
return lhs.components == rhs.components
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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