Merge branch 'master' into fix/developer/2380-data-loss-with-unicode-in-ansi-document
3
.gitignore
vendored
|
|
@ -241,6 +241,9 @@
|
|||
# VS Code
|
||||
.vscode
|
||||
|
||||
# IDE files
|
||||
**/.idea/**/*.xml
|
||||
**/.idea/**/*.iml
|
||||
|
||||
.DS_Store
|
||||
|
||||
|
|
|
|||
|
|
@ -20,5 +20,6 @@ allprojects {
|
|||
google()
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,13 @@ dependencies {
|
|||
transitive = true
|
||||
}
|
||||
implementation 'androidx.preference:preference:1.1.0'
|
||||
|
||||
// Add dependency for generating QR Codes
|
||||
// (Even though it's embedded in KMEA, because we're manually copying keyman-engine.aar,
|
||||
// we "lose" it in the dependency management)
|
||||
implementation ('com.github.kenglxn.QRGen:android:2.6.0') {
|
||||
transitive = true
|
||||
}
|
||||
}
|
||||
|
||||
/*def void dumpProperties(it){
|
||||
|
|
|
|||
|
|
@ -82,6 +82,11 @@ dependencies {
|
|||
transitive = true
|
||||
}
|
||||
|
||||
// Generate QR Codes
|
||||
implementation ('com.github.kenglxn.QRGen:android:2.6.0') {
|
||||
transitive = true
|
||||
}
|
||||
|
||||
// Assign the annotation processor for tests.
|
||||
testAnnotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ public class KMKeyboardDownloaderActivity extends AppCompatActivity {
|
|||
public static final String ARG_MODEL_ID = "KMKeyboardActivity.modelID";
|
||||
public static final String ARG_MODEL_NAME = "KMKeyboardActivity.modelName";
|
||||
public static final String ARG_MODEL_URL = "KMKeyboardActivity.modelURL";
|
||||
public static final String ARG_MODEL_CUSTOM_HELP_LINK = "KMKeyboardActivity.customHelpLink";
|
||||
public static final String ARG_CUSTOM_HELP_LINK = "KMKeyboardActivity.customHelpLink";
|
||||
|
||||
// custom keyboard
|
||||
public static final String ARG_KEYBOARD = "KMKeyboardActivity.keyboard";
|
||||
|
|
|
|||
|
|
@ -2224,12 +2224,15 @@ public final class KMManager {
|
|||
|
||||
// Move the cursor back if there's a split surrogate pair
|
||||
if (Character.isHighSurrogate(sequence.charAt(sequence.length()-1))) {
|
||||
String origChars = sequence.toString();
|
||||
ic.commitText("", -1);
|
||||
sequence = ic.getTextBeforeCursor(length, 0);
|
||||
}
|
||||
|
||||
if (sequence != null && Character.isLowSurrogate(sequence.charAt(0))) {
|
||||
if (sequence == null || sequence.length() <= 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (Character.isLowSurrogate(sequence.charAt(0))) {
|
||||
// Adjust if the first char is also a split surrogate pair
|
||||
// subSequence indices are start(inclusive) to end(exclusive)
|
||||
sequence = sequence.subSequence(1, sequence.length());
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
package com.tavultesoft.kmea;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
|
||||
|
|
@ -12,27 +11,32 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
import androidx.appcompat.widget.Toolbar;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.core.content.FileProvider;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.HelpFile;
|
||||
import com.tavultesoft.kmea.util.MapCompat;
|
||||
import com.tavultesoft.kmea.util.QRCodeUtil;
|
||||
|
||||
// Public access is necessary to avoid IllegalAccessException
|
||||
public final class KeyboardInfoActivity extends AppCompatActivity {
|
||||
|
||||
private static final String TAG = "KeyboardInfoActivity";
|
||||
private static Toolbar toolbar = null;
|
||||
private static ListView listView = null;
|
||||
private static ArrayList<HashMap<String, String>> infoList = null;
|
||||
|
|
@ -46,7 +50,6 @@ public final class KeyboardInfoActivity extends AppCompatActivity {
|
|||
super.onCreate(savedInstanceState);
|
||||
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
|
||||
final Context context = this;
|
||||
final String authority = FileProviderUtils.getAuthority(context);
|
||||
|
||||
setContentView(R.layout.activity_list_layout);
|
||||
toolbar = (Toolbar) findViewById(R.id.list_toolbar);
|
||||
|
|
@ -120,36 +123,38 @@ public final class KeyboardInfoActivity extends AppCompatActivity {
|
|||
@Override
|
||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
if (position == 1) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
if (customHelpLink != null) {
|
||||
if (FileUtils.isWelcomeFile(customHelpLink) && ! KMManager.isTestMode()) {
|
||||
File customHelp = new File(new File(customHelpLink).getAbsolutePath());
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// Starting with Android N, you can't pass file:// to intents, so we use FileProvider
|
||||
try {
|
||||
Uri contentUri = FileProvider.getUriForFile(
|
||||
context, authority, customHelp);
|
||||
i.setDataAndType(contentUri, "text/html");
|
||||
} catch (NullPointerException e) {
|
||||
String message = "FileProvider undefined in app to load" + customHelp.toString();
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
Log.e("KeyboardInfoActivity", message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
i.setData(Uri.parse(customHelpLink));
|
||||
}
|
||||
// Display local welcome.htm help file, including associated assets
|
||||
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
|
||||
|
||||
if (FileProviderUtils.exists(context)|| KMManager.isTestMode()) {
|
||||
startActivity(i);
|
||||
}
|
||||
} else {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(helpUrlStr));
|
||||
startActivity(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If QRGen library included, also display QR code for sharing keyboard
|
||||
if (QRCodeUtil.libraryExists(context)) {
|
||||
String url = String.format("%s%s", QRCodeUtil.QR_BASE, kbID);
|
||||
|
||||
// Shorten listView so the QR code will show
|
||||
ViewGroup.LayoutParams lp = listView.getLayoutParams();
|
||||
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
listView.setLayoutParams(lp);
|
||||
|
||||
LinearLayout qrLayout = findViewById(R.id.qrLayout);
|
||||
qrLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
Bitmap myBitmap = QRCodeUtil.toBitmap(url);
|
||||
ImageView imageView = (ImageView) findViewById(R.id.qrCode);
|
||||
imageView.setImageBitmap(myBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -13,14 +13,18 @@ import androidx.appcompat.widget.Toolbar;
|
|||
import android.app.DialogFragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Typeface;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import androidx.core.content.FileProvider;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.widget.AdapterView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ListAdapter;
|
||||
import android.widget.ListView;
|
||||
import android.widget.SimpleAdapter;
|
||||
|
|
@ -29,7 +33,9 @@ import android.widget.Toast;
|
|||
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.HelpFile;
|
||||
import com.tavultesoft.kmea.util.MapCompat;
|
||||
import com.tavultesoft.kmea.util.QRCodeUtil;
|
||||
|
||||
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DELETE_KEYBOARD;
|
||||
|
||||
|
|
@ -135,33 +141,19 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
|
|||
|
||||
// "Help" link clicked
|
||||
if (itemTitle.equals(getString(R.string.help_link))) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
if (customHelpLink != null) {
|
||||
if (FileUtils.isWelcomeFile(customHelpLink)) {
|
||||
File customHelp = new File(new File(customHelpLink).getAbsolutePath());
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// Starting with Android N, you can't pass file:// to intents, so we use FileProvider
|
||||
try {
|
||||
Uri contentUri = FileProvider.getUriForFile(
|
||||
context, authority, customHelp);
|
||||
i.setDataAndType(contentUri, "text/html");
|
||||
} catch (NullPointerException e) {
|
||||
String message = "FileProvider undefined in app to load" + customHelp.toString();
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
Log.e("TAG", message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
i.setData(Uri.parse(customHelpLink));
|
||||
}
|
||||
if (FileProviderUtils.exists(context)) {
|
||||
// Display local welcome.htm help file, including associated assets
|
||||
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
|
||||
|
||||
if (FileProviderUtils.exists(context) || KMManager.isTestMode()) {
|
||||
startActivity(i);
|
||||
}
|
||||
} else {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(helpUrlStr));
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
// "Uninstall Keyboard" clicked
|
||||
} else if (itemTitle.equals(getString(R.string.uninstall_keyboard))) {
|
||||
// Uninstall selected keyboard
|
||||
|
|
@ -173,6 +165,23 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
// If QRGen library included, also display QR code for sharing keyboard
|
||||
if (QRCodeUtil.libraryExists(context)) {
|
||||
String url = String.format("%s%s", QRCodeUtil.QR_BASE, kbID);
|
||||
|
||||
// Shorten listView so the QR code will show
|
||||
ViewGroup.LayoutParams lp = listView.getLayoutParams();
|
||||
lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
listView.setLayoutParams(lp);
|
||||
|
||||
LinearLayout qrLayout = findViewById(R.id.qrLayout);
|
||||
qrLayout.setVisibility(View.VISIBLE);
|
||||
|
||||
Bitmap myBitmap = QRCodeUtil.toBitmap(url);
|
||||
ImageView imageView = (ImageView) findViewById(R.id.qrCode);
|
||||
imageView.setImageBitmap(myBitmap);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import android.widget.Toast;
|
|||
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.HelpFile;
|
||||
import com.tavultesoft.kmea.util.MapCompat;
|
||||
|
||||
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DELETE_MODEL;
|
||||
|
|
@ -81,7 +82,7 @@ public final class ModelInfoActivity extends AppCompatActivity {
|
|||
// Currently, model help only available if custom link exists
|
||||
String icon = String.valueOf(R.drawable.ic_arrow_forward);
|
||||
// Don't show help link arrow if both custom help and File Provider don't exist
|
||||
// TODO: Update this when model help available on help.keyman.com
|
||||
// TODO: Update this when model help available on help.keyman.com
|
||||
if ( (!customHelpLink.equals("") && !FileProviderUtils.exists(context)) ||
|
||||
customHelpLink.equals("") ){
|
||||
icon = noIcon;
|
||||
|
|
@ -128,32 +129,17 @@ public final class ModelInfoActivity extends AppCompatActivity {
|
|||
|
||||
// "Help" link clicked
|
||||
if (itemTitle.equals(getString(R.string.help_link))) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
if (!customHelpLink.equals("")) {
|
||||
if (FileUtils.isWelcomeFile(customHelpLink)) {
|
||||
File customHelp = new File(new File(customHelpLink).getAbsolutePath());
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// Starting with Android N, you can't pass file:// to intents, so we use FileProvider
|
||||
try {
|
||||
Uri contentUri = FileProvider.getUriForFile(
|
||||
context, authority, customHelp);
|
||||
i.setDataAndType(contentUri, "text/html");
|
||||
} catch (NullPointerException e) {
|
||||
String message = "FileProvider undefined in app to load" + customHelp.toString();
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
Log.e("ModelInfoActivity", message);
|
||||
}
|
||||
}
|
||||
else {
|
||||
i.setData(Uri.parse(customHelpLink));
|
||||
}
|
||||
if (FileProviderUtils.exists(context)) {
|
||||
// Display local welcome.htm help file, including associated assets
|
||||
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
|
||||
|
||||
if (FileProviderUtils.exists(context) || KMManager.isTestMode()) {
|
||||
startActivity(i);
|
||||
}
|
||||
} else {
|
||||
// We should always have a help file packaged with models.
|
||||
}
|
||||
|
||||
// "Uninstall Model" clicked
|
||||
} else if (itemTitle.equals(getString(R.string.uninstall_model))) {
|
||||
// Uninstall selected model
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ public class CloudDataJsonUtil {
|
|||
|
||||
public static HashMap<String,String> createKeyboardInfoMap(String aPackageId,String aLanguageId, String aLanguageName, String aKeyboardId,
|
||||
String aKeyboardName, String aKeyboardVersion, String anIsCustomKeyboard,
|
||||
String aFont, String aOskFont)
|
||||
String aFont, String aOskFont, String aCustomHelpLink)
|
||||
{
|
||||
HashMap<String, String> keyboardInfo = new HashMap<String, String>();
|
||||
keyboardInfo.put(KMManager.KMKey_PackageID, aPackageId);
|
||||
|
|
@ -48,8 +48,12 @@ public class CloudDataJsonUtil {
|
|||
keyboardInfo.put(KMManager.KMKey_KeyboardVersion, aKeyboardVersion);
|
||||
keyboardInfo.put(KMManager.KMKey_CustomKeyboard, anIsCustomKeyboard);
|
||||
keyboardInfo.put(KMManager.KMKey_Font, aFont);
|
||||
if (aOskFont != null)
|
||||
if (aOskFont != null) {
|
||||
keyboardInfo.put(KMManager.KMKey_OskFont, aOskFont);
|
||||
}
|
||||
if (aCustomHelpLink != null) {
|
||||
keyboardInfo.put(KMManager.KMKey_CustomHelpLink, aCustomHelpLink);
|
||||
}
|
||||
|
||||
return keyboardInfo;
|
||||
}
|
||||
|
|
@ -81,9 +85,10 @@ public class CloudDataJsonUtil {
|
|||
String kbName = keyboardJSON.getString(KMManager.KMKey_Name);
|
||||
String kbVersion = keyboardJSON.optString(KMManager.KMKey_KeyboardVersion, "1.0");
|
||||
String kbFont = keyboardJSON.optString(KMManager.KMKey_Font, "");
|
||||
String customHelpLink = keyboardJSON.optString(KMManager.KMKey_CustomHelpLink, null);
|
||||
|
||||
//String kbKey = String.format("%s_%s", langID, kbID);
|
||||
HashMap<String, String> hashMap = createKeyboardInfoMap(pkgID,langID,langName,kbID,kbName,kbVersion,isCustom,kbFont,null);
|
||||
HashMap<String, String> hashMap = createKeyboardInfoMap(pkgID,langID,langName,kbID,kbName,kbVersion,isCustom,kbFont,null, customHelpLink);
|
||||
|
||||
|
||||
// if (keyboardModifiedDates.get(kbID) == null) {
|
||||
|
|
|
|||
|
|
@ -271,12 +271,13 @@ public class CloudKeyboardMetaDataDownloadCallback implements ICloudDownloadCall
|
|||
|
||||
String _font = _keyboard.optString(KMManager.KMKey_Font);
|
||||
String _oskFont = _keyboard.optString(KMManager.KMKey_OskFont);
|
||||
String _customHelpLink = _keyboard.optString(KMManager.KMKey_CustomHelpLink, null);
|
||||
|
||||
|
||||
theKbData.additionalDownloadid = CloudKeyboardDataDownloadCallback.createDownloadId(_key_id);
|
||||
theKbData.keyboardInfo = CloudDataJsonUtil
|
||||
.createKeyboardInfoMap(
|
||||
_pkgID, _lang_id, _langName, _key_id, _kbName, _kbVersion, _kbIsCustom, _font, _oskFont);
|
||||
_pkgID, _lang_id, _langName, _key_id, _kbName, _kbVersion, _kbIsCustom, _font, _oskFont, _customHelpLink);
|
||||
theKbData.additionalDownloads = urls;
|
||||
}
|
||||
catch(JSONException _e)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import android.os.Bundle;
|
|||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
import com.tavultesoft.kmea.KeyboardPickerActivity;
|
||||
import com.tavultesoft.kmea.util.MapCompat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
|
@ -50,6 +51,13 @@ public class Keyboard implements Serializable, LanguageResource {
|
|||
return this.map.get(KMManager.KMKey_KeyboardName);
|
||||
}
|
||||
|
||||
public String getCustomHelpLink() {
|
||||
if (this.map.containsKey(KMManager.KMKey_CustomHelpLink)) {
|
||||
return this.map.get(KMManager.KMKey_CustomHelpLink);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.map.get(KMManager.KMKey_KeyboardVersion);
|
||||
}
|
||||
|
|
@ -60,6 +68,7 @@ public class Keyboard implements Serializable, LanguageResource {
|
|||
|
||||
public Bundle buildDownloadBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, getPackage());
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, getResourceId());
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, getLanguageCode());
|
||||
|
|
@ -70,9 +79,13 @@ public class Keyboard implements Serializable, LanguageResource {
|
|||
if(isCustom == null) {
|
||||
isCustom = "N";
|
||||
}
|
||||
|
||||
bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom.equals("Y"));
|
||||
|
||||
String customHelpLink = map.get(KMManager.KMKey_CustomHelpLink);
|
||||
if (customHelpLink != null) {
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_CUSTOM_HELP_LINK, getCustomHelpLink());
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ public interface LanguageResource {
|
|||
String getLanguageName();
|
||||
String getVersion();
|
||||
String getPackage();
|
||||
String getCustomHelpLink();
|
||||
|
||||
Bundle buildDownloadBundle();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import android.os.Bundle;
|
|||
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
import com.tavultesoft.kmea.util.MapCompat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
|
@ -52,6 +53,13 @@ public class LexicalModel implements Serializable, LanguageResource {
|
|||
return this.map.get(KMManager.KMKey_PackageID);
|
||||
}
|
||||
|
||||
public String getCustomHelpLink() {
|
||||
if (this.map.containsKey(KMManager.KMKey_CustomHelpLink)) {
|
||||
return this.map.get(KMManager.KMKey_CustomHelpLink);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Bundle buildDownloadBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
|
||||
|
|
@ -64,8 +72,6 @@ public class LexicalModel implements Serializable, LanguageResource {
|
|||
return null;
|
||||
}
|
||||
|
||||
String customHelpLink = map.get(KMManager.KMKey_CustomHelpLink);
|
||||
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, getPackage());
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_MODEL_ID, getResourceId());
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, getLanguageCode());
|
||||
|
|
@ -73,7 +79,11 @@ public class LexicalModel implements Serializable, LanguageResource {
|
|||
bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, getLanguageName());
|
||||
bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false);
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_MODEL_URL, modelURL);
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_MODEL_CUSTOM_HELP_LINK, customHelpLink);
|
||||
|
||||
String customHelpLink = map.get(KMManager.KMKey_CustomHelpLink);
|
||||
if (customHelpLink != null) {
|
||||
bundle.putString(KMKeyboardDownloaderActivity.ARG_CUSTOM_HELP_LINK, customHelpLink);
|
||||
}
|
||||
|
||||
return bundle;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.tavultesoft.kmea.logic;
|
||||
|
||||
|
||||
import android.app.NotificationChannel;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
|
|
@ -13,6 +12,7 @@ import android.content.SharedPreferences;
|
|||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
|
|
@ -21,7 +21,6 @@ import androidx.core.app.NotificationCompat;
|
|||
import androidx.core.app.NotificationCompat.Builder;
|
||||
import androidx.core.app.NotificationManagerCompat;
|
||||
|
||||
|
||||
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
|
||||
import com.tavultesoft.kmea.KeyboardPickerActivity;
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
|
|
@ -39,8 +38,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
|
||||
public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownloadEventListener, CloudRepository.UpdateHandler{
|
||||
private static final String TAG = "ResourceUpdateTool";
|
||||
|
||||
/**
|
||||
* Force resource update.
|
||||
|
|
@ -63,6 +66,16 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
*/
|
||||
public static final String PREF_KEY_LAST_UPDATE_CHECK = "lastUpdateCheck";
|
||||
|
||||
/**
|
||||
* Preference key for ignored notifications
|
||||
*/
|
||||
public static final String PREF_KEY_IGNORE_NOTIFICATIONS = "ignoredNotifications";
|
||||
|
||||
/**
|
||||
* Months to ignore update notification
|
||||
*/
|
||||
public static final int MONTHS_TO_IGNORE_NOTIFICATION = 3;
|
||||
|
||||
private static final class OngoingUpdate
|
||||
{
|
||||
Integer notificationid;
|
||||
|
|
@ -269,6 +282,84 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
addOpenUpdate(createKeyboardId(langid, kbid), null, theResourceBundle);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check shared preference to see if an update notification should be ignored.
|
||||
* The window is MONTHS_TO_IGNORE_NOTIFICATION from the last time the notification was ignored.
|
||||
* @param id keyboard or lexical model ID
|
||||
* @return true if the notification should be ignored
|
||||
*/
|
||||
private boolean shouldIgnoreNotification(String id) {
|
||||
SharedPreferences prefs = currentContext.getSharedPreferences(currentContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
String ignoredNotificationsStr = prefs.getString(PREF_KEY_IGNORE_NOTIFICATIONS, null);
|
||||
|
||||
/*
|
||||
* Preference is a JSON Object (as a string)
|
||||
* { PREF_KEY_IGNORE_NOTIFICATIONS :
|
||||
* { id1 : time1 ignored,
|
||||
* id2 : time2 ignored
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
JSONObject ignoredNotificationsObj;
|
||||
if (ignoredNotificationsStr != null) {
|
||||
try {
|
||||
ignoredNotificationsObj = new JSONObject(ignoredNotificationsStr);
|
||||
|
||||
Long lastIgnoredTime = ignoredNotificationsObj.optLong(id, 0);
|
||||
if (lastIgnoredTime > 0) {
|
||||
Calendar now = Calendar.getInstance();
|
||||
Calendar ignoreUntilTime = Calendar.getInstance();
|
||||
ignoreUntilTime.setTime(new Date(lastIgnoredTime));
|
||||
ignoreUntilTime.add(Calendar.MONTH, MONTHS_TO_IGNORE_NOTIFICATION);
|
||||
if (now.compareTo(ignoreUntilTime) < 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSON Exception parsing ignoreNotifications preference");
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update preference to ignore notifications for keyboard / lexical model ID
|
||||
* @param id : keyboard or lexical model ID to ignore for MONTHS_TO_IGNORE_NOTIFICATION months
|
||||
*/
|
||||
private void setPrefKeyIgnoreNotifications(String id) {
|
||||
SharedPreferences prefs = currentContext.getSharedPreferences(
|
||||
currentContext.getString(R.string.kma_prefs_name), Context.MODE_PRIVATE);
|
||||
SharedPreferences.Editor editor = prefs.edit();
|
||||
String ignoredNotificationsStr = prefs.getString(PREF_KEY_IGNORE_NOTIFICATIONS, null);
|
||||
|
||||
/*
|
||||
* Preference is a JSON Object (stored as a string)
|
||||
* { PREF_KEY_IGNORE_NOTIFICATIONS :
|
||||
* { id1 : time1 ignored,
|
||||
* id2 : time2 ignored
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
JSONObject ignoredNotificationsObj;
|
||||
try {
|
||||
if (ignoredNotificationsStr == null) {
|
||||
ignoredNotificationsObj = new JSONObject();
|
||||
} else {
|
||||
ignoredNotificationsObj = new JSONObject(ignoredNotificationsStr);
|
||||
}
|
||||
|
||||
Calendar now = Calendar.getInstance();
|
||||
ignoredNotificationsObj.put(id, now.getTime().getTime());
|
||||
editor.putString(PREF_KEY_IGNORE_NOTIFICATIONS, ignoredNotificationsObj.toString());
|
||||
editor.commit();
|
||||
} catch (JSONException e) {
|
||||
Log.e(TAG, "JSON Exception updating ignoreNotifications preference");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* send update notification.
|
||||
* @param theResourceBundle the bundle
|
||||
|
|
@ -290,13 +381,25 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
String modelid = theResourceBundle.getString(KMKeyboardDownloaderActivity.ARG_MODEL_ID);
|
||||
String modelName = theResourceBundle.getString(KMKeyboardDownloaderActivity.ARG_MODEL_NAME);
|
||||
message = currentContext.getString(R.string.dictionary_update_message, langName, modelName);
|
||||
addOpenUpdate(createLexicalModelId(langid,modelid),notification_id, theResourceBundle);
|
||||
if (!shouldIgnoreNotification(modelid)) {
|
||||
addOpenUpdate(createLexicalModelId(langid, modelid), notification_id, theResourceBundle);
|
||||
} else {
|
||||
// Update notification should be ignored
|
||||
notificationManager.cancel(notification_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
String kbid = theResourceBundle.getString(KMKeyboardDownloaderActivity.ARG_KB_ID);
|
||||
String kbName = theResourceBundle.getString(KMKeyboardDownloaderActivity.ARG_KB_NAME);
|
||||
message = currentContext.getString(R.string.keyboard_update_message, langName, kbName);
|
||||
addOpenUpdate(createKeyboardId(langid,kbid),notification_id, theResourceBundle);
|
||||
if (!shouldIgnoreNotification(kbid)) {
|
||||
addOpenUpdate(createKeyboardId(langid, kbid), notification_id, theResourceBundle);
|
||||
} else {
|
||||
// Update notification should be ignored
|
||||
notificationManager.cancel(notification_id);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -465,6 +568,7 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
public void cancelKeyboardUpdate(String aLangId, String aKbId)
|
||||
{
|
||||
setPrefKeyIgnoreNotifications(aKbId);
|
||||
removeOpenUpdate(createKeyboardId(aLangId,aKbId));
|
||||
if(openUpdates.isEmpty())
|
||||
checkingUpdates = false;
|
||||
|
|
@ -472,6 +576,7 @@ public class ResourcesUpdateTool implements KeyboardEventHandler.OnKeyboardDownl
|
|||
|
||||
public void cancelLexicalModelUpdate(String aLangId, String aModelId)
|
||||
{
|
||||
setPrefKeyIgnoreNotifications(aModelId);
|
||||
removeOpenUpdate(createLexicalModelId(aLangId,aModelId));
|
||||
if(openUpdates.isEmpty())
|
||||
checkingUpdates = false;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,89 @@
|
|||
package com.tavultesoft.kmea.util;
|
||||
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.core.content.FileProvider;
|
||||
|
||||
import com.tavultesoft.kmea.KMManager;
|
||||
import com.tavultesoft.kmea.util.FileProviderUtils;
|
||||
import com.tavultesoft.kmea.util.FileUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
|
||||
|
||||
public final class HelpFile {
|
||||
private static final String TAG = "HelpFile";
|
||||
private static final String[] ASSET_MIME_TYPES = {
|
||||
ClipDescription.MIMETYPE_TEXT_HTML,
|
||||
"text/css",
|
||||
"image/gif",
|
||||
"image/jpeg",
|
||||
"image/png"};
|
||||
|
||||
/**
|
||||
* Utility to pass a help file and all associated assets to an Intent for Intent.ACTION_VIEW
|
||||
* @param context
|
||||
* @param helpFile Full path string of the html file to view
|
||||
* @param packageID String of the package ID
|
||||
* @return Intent
|
||||
*/
|
||||
public static Intent toActionView(Context context, String helpFile, String packageID) {
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
|
||||
if (FileUtils.isWelcomeFile(helpFile) && ! KMManager.isTestMode()) {
|
||||
File customHelp = new File(new File(helpFile).getAbsolutePath());
|
||||
i.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
||||
// Starting with Android N, you can't pass file:// to intents, so we use FileProvider
|
||||
try {
|
||||
final String authority = FileProviderUtils.getAuthority(context);
|
||||
Uri contentUri = FileProvider.getUriForFile(
|
||||
context, authority, customHelp);
|
||||
i.setDataAndType(contentUri, "text/html");
|
||||
|
||||
// Grant read permission to all the files in the package so embedded assets can be viewed
|
||||
ClipData clipData = new ClipData(null, ASSET_MIME_TYPES, new ClipData.Item(contentUri));
|
||||
|
||||
// Exclude html help files and JS files. Treat rest of the files as assets
|
||||
FileFilter _fileFilter = new FileFilter() {
|
||||
@Override
|
||||
public boolean accept(File pathname) {
|
||||
String name = pathname.getName();
|
||||
if (pathname.isFile() && (FileUtils.isReadmeFile(name) ||
|
||||
FileUtils.isWelcomeFile(name) || FileUtils.hasJavaScriptExtension(name))) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
String base = helpFile.contains("packages") ? "packages" : "models";
|
||||
File packageDir = new File(
|
||||
context.getDir("data", Context.MODE_PRIVATE), base + File.separator + packageID + File.separator);
|
||||
File[] files = packageDir.listFiles(_fileFilter);
|
||||
for(File assetFile : files) {
|
||||
Uri assetUri = FileProvider.getUriForFile(
|
||||
context, authority, assetFile);
|
||||
clipData.addItem(new ClipData.Item(assetUri));
|
||||
}
|
||||
|
||||
// Associate assets in clipData to the intent
|
||||
i.setClipData(clipData);
|
||||
} catch (NullPointerException e) {
|
||||
String message = "FileProvider undefined in app to load" + customHelp.toString();
|
||||
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
|
||||
Log.e(TAG, message);
|
||||
}
|
||||
} else {
|
||||
i.setData(Uri.parse(helpFile));
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tavultesoft.kmea.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
|
||||
import net.glxn.qrgen.android.QRCode;
|
||||
|
||||
/**
|
||||
* Utility to generate QR Code from a URL string
|
||||
*/
|
||||
public final class QRCodeUtil {
|
||||
public static final int DEFAULT_HEIGHT = 800;
|
||||
public static final int DEFAULT_WIDTH = 800;
|
||||
public static final String QR_BASE = "https://keyman.com/go/keyboard/%s/share";
|
||||
|
||||
/**
|
||||
* Generate QR Code as a Bitmap
|
||||
* @param url String
|
||||
* @return Bitmap of the QR Code
|
||||
*/
|
||||
public static Bitmap toBitmap(String url) {
|
||||
Bitmap result = QRCode.from(url).withSize(DEFAULT_WIDTH, DEFAULT_HEIGHT).bitmap();
|
||||
return result;
|
||||
}
|
||||
|
||||
public static boolean libraryExists(Context context) {
|
||||
boolean result = false;
|
||||
try {
|
||||
Class.forName("net.glxn.qrgen.android.QRCode");
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,5 @@
|
|||
|
||||
<include layout="@layout/list_layout" />
|
||||
|
||||
<include layout="@layout/qr_layout" />
|
||||
</LinearLayout>
|
||||
|
|
|
|||
32
android/KMEA/app/src/main/res/layout/qr_layout.xml
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/qrLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="invisible">
|
||||
|
||||
<View style="@style/Divider.horizontal" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCode"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/qr_height"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/image_button"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/qrDescription"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/keyboard_qr_code"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="15dp"
|
||||
android:layout_marginBottom="15dp"
|
||||
android:layout_gravity="center"
|
||||
/>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
@ -17,4 +17,5 @@
|
|||
<dimen name="close_button_margin">12dp</dimen>
|
||||
<dimen name="fab_margin">10dp</dimen>
|
||||
<dimen name="fab_padding">70dp</dimen>
|
||||
<dimen name="qr_height">250dp</dimen>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@
|
|||
<string name="help_link" translatable="false">Help link</string>
|
||||
<string name="uninstall_keyboard" translatable="false">Uninstall keyboard</string>
|
||||
<string name="keyboard_picker_new_keyboard_prefix" translatable="false">[new]</string>
|
||||
<string name="keyboard_qr_code" translatable="false">Scan this code to load this\nkeyboard on another device</string>
|
||||
|
||||
<!-- Model Updates -->
|
||||
<string name="getting_model_catalog" translatable="false">Getting dictionary catalog.\nThis may take a while…</string>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,15 @@
|
|||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
<item name="android:background">@android:color/holo_red_dark</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider.horizontal" parent="Divider">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1dp</item>
|
||||
</style>
|
||||
|
||||
<style name="PopupAnim">
|
||||
<item name="android:windowEnterAnimation">@anim/fade_in</item>
|
||||
<item name="android:windowExitAnimation">@anim/fade_out</item>
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ public class CloudDataJsonUtilTest {
|
|||
private final String customKeyboard = "N";
|
||||
private final String aFont = "AndikaAfr";
|
||||
private final String oskFont = aFont;
|
||||
private final String customHelpLink = "";
|
||||
|
||||
@Before
|
||||
public void initializeTestPackage() {
|
||||
|
|
@ -40,7 +41,7 @@ public class CloudDataJsonUtilTest {
|
|||
public void shouldLowercaseLanguageID() {
|
||||
// Test createKeyboardInfoMap() used by processKeyboardJSON()
|
||||
HashMap<String, String> kbInfo = CloudDataJsonUtil.createKeyboardInfoMap(
|
||||
pkgID, langID, langName, keyboardID, keyboardName, keyboardVersion, customKeyboard, aFont, oskFont);
|
||||
pkgID, langID, langName, keyboardID, keyboardName, keyboardVersion, customKeyboard, aFont, oskFont, customHelpLink);
|
||||
Assert.assertEquals(langID.toLowerCase(), kbInfo.get(KMManager.KMKey_LanguageID));
|
||||
|
||||
// Test processLexicalModelJSON()
|
||||
|
|
|
|||
|
|
@ -14,5 +14,6 @@ allprojects {
|
|||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,12 +7,14 @@
|
|||
# KMW - Keyman Web
|
||||
|
||||
display_usage ( ) {
|
||||
echo "build.sh [-no-kmw-build] | [-no-kmw] [-no-daemon]"
|
||||
echo "build.sh [-no-kmw-build] | [-no-kmw] [-no-daemon] | [-no-test]"
|
||||
echo
|
||||
echo "Build Keyman Engine Android (KMEA) using Keyman Web (KMW) artifacts"
|
||||
echo " -no-kmw-build Don't build KMW. Just copy existing artifacts"
|
||||
echo " -no-kmw Don't build KMW. Don't copy artifacts"
|
||||
echo " -no-daemon Don't start the Gradle daemon. Use for CI"
|
||||
echo " -no-test Don't run the unit-test suite. Use for development builds"
|
||||
echo " to facilitate manual debugging and testing"
|
||||
exit 1
|
||||
}
|
||||
|
||||
|
|
@ -44,6 +46,7 @@ die ( ) {
|
|||
# Default is building KMW and copying artifacts
|
||||
DO_BUILD=true
|
||||
DO_COPY=true
|
||||
DO_TEST=true
|
||||
NO_DAEMON=false
|
||||
EMBED_BUILD=-embed
|
||||
KMW_PATH=
|
||||
|
|
@ -71,6 +74,9 @@ while [[ $# -gt 0 ]] ; do
|
|||
-h|-?)
|
||||
display_usage
|
||||
;;
|
||||
-no-test)
|
||||
DO_TEST=false
|
||||
;;
|
||||
esac
|
||||
shift # past argument
|
||||
done
|
||||
|
|
@ -78,6 +84,7 @@ done
|
|||
echo
|
||||
echo "DO_BUILD: $DO_BUILD"
|
||||
echo "DO_COPY: $DO_COPY"
|
||||
echo "DO_TEST: $DO_TEST"
|
||||
echo "NO_DAEMON: $NO_DAEMON"
|
||||
echo "DEBUG_BUILD: $DEBUG_BUILD"
|
||||
echo "EMBED_BUILD: $EMBED_BUILD"
|
||||
|
|
@ -94,8 +101,10 @@ fi
|
|||
|
||||
PLATFORM=`uname -s`
|
||||
|
||||
# Report JUnit test results to CI
|
||||
echo "##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testReleaseUnitTest\']"
|
||||
if [ DO_TEST = true ]; then
|
||||
# Report JUnit test results to CI
|
||||
echo "##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testReleaseUnitTest\']"
|
||||
fi
|
||||
|
||||
if [ "$DO_BUILD" = true ]; then
|
||||
echo "Building keyman web engine"
|
||||
|
|
@ -130,9 +139,11 @@ cd $KMA_ROOT/KMEA
|
|||
if [ $? -ne 0 ]; then
|
||||
die "ERROR: Build of KMEA failed"
|
||||
fi
|
||||
./gradlew $DAEMON_FLAG test
|
||||
if [ $? -ne 0 ]; then
|
||||
die "ERROR: KMEA test cases failed"
|
||||
if [ DO_TEST = true ]; then
|
||||
./gradlew $DAEMON_FLAG test
|
||||
if [ $? -ne 0 ]; then
|
||||
die "ERROR: KMEA test cases failed"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "Copying Keyman Engine for Android to KMAPro, Sample apps, and Tests"
|
||||
|
|
|
|||
|
|
@ -162,13 +162,20 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'com.android.support:appcompat-v7:27.1.1'
|
||||
implementation 'com.google.firebase:firebase-core:15.0.2'
|
||||
implementation 'com.google.firebase:firebase-crash:15.0.2'
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.9.2@aar') {
|
||||
implementation 'androidx.appcompat:appcompat:1.1.1'
|
||||
implementation 'com.google.android.material:material:1.0.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation "com.google.firebase:firebase-analytics:17.2.1"
|
||||
implementation "com.google.firebase:firebase-messaging:20.0.1"
|
||||
implementation "com.google.firebase:firebase-crash:16.2.1"
|
||||
implementation('com.crashlytics.sdk.android:crashlytics:2.10.1@aar') {
|
||||
transitive = true
|
||||
}
|
||||
|
||||
// Include this if you want to have QR Codes displayed on Keyboard Info
|
||||
implementation ('com.github.kenglxn.QRGen:android:2.6.0') {
|
||||
transitive = true
|
||||
}
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
}
|
||||
|
||||
````
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Keyman for Android
|
||||
# Keyman for Android Version History
|
||||
|
||||
## 13.0 alpha
|
||||
* Start version 13.0
|
||||
|
|
@ -9,15 +9,34 @@
|
|||
* Check for keyboard updates during keyman startup (#2335)
|
||||
* Show available keyboard updates as android system notifications (#2335)
|
||||
* Add update indicator icon to inform user about updates and install updates in keyman app (#2335)
|
||||
* Add preference so update notifications can be ignored 3 months (#2412)
|
||||
* Add QR Codes to Keyboard Info pages so users can share keyboard downloads (#2458)
|
||||
* Changes:
|
||||
* Update target Android SDK version to 29 (#2279)
|
||||
* Add simple UI tests for keyboard picker and keyboard info screens (#2326)
|
||||
* Add example dictionary to KMSample1 project (#2369)
|
||||
* Prevent lower-cased API returns from causing mismatches (#2404)
|
||||
* Bug fix:
|
||||
* Sanitize the app version to `#.#.#` for the API cloud query (#2319)
|
||||
* Add linting to Debug builds and resolve lint errors (#2305)
|
||||
* Fix memory issues during build process (#2361)
|
||||
* Fix crashes when parsing JSON data from Cloud (#2393)
|
||||
* Improve compatibility with applications such as Gmail, Chrome that do not conform to the Android input APIs (#2382, #2376)
|
||||
* Propagate custom help links (#2448)
|
||||
* Fix file permissions for viewing welcome.htm assets (#2465)
|
||||
|
||||
## 2019-12-12 12.0.4214 stable
|
||||
* Bug fix:
|
||||
* Fix crash involving 0-length context (#2444)
|
||||
|
||||
## 2019-12-09 12.0.4213 stable
|
||||
* Bug fix:
|
||||
* Always use lower-case langauge ID's when processing API returns (#2406)
|
||||
* Add checks when accessing the Cloud to avoid exceptions (#2393)
|
||||
* Improve Keymanweb and KMEA compatability with devices Android API 19-23 (#2358)
|
||||
|
||||
* Change
|
||||
* Update default nrc.en.mtnt model to version 0.1.3 (#2389)
|
||||
|
||||
## 2019-11-27 12.0.4211 stable
|
||||
* Bug fix:
|
||||
|
|
|
|||
16
common/lexical-model-types/index.d.ts
vendored
|
|
@ -230,17 +230,17 @@ declare type Distribution<T> = ProbabilityMass<T>[];
|
|||
*/
|
||||
declare interface Capabilities {
|
||||
/**
|
||||
* The maximum amount of UTF-16 code units that the keyboard will provide to
|
||||
* The maximum amount of UTF-16 code points that the keyboard will provide to
|
||||
* the left of the cursor, as an integer.
|
||||
*/
|
||||
readonly maxLeftContextCodeUnits: number,
|
||||
readonly maxLeftContextCodePoints: number,
|
||||
|
||||
/**
|
||||
* The maximum amount of code units that the keyboard will provide to the
|
||||
* The maximum amount of code points that the keyboard will provide to the
|
||||
* right of the cursor, as an integer. The value 0 or the absence of this
|
||||
* rule implies that the right contexts are not supported.
|
||||
*/
|
||||
readonly maxRightContextCodeUnits?: number,
|
||||
readonly maxRightContextCodePoints?: number,
|
||||
|
||||
/**
|
||||
* Whether the platform supports deleting to the right. The absence of this
|
||||
|
|
@ -262,7 +262,9 @@ declare interface Configuration {
|
|||
* While the left context MUST NOT bisect surrogate pairs, they MAY
|
||||
* bisect graphical clusters.
|
||||
*/
|
||||
leftContextCodeUnits: number;
|
||||
leftContextCodePoints: number;
|
||||
/** deprecated; use `leftContextCodePoints` instead! */
|
||||
leftContextCodeUnits?: number,
|
||||
|
||||
/**
|
||||
* How many UTF-16 code units maximum to send as the context to the
|
||||
|
|
@ -273,7 +275,9 @@ declare interface Configuration {
|
|||
* While the right context MUST NOT bisect surrogate pairs, they MAY
|
||||
* bisect graphical clusters.
|
||||
*/
|
||||
rightContextCodeUnits: number;
|
||||
rightContextCodePoints: number;
|
||||
/** deprecated; use `leftContextCodePoints` instead! */
|
||||
rightContextCodeUnits?: number,
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
1
common/predictive-text/.gitignore
vendored
|
|
@ -70,3 +70,4 @@ typings/
|
|||
# Intentional JavaScript files.
|
||||
!testing/**/*.js
|
||||
!unit_tests/**/*.js
|
||||
!polyfills/**/*.js
|
||||
|
|
|
|||
|
|
@ -103,6 +103,24 @@ wrap-worker-code ( ) {
|
|||
js="$2"
|
||||
echo "// Autogenerated code. Do not modify!"
|
||||
printf "function %s () {\n" "${name}"
|
||||
|
||||
# Since the worker is compiled with "allowJS=false" so that we can make
|
||||
# declaration files, we have to insert polyfills here.
|
||||
|
||||
# This one's a minimal, targeted polyfill. es6-shim could do the same,
|
||||
# but also adds a lot more code the worker doesn't need to use.
|
||||
# Recommended by MDN while keeping the worker lean and efficient.
|
||||
cat "node_modules/string.prototype.codepointat/codepointat.js"
|
||||
|
||||
# Needed to ensure functionality on some older Android devices. (API 19-23 or so)
|
||||
cat "node_modules/string.prototype.startswith/startswith.js"
|
||||
|
||||
# This one's straight from MDN - I didn't find any NPM ones that don't
|
||||
# use the node `require` statement.
|
||||
cat "polyfills/array.from.js"
|
||||
|
||||
echo ""
|
||||
|
||||
cat "${js}"
|
||||
printf "\n}\n"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,17 +147,17 @@ interface LoadMessage {
|
|||
*/
|
||||
capabilities: {
|
||||
/**
|
||||
* The maximum amount of UTF-16 code units that the keyboard will provide to
|
||||
* The maximum amount of UTF-16 code points that the keyboard will provide to
|
||||
* the left of the cursor, as an integer.
|
||||
*/
|
||||
maxLeftContextCodeUnits: number,
|
||||
maxLeftContextCodePoints: number,
|
||||
|
||||
/**
|
||||
* The maximum amount of code units that the keyboard will provide to the
|
||||
* The maximum amount of code points that the keyboard will provide to the
|
||||
* right of the cursor, as an integer. The value 0 or the absence of this
|
||||
* rule implies that the right contexts are not supported.
|
||||
*/
|
||||
maxRightContextCodeUnits?: number,
|
||||
maxRightContextCodePoints?: number,
|
||||
|
||||
/**
|
||||
* Whether the platform supports deleting to the right. The absence of this
|
||||
|
|
@ -221,7 +221,7 @@ interface ReadyMessage {
|
|||
message: 'ready';
|
||||
configuration: {
|
||||
/**
|
||||
* How many UTF-16 code units maximum to send as the context to the
|
||||
* How many UTF-16 code points maximum to send as the context to the
|
||||
* left of the cursor ("left" in the Unicode character stream).
|
||||
*
|
||||
* Affects the `context` property sent in `predict` messages.
|
||||
|
|
@ -229,10 +229,10 @@ interface ReadyMessage {
|
|||
* While the left context MUST NOT bisect surrogate pairs, they MAY
|
||||
* bisect graphical clusters.
|
||||
*/
|
||||
leftContextCodeUnits: number,
|
||||
leftContextCodePoints: number,
|
||||
|
||||
/**
|
||||
* How many UTF-16 code units maximum to send as the context to the
|
||||
* How many UTF-16 code points maximum to send as the context to the
|
||||
* right of the cursor ("right" in the Unicode character stream).
|
||||
*
|
||||
* Affects the `context` property sent in `predict` messages.
|
||||
|
|
@ -240,7 +240,7 @@ interface ReadyMessage {
|
|||
* While the left context MUST NOT bisect surrogate pairs, they MAY
|
||||
* bisect graphical clusters.
|
||||
*/
|
||||
rightContextCodeUnits: number,
|
||||
rightContextCodePoints: number,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
|
@ -291,7 +291,7 @@ transform is applied to the buffer.
|
|||
```typescript
|
||||
interface Context {
|
||||
/**
|
||||
* Up to maxLeftContextCodeUnits code units of Unicode scalar value
|
||||
* Up to maxLeftContextCodePoints code points of Unicode scalar value
|
||||
* (i. e., characters) to the left of the insertion point in the
|
||||
* buffer. If there is nothing to the left of the buffer, this returns
|
||||
* an empty string.
|
||||
|
|
@ -299,7 +299,7 @@ interface Context {
|
|||
left: USVString;
|
||||
|
||||
/**
|
||||
* Up to maxRightContextCodeUnits code units of Unicode scalar value
|
||||
* Up to maxRightContextCodePoints code points of Unicode scalar value
|
||||
* (i. e., characters) to the right of the insertion point in the
|
||||
* buffer. If there is nothing to the right of the buffer, this returns
|
||||
* an empty string.
|
||||
|
|
@ -336,14 +336,14 @@ interface Transform {
|
|||
insert: USVString;
|
||||
|
||||
/**
|
||||
* The number of code units to delete to the left of the cursor.
|
||||
* The number of code points to delete to the left of the cursor.
|
||||
*
|
||||
* Corresponds to `dn` in com.keyman.KeyboardInterface.output.
|
||||
*/
|
||||
deleteLeft: number;
|
||||
|
||||
/**
|
||||
* The number of code units to delete to the right of the cursor.
|
||||
* The number of code points to delete to the right of the cursor.
|
||||
* Not available on all platforms.
|
||||
*/
|
||||
deleteRight?: number;
|
||||
|
|
|
|||
30
common/predictive-text/package-lock.json
generated
|
|
@ -68,9 +68,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"agent-base": {
|
||||
"version": "4.2.1",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.1.tgz",
|
||||
"integrity": "sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg==",
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
|
||||
"integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
|
|
@ -611,9 +611,9 @@
|
|||
}
|
||||
},
|
||||
"es6-promise": {
|
||||
"version": "4.2.6",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.6.tgz",
|
||||
"integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q==",
|
||||
"version": "4.2.8",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
|
||||
"integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==",
|
||||
"dev": true
|
||||
},
|
||||
"es6-promisify": {
|
||||
|
|
@ -898,12 +898,12 @@
|
|||
}
|
||||
},
|
||||
"https-proxy-agent": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
||||
"integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
|
||||
"integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"agent-base": "^4.1.0",
|
||||
"agent-base": "^4.3.0",
|
||||
"debug": "^3.1.0"
|
||||
}
|
||||
},
|
||||
|
|
@ -1885,6 +1885,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"string.prototype.codepointat": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz",
|
||||
"integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg=="
|
||||
},
|
||||
"string.prototype.startswith": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-0.2.0.tgz",
|
||||
"integrity": "sha1-2miYLjU6TprEpDtFCiBF0cRFrns="
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.4.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz",
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@
|
|||
"typescript": "^3.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"es6-shim": "^0.35.5"
|
||||
"es6-shim": "^0.35.5",
|
||||
"string.prototype.codepointat": "^0.2.1",
|
||||
"string.prototype.startswith": "^0.2.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
81
common/predictive-text/polyfills/array.from.js
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
|
||||
// Any npm-based ones require use of `require`, which won't work for us.
|
||||
|
||||
// Production steps of ECMA-262, Edition 6, 22.1.2.1
|
||||
if (!Array.from) {
|
||||
Array.from = (function () {
|
||||
var toStr = Object.prototype.toString;
|
||||
var isCallable = function (fn) {
|
||||
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
|
||||
};
|
||||
var toInteger = function (value) {
|
||||
var number = Number(value);
|
||||
if (isNaN(number)) { return 0; }
|
||||
if (number === 0 || !isFinite(number)) { return number; }
|
||||
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
|
||||
};
|
||||
var maxSafeInteger = Math.pow(2, 53) - 1;
|
||||
var toLength = function (value) {
|
||||
var len = toInteger(value);
|
||||
return Math.min(Math.max(len, 0), maxSafeInteger);
|
||||
};
|
||||
|
||||
// The length property of the from method is 1.
|
||||
return function from(arrayLike/*, mapFn, thisArg */) {
|
||||
// 1. Let C be the this value.
|
||||
var C = this;
|
||||
|
||||
// 2. Let items be ToObject(arrayLike).
|
||||
var items = Object(arrayLike);
|
||||
|
||||
// 3. ReturnIfAbrupt(items).
|
||||
if (arrayLike == null) {
|
||||
throw new TypeError('Array.from requires an array-like object - not null or undefined');
|
||||
}
|
||||
|
||||
// 4. If mapfn is undefined, then let mapping be false.
|
||||
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
|
||||
var T;
|
||||
if (typeof mapFn !== 'undefined') {
|
||||
// 5. else
|
||||
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
|
||||
if (!isCallable(mapFn)) {
|
||||
throw new TypeError('Array.from: when provided, the second argument must be a function');
|
||||
}
|
||||
|
||||
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
|
||||
if (arguments.length > 2) {
|
||||
T = arguments[2];
|
||||
}
|
||||
}
|
||||
|
||||
// 10. Let lenValue be Get(items, "length").
|
||||
// 11. Let len be ToLength(lenValue).
|
||||
var len = toLength(items.length);
|
||||
|
||||
// 13. If IsConstructor(C) is true, then
|
||||
// 13. a. Let A be the result of calling the [[Construct]] internal method
|
||||
// of C with an argument list containing the single item len.
|
||||
// 14. a. Else, Let A be ArrayCreate(len).
|
||||
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
|
||||
|
||||
// 16. Let k be 0.
|
||||
var k = 0;
|
||||
// 17. Repeat, while k < len… (also steps a - h)
|
||||
var kValue;
|
||||
while (k < len) {
|
||||
kValue = items[k];
|
||||
if (mapFn) {
|
||||
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
|
||||
} else {
|
||||
A[k] = kValue;
|
||||
}
|
||||
k += 1;
|
||||
}
|
||||
// 18. Let putStatus be Put(A, "length", len, true).
|
||||
A.length = len;
|
||||
// 20. Return A.
|
||||
return A;
|
||||
};
|
||||
}());
|
||||
}
|
||||
|
|
@ -24,7 +24,9 @@ describe('The default word breaker', function () {
|
|||
// The following tests are performed with model integration as an internal
|
||||
// test for the wordbreaking API.
|
||||
it('recognizes a word at end of complete lefthand context', function () {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords // wordBreakers['default'] when fully integrated.
|
||||
});
|
||||
|
||||
// Standard case - wordbreaking at the end of a word.
|
||||
var context = {
|
||||
|
|
@ -40,7 +42,9 @@ describe('The default word breaker', function () {
|
|||
// Same test as before, but we want to be sure the start/end of buffer flags
|
||||
// don't affect our results.
|
||||
it('recognizes a word at end of incomplete lefthand context', function () {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// Standard case - wordbreaking at the end of a word.
|
||||
var context = {
|
||||
|
|
@ -54,7 +58,9 @@ describe('The default word breaker', function () {
|
|||
});
|
||||
|
||||
it('returns text for a word in-progress', function() {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// Standard case - midword (xylophone) call
|
||||
var context = {
|
||||
|
|
@ -68,7 +74,9 @@ describe('The default word breaker', function () {
|
|||
});
|
||||
|
||||
it('returns empty string when called without word text', function() {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// Wordbreaking on a empty space => no word.
|
||||
context = {
|
||||
|
|
@ -82,7 +90,9 @@ describe('The default word breaker', function () {
|
|||
});
|
||||
|
||||
it('returns empty string when called with empty context', function() {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// Wordbreaking on a empty space => no word.
|
||||
context = {
|
||||
|
|
@ -96,7 +106,9 @@ describe('The default word breaker', function () {
|
|||
});
|
||||
|
||||
it('returns empty string when called with nil context', function() {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// Wordbreaking on a empty space => no word.
|
||||
context = {
|
||||
|
|
@ -110,7 +122,9 @@ describe('The default word breaker', function () {
|
|||
});
|
||||
|
||||
it.skip('correctly breaks a word when the caret is placed within it', function() {
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'));
|
||||
var model = new TrieModel(jsonFixture('tries/english-1000'), {
|
||||
wordBreaker: breakWords
|
||||
});
|
||||
|
||||
// A limitation of the current implementation; we should fix this before release.
|
||||
// Then again, when typing this is probably fine; just not when not typing.
|
||||
|
|
|
|||
|
|
@ -168,8 +168,8 @@ describe('LMLayerWorker', function() {
|
|||
sinon.assert.calledWithMatch(fakePostMessage, {
|
||||
message: 'ready',
|
||||
configuration: {
|
||||
leftContextCodeUnits: maxCodeUnits,
|
||||
rightContextCodeUnits: 0,
|
||||
leftContextCodePoints: maxCodeUnits,
|
||||
rightContextCodePoints: 0,
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ _.createMessageEventWithData = function createMessageEventWithData(data) {
|
|||
*/
|
||||
_.capabilities = function capabilities() {
|
||||
return {
|
||||
maxLeftContextCodeUnits: 64
|
||||
maxLeftContextCodePoints: 64
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,12 +47,6 @@ describe('LMLayer using dummy model', function () {
|
|||
|
||||
describe('Wordbreaking', function () {
|
||||
it('will perform (default) wordbreaking and return word at caret', function () {
|
||||
if(navigator.userAgent.indexOf('MSIE') !== -1 || navigator.appVersion.indexOf('Trident/') > -1) {
|
||||
// Our wordbreaking uses the IE-unsupported .codePointAt() function.
|
||||
console.warn("Bypassing wordbreak test on IE.");
|
||||
return;
|
||||
}
|
||||
|
||||
this.timeout(config.timeouts.standard * 3); // This one makes multiple subsequent calls across
|
||||
// the WebWorker boundary, so we should be generous here.
|
||||
var lmLayer = new LMLayer(helpers.defaultCapabilities);
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
*/
|
||||
|
||||
/// <reference path="../message.d.ts" />
|
||||
/// <reference path="../../../web/source/text/kmwstring.ts" />
|
||||
/// <reference path="models/dummy-model.ts" />
|
||||
/// <reference path="word_breaking/ascii-word-breaker.ts" />
|
||||
/// <reference path="./model-compositor.ts" />
|
||||
|
|
@ -182,12 +183,20 @@ class LMLayerWorker {
|
|||
try {
|
||||
let configuration = model.configure(this._platformCapabilities);
|
||||
|
||||
// Set reasonable defaults for the configuration.
|
||||
if (!configuration.leftContextCodeUnits) {
|
||||
configuration.leftContextCodeUnits = this._platformCapabilities.maxLeftContextCodeUnits;
|
||||
// Handle deprecations.
|
||||
if(!configuration.leftContextCodePoints) {
|
||||
configuration.leftContextCodePoints = configuration.leftContextCodeUnits;
|
||||
}
|
||||
if (!configuration.rightContextCodeUnits) {
|
||||
configuration.rightContextCodeUnits = this._platformCapabilities.maxRightContextCodeUnits || 0;
|
||||
if(!configuration.rightContextCodePoints) {
|
||||
configuration.rightContextCodePoints = configuration.rightContextCodeUnits;
|
||||
}
|
||||
|
||||
// Set reasonable defaults for the configuration.
|
||||
if (!configuration.leftContextCodePoints) {
|
||||
configuration.leftContextCodePoints = this._platformCapabilities.maxLeftContextCodePoints;
|
||||
}
|
||||
if (!configuration.rightContextCodePoints) {
|
||||
configuration.rightContextCodePoints = this._platformCapabilities.maxRightContextCodePoints || 0;
|
||||
}
|
||||
|
||||
this.transitionToReadyState(model);
|
||||
|
|
|
|||
|
|
@ -50,8 +50,8 @@ namespace models {
|
|||
|
||||
configure(capabilities: Capabilities): Configuration {
|
||||
this.configuration = {
|
||||
leftContextCodeUnits: capabilities.maxLeftContextCodeUnits,
|
||||
rightContextCodeUnits: capabilities.maxRightContextCodeUnits
|
||||
leftContextCodePoints: capabilities.maxLeftContextCodePoints,
|
||||
rightContextCodePoints: capabilities.maxRightContextCodePoints
|
||||
};
|
||||
|
||||
return this.configuration;
|
||||
|
|
|
|||
|
|
@ -85,14 +85,14 @@
|
|||
trieData['totalWeight'],
|
||||
options.searchTermToKey as Wordform2Key || defaultWordform2Key
|
||||
);
|
||||
this.breakWords = options.wordBreaker || wordBreakers.placeholder;
|
||||
this.breakWords = options.wordBreaker || wordBreakers['default'];
|
||||
this.punctuation = options.punctuation;
|
||||
}
|
||||
|
||||
configure(capabilities: Capabilities): Configuration {
|
||||
return this.configuration = {
|
||||
leftContextCodeUnits: capabilities.maxLeftContextCodeUnits,
|
||||
rightContextCodeUnits: capabilities.maxRightContextCodeUnits
|
||||
leftContextCodePoints: capabilities.maxLeftContextCodePoints,
|
||||
rightContextCodePoints: capabilities.maxRightContextCodePoints
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -113,7 +113,7 @@
|
|||
let newContext = models.applyTransform(transform, context);
|
||||
|
||||
// Computes the different in word length after applying the transform above.
|
||||
let leftDelOffset = transform.deleteLeft - transform.insert.length;
|
||||
let leftDelOffset = transform.deleteLeft - transform.insert.kmwLength();
|
||||
|
||||
// All text to the left of the cursor INCLUDING anything that has
|
||||
// just been typed.
|
||||
|
|
@ -127,7 +127,7 @@
|
|||
// Delete whatever the prefix that the user wrote.
|
||||
// Note: a separate capitalization/orthography engine can take this
|
||||
// result and transform it as needed.
|
||||
deleteLeft: leftDelOffset + prefix.length,
|
||||
deleteLeft: leftDelOffset + prefix.kmwLength(),
|
||||
},
|
||||
displayAs: text,
|
||||
p: p
|
||||
|
|
@ -291,7 +291,7 @@
|
|||
* @param index The index in the prefix. Initially 0.
|
||||
*/
|
||||
function findPrefix(node: Node, key: SearchKey, index: number = 0): Node | null {
|
||||
if (node.type === 'leaf' || index === key.length) {
|
||||
if (node.type === 'leaf' || index === key.kmwLength()) {
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -21,8 +21,14 @@ namespace wordBreakers {
|
|||
let start = boundaries[i];
|
||||
let end = boundaries[i + 1];
|
||||
let span = new LazySpan(text, start, end);
|
||||
|
||||
if (isNonSpace(span.text)) {
|
||||
spans.push(span);
|
||||
// Preserve a sequence-final space if it exists. Needed to signal "end of word".
|
||||
} else if (i == boundaries.length - 2) { // if "we just checked the final boundary"...
|
||||
// We don't want to return the whitespace itself; the correct token is simply ''.
|
||||
span = new LazySpan(text, end, end);
|
||||
spans.push(span);
|
||||
}
|
||||
}
|
||||
return spans;
|
||||
|
|
|
|||
38
developer/inst/dist/CHANGELOG.md
vendored
|
|
@ -2,13 +2,13 @@
|
|||
|
||||
Select a Node.js version below to view the changelog history:
|
||||
|
||||
* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) - **Current**
|
||||
* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) - Current
|
||||
* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) — **Long Term Support**
|
||||
* [Node.js 12](doc/changelogs/CHANGELOG_V12.md) - **Long Term Support**
|
||||
* [Node.js 11](doc/changelogs/CHANGELOG_V11.md) - End-of-Life
|
||||
* [Node.js 10](doc/changelogs/CHANGELOG_V10.md) — Long Term Support
|
||||
* [Node.js 9](doc/changelogs/CHANGELOG_V9.md) — End-of-Life
|
||||
* [Node.js 8](doc/changelogs/CHANGELOG_V8.md) — Long Term Support
|
||||
* [Node.js 7](doc/changelogs/CHANGELOG_V7.md) — End-of-Life
|
||||
* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) — Long Term Support
|
||||
* [Node.js 6](doc/changelogs/CHANGELOG_V6.md) — End-of-Life
|
||||
* [Node.js 5](doc/changelogs/CHANGELOG_V5.md) — End-of-Life
|
||||
* [Node.js 4](doc/changelogs/CHANGELOG_V4.md) — End-of-Life
|
||||
* [io.js](doc/changelogs/CHANGELOG_IOJS.md) — End-of-Life
|
||||
|
|
@ -22,22 +22,34 @@ release.
|
|||
<!--lint disable maximum-line-length-->
|
||||
<table>
|
||||
<tr>
|
||||
<th title="Current"><a href="doc/changelogs/CHANGELOG_V12.md">12</a><sup>Current</sup></th>
|
||||
<th title="LTS Until 2022-04"><a href="doc/changelogs/CHANGELOG_V12.md">12</a><sup>LTS</sup></th>
|
||||
<th title="LTS Until 2021-04"><a href="doc/changelogs/CHANGELOG_V10.md">10</a><sup>LTS</sup></th>
|
||||
<th title="LTS Until 2019-12"><a href="doc/changelogs/CHANGELOG_V8.md">8</a><sup>LTS</sup></th>
|
||||
<th title="LTS Until 2019-04"><a href="doc/changelogs/CHANGELOG_V6.md">6</a><sup>LTS</sup></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td valign="top">
|
||||
<b><a href="doc/changelogs/CHANGELOG_V12.md#12.1.0">12.1.0</a></b><br/>
|
||||
<b><a href="doc/changelogs/CHANGELOG_V12.md#12.13.1">12.13.1</a></b><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.13.0">12.13.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.12.0">12.12.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.11.1">12.11.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.11.0">12.11.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.10.0">12.10.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.9.1">12.9.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.9.0">12.9.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.8.1">12.8.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.8.0">12.8.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.7.0">12.7.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.6.0">12.6.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.5.0">12.5.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.4.0">12.4.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.3.1">12.3.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.3.0">12.3.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.2.0">12.2.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.1.0">12.1.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V12.md#12.0.0">12.0.0</a><br/>
|
||||
</td>
|
||||
<td valign="top">
|
||||
<b><a href="doc/changelogs/CHANGELOG_V10.md#10.16.3">10.16.3</a></b><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.16.2">10.16.2</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.16.1">10.16.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.16.0">10.16.0</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.15.3">10.15.3</a><br/>
|
||||
<b><a href="doc/changelogs/CHANGELOG_V10.md#10.15.3">10.15.3</a></b><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.15.2">10.15.2</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.15.1">10.15.1</a><br/>
|
||||
<a href="doc/changelogs/CHANGELOG_V10.md#10.15.0">10.15.0</a><br/>
|
||||
|
|
@ -99,7 +111,7 @@ release.
|
|||
</tr>
|
||||
</table>
|
||||
|
||||
### Notes
|
||||
## Notes
|
||||
|
||||
* The [Node.js Long Term Support plan](https://github.com/nodejs/Release) covers
|
||||
LTS releases.
|
||||
|
|
|
|||
180
developer/inst/dist/LICENSE
vendored
|
|
@ -74,6 +74,29 @@ The externally maintained libraries used by Node.js are:
|
|||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- Acorn plugins, located at deps/acorn-plugins, is licensed as follows:
|
||||
"""
|
||||
Copyright (C) 2017-2018 by Adrian Heine
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- c-ares, located at deps/cares, is licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2007 - 2018, Daniel Stenberg with many contributors, see AUTHORS
|
||||
|
|
@ -606,9 +629,35 @@ The externally maintained libraries used by Node.js are:
|
|||
n° 289016). Three clause BSD license.
|
||||
"""
|
||||
|
||||
- llhttp, located at deps/llhttp, is licensed as follows:
|
||||
"""
|
||||
This software is licensed under the MIT License.
|
||||
|
||||
Copyright Fedor Indutny, 2018.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to permit
|
||||
persons to whom the Software is furnished to do so, subject to the
|
||||
following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
|
||||
USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- OpenSSL, located at deps/openssl, is licensed as follows:
|
||||
"""
|
||||
Copyright (c) 1998-2018 The OpenSSL Project. All rights reserved.
|
||||
Copyright (c) 1998-2019 The OpenSSL Project. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
|
|
@ -708,11 +757,13 @@ The externally maintained libraries used by Node.js are:
|
|||
This code is copyrighted by Sun Microsystems Inc. and released
|
||||
under a 3-clause BSD license.
|
||||
|
||||
- Valgrind client API header, located at third_party/valgrind/valgrind.h
|
||||
This is release under the BSD license.
|
||||
- Valgrind client API header, located at src/third_party/valgrind/valgrind.h
|
||||
This is released under the BSD license.
|
||||
|
||||
- antlr4 parser generator Cpp library located in third_party/antlr4
|
||||
This is release under the BSD license.
|
||||
- The Wasm C/C++ API headers, located at third_party/wasm-api/wasm.{h,hh}
|
||||
This is released under the Apache license. The API's upstream prototype
|
||||
implementation also formed the basis of V8's implementation in
|
||||
src/wasm/c-api.cc.
|
||||
|
||||
These libraries have their own licenses; we recommend you read them,
|
||||
as their terms may differ from the terms below.
|
||||
|
|
@ -1334,6 +1385,24 @@ The externally maintained libraries used by Node.js are:
|
|||
OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- caja, located at lib/internal/freeze_intrinsics.js, is licensed as follows:
|
||||
"""
|
||||
Adapted from SES/Caja - Copyright (C) 2011 Google Inc.
|
||||
Copyright (C) 2018 Agoric
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
"""
|
||||
|
||||
- brotli, located at deps/brotli, is licensed as follows:
|
||||
"""
|
||||
Copyright (c) 2009, 2010, 2013-2016 by the Brotli Authors.
|
||||
|
|
@ -1356,3 +1425,104 @@ The externally maintained libraries used by Node.js are:
|
|||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
"""
|
||||
|
||||
- HdrHistogram, located at deps/histogram, is licensed as follows:
|
||||
"""
|
||||
The code in this repository code was Written by Gil Tene, Michael Barker,
|
||||
and Matt Warren, and released to the public domain, as explained at
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
For users of this code who wish to consume it under the "BSD" license
|
||||
rather than under the public domain or CC0 contribution text mentioned
|
||||
above, the code found under this directory is *also* provided under the
|
||||
following license (commonly referred to as the BSD 2-Clause License). This
|
||||
license does not detract from the above stated release of the code into
|
||||
the public domain, and simply represents an additional license granted by
|
||||
the Author.
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
** Beginning of "BSD 2-Clause License" text. **
|
||||
|
||||
Copyright (c) 2012, 2013, 2014 Gil Tene
|
||||
Copyright (c) 2014 Michael Barker
|
||||
Copyright (c) 2014 Matt Warren
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
||||
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
|
||||
THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
|
||||
- node-heapdump, located at src/heap_utils.cc, is licensed as follows:
|
||||
"""
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2012, Ben Noordhuis <info@bnoordhuis.nl>
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
=== src/compat.h src/compat-inl.h ===
|
||||
|
||||
ISC License
|
||||
|
||||
Copyright (c) 2014, StrongLoop Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
|
||||
- rimraf, located at lib/internal/fs/rimraf.js, is licensed as follows:
|
||||
"""
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
"""
|
||||
|
|
|
|||
146
developer/inst/dist/README.md
vendored
|
|
@ -12,11 +12,10 @@ Node.js is a JavaScript runtime built on Chrome's V8 JavaScript engine. For
|
|||
more information on using Node.js, see the [Node.js Website][].
|
||||
|
||||
The Node.js project uses an [open governance model](./GOVERNANCE.md). The
|
||||
[Node.js Foundation][] provides support for the project.
|
||||
[OpenJS Foundation][] provides support for the project.
|
||||
|
||||
**This project is bound by a [Code of Conduct][].**
|
||||
|
||||
|
||||
# Table of Contents
|
||||
|
||||
* [Support](#support)
|
||||
|
|
@ -28,39 +27,18 @@ The Node.js project uses an [open governance model](./GOVERNANCE.md). The
|
|||
* [Verifying Binaries](#verifying-binaries)
|
||||
* [Building Node.js](#building-nodejs)
|
||||
* [Security](#security)
|
||||
* [Contributing to Node.js](#contributing-to-nodejs)
|
||||
* [Current Project Team Members](#current-project-team-members)
|
||||
* [TSC (Technical Steering Committee)](#tsc-technical-steering-committee)
|
||||
* [Collaborators](#collaborators)
|
||||
* [Release Keys](#release-keys)
|
||||
* [Contributing to Node.js](#contributing-to-nodejs)
|
||||
|
||||
## Support
|
||||
|
||||
Node.js contributors have limited availability to address general support
|
||||
questions. Please make sure you are using a [currently-supported version of
|
||||
Node.js](https://github.com/nodejs/Release#release-schedule).
|
||||
Looking for help? Check out the
|
||||
[instructions for getting support](.github/SUPPORT.md).
|
||||
|
||||
When looking for support, please first search for your question in these venues:
|
||||
|
||||
* [Node.js Website][]
|
||||
* [Node.js Help][]
|
||||
* [Open or closed issues in the Node.js GitHub organization](https://github.com/issues?utf8=%E2%9C%93&q=sort%3Aupdated-desc+org%3Anodejs+is%3Aissue)
|
||||
|
||||
If you didn't find an answer in the resources above, try these unofficial
|
||||
resources:
|
||||
|
||||
* [Questions tagged 'node.js' on StackOverflow][]
|
||||
* [#node.js channel on chat.freenode.net][]
|
||||
* [Node.js Slack Community](https://node-js.slack.com/)
|
||||
* To register: [nodeslackers.com](http://www.nodeslackers.com/)
|
||||
|
||||
GitHub issues are for tracking enhancements and bugs, not general support.
|
||||
|
||||
The open source license grants you the freedom to use Node.js. It does not
|
||||
guarantee commitments of other people's time. Please be respectful and manage
|
||||
your expectations.
|
||||
|
||||
## Release Types
|
||||
## Release Types
|
||||
|
||||
* **Current**: Under active development. Code for the Current release is in the
|
||||
branch for its major version number (for example,
|
||||
|
|
@ -110,7 +88,6 @@ Version-specific documentation is available in each release directory in the
|
|||
_docs_ subdirectory. Version-specific documentation is also at
|
||||
<https://nodejs.org/download/docs/>.
|
||||
|
||||
|
||||
### Verifying Binaries
|
||||
|
||||
Download directories contain a `SHASUMS256.txt` file with SHA checksums for the
|
||||
|
|
@ -160,6 +137,12 @@ source and a list of supported platforms.
|
|||
For information on reporting security vulnerabilities in Node.js, see
|
||||
[SECURITY.md](./SECURITY.md).
|
||||
|
||||
## Contributing to Node.js
|
||||
|
||||
* [Contributing to the project][]
|
||||
* [Working Groups][]
|
||||
* [Strategic Initiatives][]
|
||||
|
||||
## Current Project Team Members
|
||||
|
||||
For information about the governance of the Node.js project, see
|
||||
|
|
@ -171,6 +154,8 @@ For information about the governance of the Node.js project, see
|
|||
**Anna Henningsen** <anna@addaleax.net> (she/her)
|
||||
* [apapirovski](https://github.com/apapirovski) -
|
||||
**Anatoli Papirovski** <apapirovski@mac.com> (he/him)
|
||||
* [BethGriggs](https://github.com/BethGriggs) -
|
||||
**Beth Griggs** <Bethany.Griggs@uk.ibm.com> (she/her)
|
||||
* [ChALkeR](https://github.com/ChALkeR) -
|
||||
**Сковорода Никита Андреевич** <chalkerx@gmail.com> (he/him)
|
||||
* [cjihrig](https://github.com/cjihrig) -
|
||||
|
|
@ -183,10 +168,10 @@ For information about the governance of the Node.js project, see
|
|||
**Jeremiah Senkpiel** <fishrock123@rocketmail.com>
|
||||
* [gabrielschulhof](https://github.com/gabrielschulhof) -
|
||||
**Gabriel Schulhof** <gabriel.schulhof@intel.com>
|
||||
* [jasnell](https://github.com/jasnell) -
|
||||
**James M Snell** <jasnell@gmail.com> (he/him)
|
||||
* [gireeshpunathil](https://github.com/gireeshpunathil) -
|
||||
**Gireesh Punathil** <gpunathi@in.ibm.com> (he/him)
|
||||
* [jasnell](https://github.com/jasnell) -
|
||||
**James M Snell** <jasnell@gmail.com> (he/him)
|
||||
* [joyeecheung](https://github.com/joyeecheung) -
|
||||
**Joyee Cheung** <joyeec9h3@gmail.com> (she/her)
|
||||
* [mcollina](https://github.com/mcollina) -
|
||||
|
|
@ -195,14 +180,14 @@ For information about the governance of the Node.js project, see
|
|||
**Michael Dawson** <michael_dawson@ca.ibm.com> (he/him)
|
||||
* [MylesBorins](https://github.com/MylesBorins) -
|
||||
**Myles Borins** <myles.borins@gmail.com> (he/him)
|
||||
* [ofrobots](https://github.com/ofrobots) -
|
||||
**Ali Ijaz Sheikh** <ofrobots@google.com> (he/him)
|
||||
* [rvagg](https://github.com/rvagg) -
|
||||
**Rod Vagg** <rod@vagg.org>
|
||||
* [sam-github](https://github.com/sam-github) -
|
||||
**Sam Roberts** <vieuxtech@gmail.com>
|
||||
* [targos](https://github.com/targos) -
|
||||
**Michaël Zasso** <targos@protonmail.com> (he/him)
|
||||
* [thefourtheye](https://github.com/thefourtheye) -
|
||||
**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him)
|
||||
* [tniessen](https://github.com/tniessen) -
|
||||
**Tobias Nießen** <tniessen@tnie.de>
|
||||
* [Trott](https://github.com/Trott) -
|
||||
**Rich Trott** <rtrott@gmail.com> (he/him)
|
||||
|
||||
|
|
@ -226,10 +211,14 @@ For information about the governance of the Node.js project, see
|
|||
**Brian White** <mscdex@mscdex.net>
|
||||
* [nebrius](https://github.com/nebrius) -
|
||||
**Bryan Hughes** <bryan@nebri.us>
|
||||
* [ofrobots](https://github.com/ofrobots) -
|
||||
**Ali Ijaz Sheikh** <ofrobots@google.com> (he/him)
|
||||
* [orangemocha](https://github.com/orangemocha) -
|
||||
**Alexis Campailla** <orangemocha@nodejs.org>
|
||||
* [piscisaureus](https://github.com/piscisaureus) -
|
||||
**Bert Belder** <bertbelder@gmail.com>
|
||||
* [rvagg](https://github.com/rvagg) -
|
||||
**Rod Vagg** <r@va.gg>
|
||||
* [shigeki](https://github.com/shigeki) -
|
||||
**Shigeki Ohtsu** <ohtsu@ohtsu.org> (he/him)
|
||||
* [TimothyGu](https://github.com/TimothyGu) -
|
||||
|
|
@ -243,12 +232,8 @@ For information about the governance of the Node.js project, see
|
|||
**Anna Henningsen** <anna@addaleax.net> (she/her)
|
||||
* [ak239](https://github.com/ak239) -
|
||||
**Aleksei Koziatinskii** <ak239spb@gmail.com>
|
||||
* [andrasq](https://github.com/andrasq) -
|
||||
**Andras** <andras@kinvey.com>
|
||||
* [AndreasMadsen](https://github.com/AndreasMadsen) -
|
||||
**Andreas Madsen** <amwebdk@gmail.com> (he/him)
|
||||
* [AnnaMag](https://github.com/AnnaMag) -
|
||||
**Anna M. Kedzierska** <anna.m.kedzierska@gmail.com>
|
||||
* [antsmartian](https://github.com/antsmartian) -
|
||||
**Anto Aravinth** <anto.aravinth.cse@gmail.com> (he/him)
|
||||
* [apapirovski](https://github.com/apapirovski) -
|
||||
|
|
@ -279,10 +264,10 @@ For information about the governance of the Node.js project, see
|
|||
**Bartosz Sosnowski** <bartosz@janeasystems.com>
|
||||
* [calvinmetcalf](https://github.com/calvinmetcalf) -
|
||||
**Calvin Metcalf** <calvin.metcalf@gmail.com>
|
||||
* [cclauss](https://github.com/cclauss) -
|
||||
**Christian Clauss** <cclauss@me.com> (he/him)
|
||||
* [ChALkeR](https://github.com/ChALkeR) -
|
||||
**Сковорода Никита Андреевич** <chalkerx@gmail.com> (he/him)
|
||||
* [chrisdickinson](https://github.com/chrisdickinson) -
|
||||
**Chris Dickinson** <christopher.s.dickinson@gmail.com>
|
||||
* [cjihrig](https://github.com/cjihrig) -
|
||||
**Colin Ihrig** <cjihrig@gmail.com> (he/him)
|
||||
* [claudiorodriguez](https://github.com/claudiorodriguez) -
|
||||
|
|
@ -295,6 +280,8 @@ For information about the governance of the Node.js project, see
|
|||
**David Cai** <davidcai1993@yahoo.com> (he/him)
|
||||
* [davisjam](https://github.com/davisjam) -
|
||||
**Jamie Davis** <davisjam@vt.edu> (he/him)
|
||||
* [devnexen](https://github.com/devnexen) -
|
||||
**David Carlier** <devnexen@gmail.com>
|
||||
* [devsnek](https://github.com/devsnek) -
|
||||
**Gus Caplan** <me@gus.host> (he/him)
|
||||
* [digitalinfinity](https://github.com/digitalinfinity) -
|
||||
|
|
@ -303,16 +290,12 @@ For information about the governance of the Node.js project, see
|
|||
**Adrian Estrada** <edsadr@gmail.com> (he/him)
|
||||
* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) -
|
||||
**Robert Jefe Lindstaedt** <robert.lindstaedt@gmail.com>
|
||||
* [estliberitas](https://github.com/estliberitas) -
|
||||
**Alexander Makarenko** <estliberitas@gmail.com>
|
||||
* [eugeneo](https://github.com/eugeneo) -
|
||||
**Eugene Ostroukhov** <eostroukhov@google.com>
|
||||
* [evanlucas](https://github.com/evanlucas) -
|
||||
**Evan Lucas** <evanlucas@me.com> (he/him)
|
||||
* [fhinkel](https://github.com/fhinkel) -
|
||||
**Franziska Hinkelmann** <franziska.hinkelmann@gmail.com> (she/her)
|
||||
* [firedfox](https://github.com/firedfox) -
|
||||
**Daniel Wang** <wangyang0123@gmail.com>
|
||||
* [Fishrock123](https://github.com/Fishrock123) -
|
||||
**Jeremiah Senkpiel** <fishrock123@rocketmail.com>
|
||||
* [gabrielschulhof](https://github.com/gabrielschulhof) -
|
||||
|
|
@ -321,6 +304,8 @@ For information about the governance of the Node.js project, see
|
|||
**George Adams** <george.adams@uk.ibm.com> (he/him)
|
||||
* [geek](https://github.com/geek) -
|
||||
**Wyatt Preul** <wpreul@gmail.com>
|
||||
* [gengjiawen](https://github.com/gengjiawen) -
|
||||
**Jiawen Geng** <technicalcute@gmail.com>
|
||||
* [gibfahn](https://github.com/gibfahn) -
|
||||
**Gibson Fahnestock** <gibfahn@gmail.com> (he/him)
|
||||
* [gireeshpunathil](https://github.com/gireeshpunathil) -
|
||||
|
|
@ -349,32 +334,26 @@ For information about the governance of the Node.js project, see
|
|||
**Johan Bergström** <bugs@bergstroem.nu>
|
||||
* [jdalton](https://github.com/jdalton) -
|
||||
**John-David Dalton** <john.david.dalton@gmail.com>
|
||||
* [jhamhader](https://github.com/jhamhader) -
|
||||
**Yuval Brik** <yuval@brik.org.il>
|
||||
* [jkrems](https://github.com/jkrems) -
|
||||
**Jan Krems** <jan.krems@gmail.com> (he/him)
|
||||
* [joaocgreis](https://github.com/joaocgreis) -
|
||||
**João Reis** <reis@janeasystems.com>
|
||||
* [joshgav](https://github.com/joshgav) -
|
||||
**Josh Gavant** <josh.gavant@outlook.com>
|
||||
* [joyeecheung](https://github.com/joyeecheung) -
|
||||
**Joyee Cheung** <joyeec9h3@gmail.com> (she/her)
|
||||
* [julianduque](https://github.com/julianduque) -
|
||||
**Julian Duque** <julianduquej@gmail.com> (he/him)
|
||||
* [JungMinu](https://github.com/JungMinu) -
|
||||
**Minwoo Jung** <minwoo@nodesource.com> (he/him)
|
||||
**Minwoo Jung** <nodecorelab@gmail.com> (he/him)
|
||||
* [kfarnung](https://github.com/kfarnung) -
|
||||
**Kyle Farnung** <kfarnung@microsoft.com> (he/him)
|
||||
* [kunalspathak](https://github.com/kunalspathak) -
|
||||
**Kunal Pathak** <kunal.pathak@microsoft.com>
|
||||
* [lance](https://github.com/lance) -
|
||||
**Lance Ball** <lball@redhat.com> (he/him)
|
||||
* [legendecas](https://github.com/legendecas) -
|
||||
**Chengzhong Wu** <legendecas@gmail.com> (he/him)
|
||||
* [Leko](https://github.com/Leko) -
|
||||
**Shingo Inoue** <leko.noor@gmail.com> (he/him)
|
||||
* [lpinca](https://github.com/lpinca) -
|
||||
**Luigi Pinca** <luigipinca@gmail.com> (he/him)
|
||||
* [lucamaraschi](https://github.com/lucamaraschi) -
|
||||
**Luca Maraschi** <luca.maraschi@gmail.com> (he/him)
|
||||
* [lundibundi](https://github.com/lundibundi) -
|
||||
**Denys Otrishko** <shishugi@gmail.com> (he/him)
|
||||
* [maclover7](https://github.com/maclover7) -
|
||||
|
|
@ -399,14 +378,8 @@ For information about the governance of the Node.js project, see
|
|||
**Teddy Katz** <teddy.katz@gmail.com> (he/him)
|
||||
* [ofrobots](https://github.com/ofrobots) -
|
||||
**Ali Ijaz Sheikh** <ofrobots@google.com> (he/him)
|
||||
* [orangemocha](https://github.com/orangemocha) -
|
||||
**Alexis Campailla** <orangemocha@nodejs.org>
|
||||
* [othiym23](https://github.com/othiym23) -
|
||||
**Forrest L Norvell** <ogd@aoaioxxysz.net> (he/him)
|
||||
* [oyyd](https://github.com/oyyd) -
|
||||
**Ouyang Yadong** <oyydoibh@gmail.com> (he/him)
|
||||
* [pmq20](https://github.com/pmq20) -
|
||||
**Minqi Pan** <pmq2001@gmail.com>
|
||||
* [princejwesley](https://github.com/princejwesley) -
|
||||
**Prince John Wesley** <princejohnwesley@gmail.com>
|
||||
* [psmarshall](https://github.com/psmarshall) -
|
||||
|
|
@ -414,7 +387,7 @@ For information about the governance of the Node.js project, see
|
|||
* [Qard](https://github.com/Qard) -
|
||||
**Stephen Belanger** <admin@stephenbelanger.com> (he/him)
|
||||
* [refack](https://github.com/refack) -
|
||||
**Refael Ackermann** <refack@gmail.com> (he/him)
|
||||
**Refael Ackermann (רפאל פלחי)** <refack@gmail.com> (he/him/הוא/אתה)
|
||||
* [richardlau](https://github.com/richardlau) -
|
||||
**Richard Lau** <riclau@uk.ibm.com>
|
||||
* [ronkorving](https://github.com/ronkorving) -
|
||||
|
|
@ -447,16 +420,12 @@ For information about the governance of the Node.js project, see
|
|||
**Steven R Loomis** <srloomis@us.ibm.com>
|
||||
* [starkwang](https://github.com/starkwang) -
|
||||
**Weijia Wang** <starkwang@126.com>
|
||||
* [stefanmb](https://github.com/stefanmb) -
|
||||
**Stefan Budeanu** <stefan@budeanu.com>
|
||||
* [targos](https://github.com/targos) -
|
||||
**Michaël Zasso** <targos@protonmail.com> (he/him)
|
||||
* [thefourtheye](https://github.com/thefourtheye) -
|
||||
**Sakthipriyan Vairamani** <thechargingvolcano@gmail.com> (he/him)
|
||||
* [thekemkid](https://github.com/thekemkid) -
|
||||
**Glen Keane** <glenkeane.94@gmail.com> (he/him)
|
||||
* [thlorenz](https://github.com/thlorenz) -
|
||||
**Thorsten Lorenz** <thlorenz@gmx.de>
|
||||
* [TimothyGu](https://github.com/TimothyGu) -
|
||||
**Tiancheng "Timothy" Gu** <timothygu99@gmail.com> (he/him)
|
||||
* [tniessen](https://github.com/tniessen) -
|
||||
|
|
@ -471,14 +440,10 @@ For information about the governance of the Node.js project, see
|
|||
**Vladimir de Turckheim** <vlad2t@hotmail.com> (he/him)
|
||||
* [vkurchatkin](https://github.com/vkurchatkin) -
|
||||
**Vladimir Kurchatkin** <vladimir.kurchatkin@gmail.com>
|
||||
* [vsemozhetbyt](https://github.com/vsemozhetbyt) -
|
||||
**Vse Mozhet Byt** <vsemozhetbyt@gmail.com> (he/him)
|
||||
* [watilde](https://github.com/watilde) -
|
||||
**Daijiro Wachi** <daijiro.wachi@gmail.com> (he/him)
|
||||
* [watson](https://github.com/watson) -
|
||||
**Thomas Watson** <w@tson.dk>
|
||||
* [whitlockjc](https://github.com/whitlockjc) -
|
||||
**Jeremy Whitlock** <jwhitlock@apache.org>
|
||||
* [XadillaX](https://github.com/XadillaX) -
|
||||
**Khaidi Chu** <i@2333.moe> (he/him)
|
||||
* [yhwang](https://github.com/yhwang) -
|
||||
|
|
@ -492,10 +457,28 @@ For information about the governance of the Node.js project, see
|
|||
|
||||
### Collaborator Emeriti
|
||||
|
||||
* [andrasq](https://github.com/andrasq) -
|
||||
**Andras** <andras@kinvey.com>
|
||||
* [AnnaMag](https://github.com/AnnaMag) -
|
||||
**Anna M. Kedzierska** <anna.m.kedzierska@gmail.com>
|
||||
* [estliberitas](https://github.com/estliberitas) -
|
||||
**Alexander Makarenko** <estliberitas@gmail.com>
|
||||
* [chrisdickinson](https://github.com/chrisdickinson) -
|
||||
**Chris Dickinson** <christopher.s.dickinson@gmail.com>
|
||||
* [firedfox](https://github.com/firedfox) -
|
||||
**Daniel Wang** <wangyang0123@gmail.com>
|
||||
* [imran-iq](https://github.com/imran-iq) -
|
||||
**Imran Iqbal** <imran@imraniqbal.org>
|
||||
* [isaacs](https://github.com/isaacs) -
|
||||
**Isaac Z. Schlueter** <i@izs.me>
|
||||
* [jhamhader](https://github.com/jhamhader) -
|
||||
**Yuval Brik** <yuval@brik.org.il>
|
||||
* [joshgav](https://github.com/joshgav) -
|
||||
**Josh Gavant** <josh.gavant@outlook.com>
|
||||
* [kunalspathak](https://github.com/kunalspathak) -
|
||||
**Kunal Pathak** <kunal.pathak@microsoft.com>
|
||||
* [lucamaraschi](https://github.com/lucamaraschi) -
|
||||
**Luca Maraschi** <luca.maraschi@gmail.com> (he/him)
|
||||
* [lxe](https://github.com/lxe) -
|
||||
**Aleksey Smolenchuk** <lxe@lxe.co>
|
||||
* [matthewloring](https://github.com/matthewloring) -
|
||||
|
|
@ -508,12 +491,18 @@ For information about the governance of the Node.js project, see
|
|||
**Christopher Monsanto** <chris@monsan.to>
|
||||
* [Olegas](https://github.com/Olegas) -
|
||||
**Oleg Elifantiev** <oleg@elifantiev.ru>
|
||||
* [orangemocha](https://github.com/orangemocha) -
|
||||
**Alexis Campailla** <orangemocha@nodejs.org>
|
||||
* [othiym23](https://github.com/othiym23) -
|
||||
**Forrest L Norvell** <ogd@aoaioxxysz.net> (he/him)
|
||||
* [petkaantonov](https://github.com/petkaantonov) -
|
||||
**Petka Antonov** <petka_antonov@hotmail.com>
|
||||
* [phillipj](https://github.com/phillipj) -
|
||||
**Phillip Johnsen** <johphi@gmail.com>
|
||||
* [piscisaureus](https://github.com/piscisaureus) -
|
||||
**Bert Belder** <bertbelder@gmail.com>
|
||||
* [pmq20](https://github.com/pmq20) -
|
||||
**Minqi Pan** <pmq2001@gmail.com>
|
||||
* [rlidwka](https://github.com/rlidwka) -
|
||||
**Alex Kocharin** <alex@kocharin.ru>
|
||||
* [rmg](https://github.com/rmg) -
|
||||
|
|
@ -522,10 +511,18 @@ For information about the governance of the Node.js project, see
|
|||
**Robert Kowalski** <rok@kowalski.gd>
|
||||
* [romankl](https://github.com/romankl) -
|
||||
**Roman Klauke** <romaaan.git@gmail.com>
|
||||
* [stefanmb](https://github.com/stefanmb) -
|
||||
**Stefan Budeanu** <stefan@budeanu.com>
|
||||
* [tellnes](https://github.com/tellnes) -
|
||||
**Christian Tellnes** <christian@tellnes.no>
|
||||
* [thlorenz](https://github.com/thlorenz) -
|
||||
**Thorsten Lorenz** <thlorenz@gmx.de>
|
||||
* [tunniclm](https://github.com/tunniclm) -
|
||||
**Mike Tunnicliffe** <m.j.tunnicliffe@gmail.com>
|
||||
* [vsemozhetbyt](https://github.com/vsemozhetbyt) -
|
||||
**Vse Mozhet Byt** <vsemozhetbyt@gmail.com> (he/him)
|
||||
* [whitlockjc](https://github.com/whitlockjc) -
|
||||
**Jeremy Whitlock** <jwhitlock@apache.org>
|
||||
|
||||
Collaborators follow the [COLLABORATOR_GUIDE.md](./COLLABORATOR_GUIDE.md) in
|
||||
maintaining the Node.js project.
|
||||
|
|
@ -589,18 +586,9 @@ Other keys used to sign some previous releases:
|
|||
* **Timothy J Fontaine** <tjfontaine@gmail.com>
|
||||
`7937DFD2AB06298B2293C3187D33FF9D0246406D`
|
||||
|
||||
## Contributing to Node.js
|
||||
|
||||
* [Contributing to the project][]
|
||||
* [Working Groups][]
|
||||
* [Strategic Initiatives][]
|
||||
|
||||
[Code of Conduct]: https://github.com/nodejs/admin/blob/master/CODE_OF_CONDUCT.md
|
||||
[Contributing to the project]: CONTRIBUTING.md
|
||||
[Node.js Help]: https://github.com/nodejs/help
|
||||
[Node.js Foundation]: https://nodejs.org/en/foundation/
|
||||
[Node.js Website]: https://nodejs.org/en/
|
||||
[Questions tagged 'node.js' on StackOverflow]: https://stackoverflow.com/questions/tagged/node.js
|
||||
[Node.js Website]: https://nodejs.org/
|
||||
[OpenJS Foundation]: http://openjs.foundation/
|
||||
[Working Groups]: https://github.com/nodejs/TSC/blob/master/WORKING_GROUPS.md
|
||||
[Strategic Initiatives]: https://github.com/nodejs/TSC/blob/master/Strategic-Initiatives.md
|
||||
[#node.js channel on chat.freenode.net]: https://webchat.freenode.net?channels=node.js&uio=d4
|
||||
|
|
|
|||
BIN
developer/inst/dist/node.exe
vendored
13
developer/js/package-lock.json
generated
|
|
@ -554,19 +554,6 @@
|
|||
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
|
||||
"dev": true
|
||||
},
|
||||
"node": {
|
||||
"version": "11.15.0",
|
||||
"resolved": "https://registry.npmjs.org/node/-/node-11.15.0.tgz",
|
||||
"integrity": "sha512-Nbzq8qr133iwjGo0ZtzQR0mYeawW2eddYpW/k/+yjgbQW2/zG1a/5QiizVOrJ8yc4hbu3369zkj4dDIEd8f7dg==",
|
||||
"requires": {
|
||||
"node-bin-setup": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node-bin-setup": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/node-bin-setup/-/node-bin-setup-1.0.6.tgz",
|
||||
"integrity": "sha512-uPIxXNis1CRbv1DwqAxkgBk5NFV3s7cMN/Gf556jSw6jBvV7ca4F9lRL/8cALcZecRibeqU+5dFYqFFmzv5a0Q=="
|
||||
},
|
||||
"node-environment-flags": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
|
||||
|
|
|
|||
|
|
@ -34,9 +34,11 @@
|
|||
"kmlmp": "dist/kmlmp.js",
|
||||
"kmlmi": "dist/kmlmi.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^3.0.0",
|
||||
"node": "^11.7.0",
|
||||
"typescript": "^3.2.4",
|
||||
"xml2js": "^0.4.19"
|
||||
},
|
||||
|
|
|
|||
6
developer/js/tests/fixtures/example.qaa.smp/example.qaa.smp.model.ts
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
const source: LexicalModelSource = {
|
||||
format: 'trie-1.0',
|
||||
sources: ['wordlist.tsv'],
|
||||
wordBreaker: 'default',
|
||||
};
|
||||
export default source;
|
||||
5
developer/js/tests/fixtures/example.qaa.smp/wordlist.tsv
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
CRAZ🤪 13644
|
||||
🙄 9134
|
||||
😇 4816
|
||||
🇸
|
||||
🇺
|
||||
|
Can't render this file because it has a wrong number of fields in line 4.
|
|
|
@ -4,7 +4,6 @@ import 'mocha';
|
|||
|
||||
import {makePathToFixture, compileModelSourceCode} from './helpers';
|
||||
|
||||
|
||||
describe('LexicalModelCompiler', function () {
|
||||
describe('#generateLexicalModelCode', function () {
|
||||
it('should compile a trivial word list', function () {
|
||||
|
|
@ -70,4 +69,39 @@ describe('LexicalModelCompiler', function () {
|
|||
// Sanity check: the word breaker is a property of the object.
|
||||
assert.match(code, /\bwordBreaker\b["']?:\s+function\b/);
|
||||
});
|
||||
|
||||
it('should not generate unpaired surrogate code units', function () {
|
||||
const MODEL_ID = 'example.qaa.smp';
|
||||
const PATH = makePathToFixture(MODEL_ID);
|
||||
|
||||
let compiler = new LexicalModelCompiler;
|
||||
let code = compiler.generateLexicalModelCode(MODEL_ID, {
|
||||
format: 'trie-1.0',
|
||||
sources: ['wordlist.tsv']
|
||||
}, PATH) as string;
|
||||
|
||||
let result = compileModelSourceCode(code);
|
||||
assert.isFalse(result.hasSyntaxError);
|
||||
assert.isNotNull(result.exportedModel);
|
||||
assert.equal(result.modelConstructorName, 'TrieModel');
|
||||
|
||||
// Test every character in the string to make sure we don't have
|
||||
// unpaired surrogates which destroy everything.
|
||||
// We can assume that the first and last chars are not SMP
|
||||
for(var i = 1; i < code.length - 1; i++) {
|
||||
assert.notEqual(0xFFFD, code.charCodeAt(i));
|
||||
if(code.charCodeAt(i) >= 0xD800 && code.charCodeAt(i) < 0xDC00) {
|
||||
assert.isTrue((code.charCodeAt(i+1) >= 0xDC00 && code.charCodeAt(i+1) < 0xE000),
|
||||
'Unpaired lead surrogate U+'+code.charCodeAt(i).toString(16)+' at position '+i+' of \''+code+'\'');
|
||||
} else if(code.charCodeAt(i) >= 0xDC00 && code.charCodeAt(i) < 0xE000) {
|
||||
assert.isTrue((code.charCodeAt(i-1) >= 0xD800 && code.charCodeAt(i-1) < 0xDC00),
|
||||
'Unpaired trail surrogate U+'+code.charCodeAt(i).toString(16)+' at position '+i+' of \''+code+'\'');
|
||||
}
|
||||
}
|
||||
|
||||
// Sanity check: the word list has three total unweighted words, with a
|
||||
// total weight of 27,596!
|
||||
assert.match(code, /\btotalWeight\b["']?:\s*27596\b/);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -210,6 +210,13 @@
|
|||
CE2B1E4821B60E8A007D092E /* DeviceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE2B1E4521B60E7C007D092E /* DeviceKit.framework */; };
|
||||
CE2B1E4A21B60FB1007D092E /* DeviceKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE2B1E4521B60E7C007D092E /* DeviceKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */; };
|
||||
CE71705823A9C14D00A924A1 /* ResourceFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE71705723A9C14D00A924A1 /* ResourceFileManager.swift */; };
|
||||
CE71705F23A9C97F00A924A1 /* PackageInstallViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE71705E23A9C97F00A924A1 /* PackageInstallViewController.swift */; };
|
||||
CE7A26D123CEE5790005955C /* Keyboard Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */; };
|
||||
CE7A26D423CEE71B0005955C /* Keyboard Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */; };
|
||||
CE7A26D823CEEC640005955C /* Colors+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A26D723CEEC630005955C /* Colors+Extension.swift */; };
|
||||
CE7A26D923CEEC640005955C /* Colors+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A26D723CEEC630005955C /* Colors+Extension.swift */; };
|
||||
CE7A26DB23CEEF640005955C /* Colors.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7A26DA23CEEF640005955C /* Colors.swift */; };
|
||||
CE808A48236697BE00713E6B /* DeviceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE2B1E4521B60E7C007D092E /* DeviceKit.framework */; };
|
||||
CE808A4B236697D400713E6B /* ObjcExceptionBridging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1687ACCD1FD8DE5300926D69 /* ObjcExceptionBridging.framework */; };
|
||||
CE808A4D236697D500713E6B /* Reachability.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A0FC9FC22D66D9E00D33F86 /* Reachability.framework */; };
|
||||
|
|
@ -414,6 +421,11 @@
|
|||
CE24ECEF21B763740052D291 /* KeymanResponder+Types.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "KeymanResponder+Types.swift"; sourceTree = "<group>"; };
|
||||
CE2B1E4521B60E7C007D092E /* DeviceKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DeviceKit.framework; path = ../../Carthage/Build/iOS/DeviceKit.framework; sourceTree = "<group>"; };
|
||||
CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardCommandStructs.swift; sourceTree = "<group>"; };
|
||||
CE71705723A9C14D00A924A1 /* ResourceFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceFileManager.swift; sourceTree = "<group>"; };
|
||||
CE71705E23A9C97F00A924A1 /* PackageInstallViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageInstallViewController.swift; sourceTree = "<group>"; };
|
||||
CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Keyboard Colors.xcassets"; sourceTree = "<group>"; };
|
||||
CE7A26D723CEEC630005955C /* Colors+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Colors+Extension.swift"; sourceTree = "<group>"; };
|
||||
CE7A26DA23CEEF640005955C /* Colors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Colors.swift; sourceTree = "<group>"; };
|
||||
CECB38931F2199BC0098882F /* Reachability.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Reachability.h; path = KeymanEngine/lib/Reachability/Reachability.h; sourceTree = SOURCE_ROOT; };
|
||||
CECB38941F2199BC0098882F /* Reachability.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = Reachability.m; path = KeymanEngine/lib/Reachability/Reachability.m; sourceTree = SOURCE_ROOT; };
|
||||
F243887E14BBD43000A3E055 /* KeymanEngineDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KeymanEngineDemo.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
|
|
@ -696,6 +708,7 @@
|
|||
C06D372C1F81F4E100F61AE0 /* KeymanEngine */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE7A26D023CEE5790005955C /* Keyboard Colors.xcassets */,
|
||||
C06D372D1F81F4E100F61AE0 /* KeymanEngine.h */,
|
||||
C06D372E1F81F4E100F61AE0 /* Info.plist */,
|
||||
F2606FC4158F8B8600F37184 /* lib */,
|
||||
|
|
@ -714,8 +727,6 @@
|
|||
9A9CB0852241717C00231FB9 /* LexicalModelRepositoryDelegate.swift */,
|
||||
C07A9D8D1FD1798900828ADD /* APIKeyboardRepository.swift */,
|
||||
9A9CB0812241704800231FB9 /* APILexicalModelRepository.swift */,
|
||||
CE1F67A22304EB3800FF6972 /* ResourceDownloadManager.swift */,
|
||||
CE22DFB9230B94DB00A4551C /* ResourceDownloadQueue.swift */,
|
||||
);
|
||||
path = KeyboardRepository;
|
||||
sourceTree = "<group>";
|
||||
|
|
@ -738,6 +749,17 @@
|
|||
name = KeymanWebView;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
CE71705923A9C7D300A924A1 /* Resource Management */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE1F67A22304EB3800FF6972 /* ResourceDownloadManager.swift */,
|
||||
CE22DFB9230B94DB00A4551C /* ResourceDownloadQueue.swift */,
|
||||
CE71705723A9C14D00A924A1 /* ResourceFileManager.swift */,
|
||||
CE71705E23A9C97F00A924A1 /* PackageInstallViewController.swift */,
|
||||
);
|
||||
path = "Resource Management";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F243887314BBD43000A3E055 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
|
|
@ -825,6 +847,7 @@
|
|||
F273AB9615641D9300A47CEE /* Classes */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CE71705923A9C7D300A924A1 /* Resource Management */,
|
||||
165EB39F2098992D00040A69 /* Errors */,
|
||||
C055E6E81F99EA320035C2DD /* Extension */,
|
||||
C0452BA91F9F1CAF0064431A /* Model */,
|
||||
|
|
@ -844,6 +867,8 @@
|
|||
F273AB9E156440CD00A47CEE /* UITextField */,
|
||||
F273AB9D156440BA00A47CEE /* UITextView */,
|
||||
C0959CD31F99C44E00B616BC /* Constants.swift */,
|
||||
CE7A26D723CEEC630005955C /* Colors+Extension.swift */,
|
||||
CE7A26DA23CEEF640005955C /* Colors.swift */,
|
||||
C0B09EAD1FCFD10F002F39AF /* FontManager.swift */,
|
||||
C0E30C8B1FC40D0400C80416 /* Storage.swift */,
|
||||
C0EF3E7A1F95B65300CE9BD4 /* KeymanWebDelegate.swift */,
|
||||
|
|
@ -1052,6 +1077,7 @@
|
|||
C06D37601F82095200F61AE0 /* Keyman.bundle in Resources */,
|
||||
9ADC459F22E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */,
|
||||
9AD4F53D229F85AC007992D3 /* LanguageSettingsViewController.xib in Resources */,
|
||||
CE7A26D123CEE5790005955C /* Keyboard Colors.xcassets in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
@ -1064,6 +1090,7 @@
|
|||
6CD5DFAB150F6DC8007A5DDE /* icon@2x.png in Resources */,
|
||||
988B36B61ADF67290008752C /* inuktitut_pirurvik-1.0.js in Resources */,
|
||||
98D4190C17695E58008D2FF3 /* Default-568h@2x.png in Resources */,
|
||||
CE7A26D423CEE71B0005955C /* Keyboard Colors.xcassets in Resources */,
|
||||
988B36B91ADF728A0008752C /* inuktitut_latin-1.0.js in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -1197,6 +1224,7 @@
|
|||
9A079DE9223613E400581263 /* APIKeyboardRepository.swift in Sources */,
|
||||
9A079E48223DBF0A00581263 /* KeyboardMenuView.swift in Sources */,
|
||||
9A31E2C7224AE85700D9A491 /* Collection+SafeAccess.swift in Sources */,
|
||||
CE7A26D823CEEC640005955C /* Colors+Extension.swift in Sources */,
|
||||
9A079E3022361C2D00581263 /* KeyboardAPICall.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
@ -1210,6 +1238,7 @@
|
|||
C06D37341F81F5C300F61AE0 /* HTTPDownloader.swift in Sources */,
|
||||
C0452BAF1F9F22A80064431A /* Font.swift in Sources */,
|
||||
C0D3F3601F9F3AD80055C7CF /* InstallableKeyboard.swift in Sources */,
|
||||
CE7A26D923CEEC640005955C /* Colors+Extension.swift in Sources */,
|
||||
CE1E1EC12303C8CC001C7BE0 /* ResourceDownloadStatusToolbar.swift in Sources */,
|
||||
C042ED5D1FC6A65A001D82F4 /* Version.swift in Sources */,
|
||||
C0D3F35E1F9F33490055C7CF /* Options.swift in Sources */,
|
||||
|
|
@ -1224,6 +1253,7 @@
|
|||
CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */,
|
||||
9A4609972241B39B00B0BFD1 /* LexicalModelInfoViewController.swift in Sources */,
|
||||
9AD4F53C229F85AC007992D3 /* LanguageSettingsViewController.swift in Sources */,
|
||||
CE7A26DB23CEEF640005955C /* Colors.swift in Sources */,
|
||||
9A9CB08022416E5400231FB9 /* LexicalModelPickerViewController.swift in Sources */,
|
||||
9A079E3D223B5FAF00581263 /* InstallableLexicalModel.swift in Sources */,
|
||||
C06D37381F81F5C400F61AE0 /* SubKeysView.swift in Sources */,
|
||||
|
|
@ -1252,6 +1282,7 @@
|
|||
C0324B931F87689B00AF3785 /* KeymanURLProtocol.swift in Sources */,
|
||||
C05F43311FBD62550058CBD4 /* JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift in Sources */,
|
||||
C0452BAB1F9F1FE10064431A /* Language.swift in Sources */,
|
||||
CE71705F23A9C97F00A924A1 /* PackageInstallViewController.swift in Sources */,
|
||||
C06D37431F81F5C400F61AE0 /* KeyboardPickerBarButtonItem.swift in Sources */,
|
||||
C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */,
|
||||
C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */,
|
||||
|
|
@ -1260,6 +1291,7 @@
|
|||
C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */,
|
||||
9A9CB0822241704800231FB9 /* APILexicalModelRepository.swift in Sources */,
|
||||
C040E5101F8606E300901EE4 /* TextField.swift in Sources */,
|
||||
CE71705823A9C14D00A924A1 /* ResourceFileManager.swift in Sources */,
|
||||
9A082559227589360051EBB0 /* Formatter+ISODateExtension.swift in Sources */,
|
||||
C0324B8D1F87480700AF3785 /* TextFieldDelegateProxy.swift in Sources */,
|
||||
C075EB061F8EFF870041F4BD /* String+Helpers.swift in Sources */,
|
||||
|
|
|
|||
118
ios/engine/KMEI/KeymanEngine/Classes/Colors+Extension.swift
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
//
|
||||
// Colors+Extensions.swift
|
||||
// Keyman
|
||||
//
|
||||
// Created by Joshua Horton on 1/15/20.
|
||||
// Copyright © 2020 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// TODO: Relocated to the Keyman App's project. Not possible while the Settings menus are part of KMEI.
|
||||
extension Colors {
|
||||
public static var systemBackground: UIColor {
|
||||
get {
|
||||
if #available(iOS 13.0, *) {
|
||||
return UIColor.systemBackground
|
||||
} else {
|
||||
return UIColor.white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The primary color used for selected UI elements in the settings menu.
|
||||
public static var selectionPrimary: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "SelectionPrimary")!
|
||||
} else {
|
||||
return UIColor(red: 204.0 / 255.0,
|
||||
green: 136.0 / 255.0,
|
||||
blue: 34.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The primary color used for selected UI elements in Get Started menu.
|
||||
public static var selectionSecondary: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "SelectionSecondary")!
|
||||
} else {
|
||||
return UIColor(red: 95.0 / 255.0,
|
||||
green: 196.0 / 255.0,
|
||||
blue: 217.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var statusToolbar: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "StatusToolbar")!
|
||||
} else {
|
||||
return UIColor(red: 0.5,
|
||||
green: 0.75,
|
||||
blue: 0.25,
|
||||
alpha: 0.9)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var statusResourceUpdateButton: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "StatusResourceUpdateButton")!
|
||||
} else {
|
||||
return UIColor(red: 0.75,
|
||||
green: 1.0,
|
||||
blue: 0.5,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var spinnerBackground: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "SpinnerBackground")!
|
||||
} else {
|
||||
return UIColor(white: 0.5,
|
||||
alpha: 0.8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var labelNormal: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "LabelNormal")!
|
||||
} else {
|
||||
return UIColor.lightGray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var labelHighlighted: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "LabelHighlighted")!
|
||||
} else {
|
||||
return UIColor.darkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var listSeparator: UIColor {
|
||||
get {
|
||||
if #available(iOS 11.0, *) {
|
||||
return UIColor(named: "ListSeparator")!
|
||||
} else {
|
||||
return UIColor.lightGray
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
123
ios/engine/KMEI/KeymanEngine/Classes/Colors.swift
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// Colors.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Joshua Horton on 1/15/20.
|
||||
// Copyright © 2020 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// Used to facilitate constant colors with legacy (pre iOS-11.0) devices,
|
||||
// as they can't use "Color Assets".
|
||||
public class Colors {
|
||||
private static var engineBundle: Bundle {
|
||||
get {
|
||||
// We have to specify the bundle for _this framework_ since these are not
|
||||
// set by the app.
|
||||
return Bundle(for: Manager.self)
|
||||
}
|
||||
}
|
||||
|
||||
public static var popupBorder: UIColor {
|
||||
get {
|
||||
// if #available(iOSApplicationExtension 11.0, *) {
|
||||
// return UIColor(named: "SelectionPrimary")!
|
||||
// } else {
|
||||
return UIColor(red: 134.0 / 255.0,
|
||||
green: 137.0 / 255.0,
|
||||
blue: 139.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public static var popupKey: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "KeyPrimary", in: engineBundle, compatibleWith: nil)!
|
||||
} else {
|
||||
return UIColor(red: 244.0 / 255.0,
|
||||
green: 244.0 / 255.0,
|
||||
blue: 244.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var keyText: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "KeyText", in: engineBundle, compatibleWith: nil)!
|
||||
} else {
|
||||
return UIColor.black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var popupKeyHighlighted: UIColor {
|
||||
get {
|
||||
// if #available(iOSApplicationExtension 11.0, *) {
|
||||
// return UIColor(named: "SelectionPrimary")!
|
||||
// } else {
|
||||
return UIColor(red: 136.0 / 255.0,
|
||||
green: 136.0 / 255.0,
|
||||
blue: 1.0,
|
||||
alpha: 1.0)
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
public static var popupKeyTint: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "SelectionPrimary")!
|
||||
} else {
|
||||
return UIColor(red: 181.0 / 255.0,
|
||||
green: 181.0 / 255.0,
|
||||
blue: 181.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var helpBubbleGradient1: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "HelpBubbleGradient1")!
|
||||
} else {
|
||||
return UIColor(red: 253.0 / 255.0,
|
||||
green: 244.0 / 255.0,
|
||||
blue: 196.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var helpBubbleGradient2: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "HelpBubbleGradient2")!
|
||||
} else {
|
||||
return UIColor(red: 233.0 / 255.0,
|
||||
green: 224.0 / 255.0,
|
||||
blue: 176.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static var keyboardBackground: UIColor {
|
||||
get {
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
return UIColor(named: "KeyboardBackground", in: engineBundle, compatibleWith: nil)!
|
||||
} else {
|
||||
return UIColor(red: 210.0 / 255.0,
|
||||
green: 214.0 / 255.0,
|
||||
blue: 220.0 / 255.0,
|
||||
alpha: 1.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -21,6 +21,107 @@ public enum MenuBehaviour {
|
|||
case showNever
|
||||
}
|
||||
|
||||
private class CustomInputView: UIInputView {
|
||||
var setFrame: CGRect = CGRect.zero
|
||||
var keymanWeb: KeymanWebViewController!
|
||||
|
||||
// Constraints dependent upon the device's current rotation state.
|
||||
// For now, should be mostly upon keymanWeb.view.heightAnchor.
|
||||
var portraitConstraint: NSLayoutConstraint?
|
||||
var landscapeConstraint: NSLayoutConstraint?
|
||||
|
||||
init(frame: CGRect, innerVC: KeymanWebViewController!, inputViewStyle: UIInputView.Style) {
|
||||
super.init(frame: frame, inputViewStyle: inputViewStyle)
|
||||
self.setFrame = frame
|
||||
self.keymanWeb = innerVC
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
super.init(coder: coder)
|
||||
}
|
||||
|
||||
override var intrinsicContentSize: CGSize {
|
||||
/*
|
||||
* This function is the motivating reason for this class to exist as-is. If we return the default value
|
||||
* for this property, we cannot properly control the keyboard's scale in a manner consistent across both
|
||||
* use cases: in-app and system-wide.
|
||||
*/
|
||||
return self.setFrame.size
|
||||
}
|
||||
|
||||
// Allows us to intercept value assignments to keep `intrinsicContentSize` properly updated.
|
||||
override var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
}
|
||||
|
||||
set(value) {
|
||||
super.frame = value
|
||||
|
||||
// Store the originally-intended value, just in case iOS changes it later without our consent.
|
||||
self.setFrame = value
|
||||
}
|
||||
}
|
||||
|
||||
func setConstraints() {
|
||||
let innerView = keymanWeb.view!
|
||||
|
||||
var guide: UILayoutGuide
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
guide = self.safeAreaLayoutGuide
|
||||
} else {
|
||||
guide = self.layoutMarginsGuide
|
||||
}
|
||||
|
||||
// Fallback on earlier versions
|
||||
innerView.topAnchor.constraint(equalTo: guide.topAnchor).isActive = true
|
||||
innerView.bottomAnchor.constraint(equalTo: guide.bottomAnchor).isActive = true
|
||||
innerView.leftAnchor.constraint(equalTo: guide.leftAnchor).isActive = true
|
||||
innerView.rightAnchor.constraint(equalTo: guide.rightAnchor).isActive = true
|
||||
|
||||
// Allow these to be broken if/as necessary to resolve layout issues.
|
||||
let kbdWidthConstraint = innerView.widthAnchor.constraint(equalTo: guide.widthAnchor)
|
||||
kbdWidthConstraint.priority = .defaultHigh
|
||||
kbdWidthConstraint.isActive = true
|
||||
|
||||
// Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard.
|
||||
let portraitHeight = innerView.heightAnchor.constraint(equalToConstant: InputViewController.topBarHeight + keymanWeb.constraintTargetHeight(isPortrait: true))
|
||||
portraitHeight.identifier = "Height constraint for portrait mode"
|
||||
portraitHeight.priority = .defaultHigh
|
||||
let landscapeHeight = innerView.heightAnchor.constraint(equalToConstant: InputViewController.topBarHeight + keymanWeb.constraintTargetHeight(isPortrait: false))
|
||||
landscapeHeight.identifier = "Height constraint for landscape mode"
|
||||
landscapeHeight.priority = .defaultHigh
|
||||
|
||||
portraitConstraint = portraitHeight
|
||||
landscapeConstraint = landscapeHeight
|
||||
// .isActive will be set according to the current portrait/landscape perspective.
|
||||
}
|
||||
|
||||
override func updateConstraints() {
|
||||
super.updateConstraints()
|
||||
|
||||
// Keep the constraints up-to-date! They should vary based upon the selected keyboard.
|
||||
// TODO: actually check that the banner should be displayed! The property doesn't do this.
|
||||
let topBarHeight = InputViewController.topBarHeight
|
||||
portraitConstraint?.constant = topBarHeight + keymanWeb.constraintTargetHeight(isPortrait: true)
|
||||
landscapeConstraint?.constant = topBarHeight + keymanWeb.constraintTargetHeight(isPortrait: false)
|
||||
|
||||
// Activate / deactivate layout-specific constraints.
|
||||
if InputViewController.isPortrait {
|
||||
landscapeConstraint?.isActive = false
|
||||
portraitConstraint?.isActive = true
|
||||
} else {
|
||||
portraitConstraint?.isActive = false
|
||||
landscapeConstraint?.isActive = true
|
||||
}
|
||||
|
||||
keymanWeb.setBannerHeight(to: Int(InputViewController.topBarHeight))
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
|
||||
open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
||||
public var menuCloseButtonTitle: String?
|
||||
public var isInputClickSoundEnabled = true
|
||||
|
|
@ -45,7 +146,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
return UIScreen.main.bounds.width < UIScreen.main.bounds.height
|
||||
}
|
||||
|
||||
open class var topBarHeight: Int {
|
||||
open class var topBarHeight: CGFloat {
|
||||
if InputViewController.isPortrait {
|
||||
return 41
|
||||
}
|
||||
|
|
@ -93,25 +194,12 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
open override func updateViewConstraints() {
|
||||
resetKeyboardState()
|
||||
|
||||
// Activate / deactivate layout-specific constraints.
|
||||
if InputViewController.isPortrait {
|
||||
landscapeConstraint?.isActive = false
|
||||
portraitConstraint?.isActive = true
|
||||
} else {
|
||||
portraitConstraint?.isActive = false
|
||||
landscapeConstraint?.isActive = true
|
||||
}
|
||||
|
||||
keymanWeb.setBannerHeight(to: InputViewController.topBarHeight)
|
||||
|
||||
super.updateViewConstraints()
|
||||
}
|
||||
|
||||
open override func loadView() {
|
||||
let bgColor = UIColor(red: 210.0 / 255.0, green: 214.0 / 255.0, blue: 220.0 / 255.0, alpha: 1.0)
|
||||
let baseView = UIInputView(frame: CGRect.zero, inputViewStyle: .keyboard)
|
||||
|
||||
baseView.backgroundColor = bgColor
|
||||
let baseView = CustomInputView(frame: CGRect.zero, innerVC: keymanWeb, inputViewStyle: .keyboard)
|
||||
baseView.backgroundColor = Colors.keyboardBackground
|
||||
|
||||
// TODO: If the following line is enabled, the WKWebView does not respond to touch events
|
||||
// Can figure out why one day maybe
|
||||
|
|
@ -320,40 +408,8 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
|
|||
}
|
||||
|
||||
private func setInnerConstraints() {
|
||||
let container = keymanWeb.view!
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
container.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
|
||||
container.bottomAnchor.constraint(equalTo:view.safeAreaLayoutGuide.bottomAnchor).isActive = true
|
||||
container.leftAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leftAnchor).isActive = true
|
||||
container.rightAnchor.constraint(equalTo: view.safeAreaLayoutGuide.rightAnchor).isActive = true
|
||||
|
||||
// Allow these to be broken if/as necessary to resolve layout issues.
|
||||
let kbdWidthConstraint = container.widthAnchor.constraint(equalTo: view.safeAreaLayoutGuide.widthAnchor)
|
||||
kbdWidthConstraint.priority = .defaultHigh
|
||||
kbdWidthConstraint.isActive = true
|
||||
} else {
|
||||
// Fallback on earlier versions
|
||||
container.topAnchor.constraint(equalTo:view.layoutMarginsGuide.topAnchor).isActive = true
|
||||
container.bottomAnchor.constraint(equalTo:view.layoutMarginsGuide.bottomAnchor).isActive = true
|
||||
container.leftAnchor.constraint(equalTo:view.layoutMarginsGuide.leftAnchor).isActive = true
|
||||
container.rightAnchor.constraint(equalTo:view.layoutMarginsGuide.rightAnchor).isActive = true
|
||||
|
||||
// Allow these to be broken if/as necessary to resolve layout issues.
|
||||
let kbdWidthConstraint = container.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor)
|
||||
kbdWidthConstraint.priority = .defaultHigh
|
||||
kbdWidthConstraint.isActive = true
|
||||
}
|
||||
|
||||
// Cannot be met by the in-app keyboard, but helps to 'force' height for the system keyboard.
|
||||
let portraitHeight = container.heightAnchor.constraint(equalToConstant: keymanWeb.constraintTargetHeight(isPortrait: true))
|
||||
portraitHeight.priority = .defaultHigh
|
||||
let landscapeHeight = container.heightAnchor.constraint(equalToConstant: keymanWeb.constraintTargetHeight(isPortrait: false))
|
||||
landscapeHeight.priority = .defaultHigh
|
||||
|
||||
portraitConstraint = portraitHeight
|
||||
landscapeConstraint = landscapeHeight
|
||||
// .isActive will be set according to the current portrait/landscape perspective.
|
||||
let iv = self.inputView as! CustomInputView
|
||||
iv.setConstraints()
|
||||
|
||||
self.updateViewConstraints()
|
||||
fixLayout()
|
||||
|
|
|
|||
|
|
@ -15,9 +15,9 @@ class KeyPreviewView: UIView {
|
|||
private let adjX: CGFloat
|
||||
private let adjY: CGFloat
|
||||
|
||||
private let borderColor = UIColor(red: 145.0 / 255.0, green: 148.0 / 255.0, blue: 152.0 / 255.0, alpha: 1.0)
|
||||
private var bgColor = UIColor.white
|
||||
private let bgColor2 = UIColor.white
|
||||
private let borderColor = Colors.popupBorder
|
||||
private var bgColor = Colors.keyboardBackground
|
||||
private let bgColor2 = Colors.keyboardBackground
|
||||
private let label: UILabel
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
|
@ -59,7 +59,7 @@ class KeyPreviewView: UIView {
|
|||
label.shadowColor = UIColor.lightText
|
||||
label.shadowOffset = CGSize(width: 1.0, height: 1.0)
|
||||
label.backgroundColor = UIColor.clear
|
||||
label.textColor = UIColor.darkText
|
||||
label.textColor = Colors.keyText
|
||||
label.text = ""
|
||||
|
||||
super.init(frame: CGRect(x: viewPosX, y: viewPosY, width: viewWidth, height: viewHeight))
|
||||
|
|
|
|||
|
|
@ -9,10 +9,11 @@
|
|||
import Foundation
|
||||
import UIKit
|
||||
|
||||
// Used by the system keyboard as its globe key menu.
|
||||
class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGestureRecognizerDelegate {
|
||||
private let bgColor = UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0)
|
||||
private let bgColor2 = UIColor(red: 255.0 / 255.0, green: 255.0 / 255.0, blue: 255.0 / 255.0, alpha: 1.0)
|
||||
private let borderColor = UIColor(red: 134.0 / 255.0, green: 137.0 / 255.0, blue: 139.0 / 255.0, alpha: 1.0)
|
||||
private let bgColor = Colors.systemBackground
|
||||
private let bgColor2 = Colors.systemBackground
|
||||
private let borderColor = Colors.popupBorder
|
||||
private var borderRadius: CGFloat = 5.0
|
||||
private var strokeWidth: CGFloat = 0.75
|
||||
|
||||
|
|
@ -251,7 +252,7 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe
|
|||
|
||||
let cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 74.0 / 255.0, green: 186.0 / 255.0, blue: 208.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionSecondary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
return cell
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ import AudioToolbox
|
|||
|
||||
private let keyboardChangeHelpText = "Tap here to change keyboard"
|
||||
|
||||
private let subKeyColor = #colorLiteral(red: 244.0 / 255.0, green: 244.0 / 255.0, blue: 244.0 / 255.0, alpha: 1.0)
|
||||
private let subKeyColorHighlighted = #colorLiteral(red: 136.0 / 255.0, green: 136.0 / 255.0, blue: 1.0, alpha: 1.0)
|
||||
private let subKeyColor = Colors.popupKey
|
||||
private let subKeyColorHighlighted = Colors.popupKeyHighlighted
|
||||
|
||||
// UI In-App Keyboard Constants
|
||||
private let phonePortraitInAppKeyboardHeight: CGFloat = 183.0
|
||||
|
|
@ -317,7 +317,7 @@ extension KeymanWebViewController {
|
|||
webView!.evaluateJavaScript("keyman.registerModel(\(stubString));", completionHandler: nil)
|
||||
}
|
||||
|
||||
setBannerHeight(to: InputViewController.topBarHeight)
|
||||
setBannerHeight(to: Int(InputViewController.topBarHeight))
|
||||
}
|
||||
|
||||
func showBanner(_ display: Bool) {
|
||||
|
|
@ -719,8 +719,9 @@ extension KeymanWebViewController: UIGestureRecognizerDelegate {
|
|||
button.tag = i
|
||||
button.backgroundColor = subKeyColor
|
||||
button.setRoundedBorder(withRadius: 4.0, borderWidth: 1.0, color: .gray)
|
||||
button.setTitleColor(.black, for: .disabled)
|
||||
button.setTitleColor(.black, for: .highlighted)
|
||||
button.setTitleColor(Colors.keyText, for: .disabled)
|
||||
button.setTitleColor(Colors.keyText, for: .highlighted)
|
||||
button.setTitleColor(Colors.keyText, for: .normal)
|
||||
|
||||
if let oskFontName = oskFontName {
|
||||
button.titleLabel?.font = UIFont(name: oskFontName, size: fontSize)
|
||||
|
|
@ -753,7 +754,7 @@ extension KeymanWebViewController: UIGestureRecognizerDelegate {
|
|||
}
|
||||
|
||||
button.setTitle(displayText, for: .normal)
|
||||
button.tintColor = UIColor(red: 181.0 / 255.0, green: 181.0 / 255.0, blue: 181.0 / 255.0, alpha: 1.0)
|
||||
button.tintColor = Colors.popupKeyTint
|
||||
button.isEnabled = false
|
||||
return button
|
||||
}
|
||||
|
|
@ -911,11 +912,9 @@ extension KeymanWebViewController {
|
|||
self.helpBubbleView?.removeFromSuperview()
|
||||
let helpBubbleView = PopoverView(frame: CGRect.zero)
|
||||
self.helpBubbleView = helpBubbleView
|
||||
helpBubbleView.backgroundColor = UIColor(red: 253.0 / 255.0, green: 244.0 / 255.0,
|
||||
blue: 196.0 / 255.0, alpha: 1.0)
|
||||
helpBubbleView.backgroundColor2 = UIColor(red: 233.0 / 255.0, green: 224.0 / 255.0,
|
||||
blue: 176.0 / 255.0, alpha: 1.0)
|
||||
helpBubbleView.borderColor = UIColor(red: 0.5, green: 0.25, blue: 0.25, alpha: 1.0)
|
||||
helpBubbleView.backgroundColor = Colors.helpBubbleGradient1
|
||||
helpBubbleView.backgroundColor2 = Colors.helpBubbleGradient2
|
||||
helpBubbleView.borderColor = Colors.popupBorder
|
||||
|
||||
let isPad = UIDevice.current.userInterfaceIdiom == .pad
|
||||
let sizeMultiplier = CGFloat(isPad ? 1.5 : 1.0)
|
||||
|
|
@ -959,7 +958,7 @@ extension KeymanWebViewController {
|
|||
helpText.backgroundColor = UIColor.clear
|
||||
helpText.font = helpText.font.withSize(fontSize)
|
||||
helpText.textAlignment = .center
|
||||
helpText.textColor = UIColor.darkText
|
||||
//helpText.textColor = UIColor.darkText
|
||||
helpText.lineBreakMode = .byWordWrapping
|
||||
helpText.numberOfLines = 0
|
||||
helpText.text = keyboardChangeHelpText
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class KeyboardSwitcherViewController: UITableViewController, UIAlertViewDelegate
|
|||
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
return cell
|
||||
}
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
|
|||
|
||||
let cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
return cell
|
||||
}
|
||||
|
|
@ -172,7 +172,7 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
|
|||
view.isUserInteractionEnabled = false
|
||||
let indicatorView = UIActivityIndicatorView(style: .whiteLarge)
|
||||
let activityView = UIView(frame: indicatorView.bounds.insetBy(dx: -10.0, dy: -10.0))
|
||||
activityView.backgroundColor = UIColor(white: 0.5, alpha: 0.8)
|
||||
activityView.backgroundColor = Colors.spinnerBackground
|
||||
activityView.layer.cornerRadius = 6.0
|
||||
activityView.center = view.center
|
||||
activityView.tag = activityViewTag
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ class LanguageLMDetailViewController: UITableViewController, UIAlertViewDelegate
|
|||
|
||||
let cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
return cell
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,15 +125,13 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
|
|||
if keyboards.count < 2 {
|
||||
cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0,
|
||||
blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
} else {
|
||||
cell = UITableViewCell(style: .default, reuseIdentifier: cellIdentifier)
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0,
|
||||
blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +257,7 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
|
|||
view.isUserInteractionEnabled = false
|
||||
let indicatorView = UIActivityIndicatorView(style: .whiteLarge)
|
||||
let activityView = UIView(frame: indicatorView.bounds.insetBy(dx: -10.0, dy: -10.0))
|
||||
activityView.backgroundColor = UIColor(white: 0.5, alpha: 0.8)
|
||||
activityView.backgroundColor = Colors.spinnerBackground
|
||||
activityView.layer.cornerRadius = 6.0
|
||||
activityView.center = view.center
|
||||
activityView.tag = activityViewTag
|
||||
|
|
|
|||
|
|
@ -41,8 +41,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
navigationItem.rightBarButtonItem = addButton
|
||||
}
|
||||
|
||||
navigationController?.toolbar?.barTintColor = UIColor(red: 0.5, green: 0.75,
|
||||
blue: 0.25, alpha: 0.9)
|
||||
navigationController?.toolbar?.barTintColor = Colors.statusToolbar
|
||||
|
||||
lexicalModelDownloadStartedObserver = NotificationCenter.default.addObserver(
|
||||
forName: Notifications.lexicalModelDownloadStarted,
|
||||
|
|
@ -92,7 +91,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
return cell
|
||||
}
|
||||
|
|
@ -308,9 +307,9 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
self.lexicalModelDownloadFailed(LexicalModelDownloadFailedNotification(lmOrLanguageID: self.language.id, error: error))
|
||||
}
|
||||
} else if nil == lexicalModels {
|
||||
log.info("No lexical models available for language \(language.id) (nil)")
|
||||
noModelsAvailable(cause: "nil")
|
||||
} else if 0 == lexicalModels?.count {
|
||||
log.info("No lexical models available for language \(language.id) (empty)")
|
||||
noModelsAvailable(cause: "empty")
|
||||
} else {
|
||||
log.info("Fetched lexical model list for "+language.id+".")
|
||||
// show the list of lexical models (on the main thread)
|
||||
|
|
@ -326,6 +325,22 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
|
|||
|
||||
Manager.shared.apiLexicalModelRepository.fetchList(languageID: language.id, completionHandler: listCompletionHandler)
|
||||
}
|
||||
|
||||
func noModelsAvailable(cause: String = "nil") {
|
||||
let msg = "No dictionaries available"
|
||||
let logMsg = "No lexical models available for language \(language.id) (\(cause))"
|
||||
log.info(logMsg)
|
||||
|
||||
let alertController = UIAlertController(title: title, message: msg,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: { _ in
|
||||
self.navigationController?.popViewController(animated: true)
|
||||
}))
|
||||
|
||||
self.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -44,6 +44,8 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
|
||||
public static let shared = Manager()
|
||||
|
||||
public var fileBrowserLauncher: ((UINavigationController) -> Void)? = nil
|
||||
|
||||
/// Display the help bubble on first use.
|
||||
public var isKeymanHelpOn = true
|
||||
|
||||
|
|
@ -976,9 +978,12 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
|
|||
|
||||
// Keyboard download notification observers
|
||||
private func keyboardDownloadCompleted(_ keyboards: [InstallableKeyboard]) {
|
||||
// TODO: Only do this if it's an update. We'll need a bit of notification retooling for this first.
|
||||
// There's little harm in reloading the keyboard (and thus, KMW) for a clean reset
|
||||
// after resource downloads or updates. That said, we should avoid *directly*
|
||||
// triggering an immediate reset, as an extra reset will occur once we leave the
|
||||
// settings menu. The delay also helps any chained downloads (keyboard > lexical model)
|
||||
// to fully complete first.
|
||||
shouldReloadKeyboard = true
|
||||
inputViewController.reload()
|
||||
}
|
||||
|
||||
/*-----------------------------
|
||||
|
|
|
|||
|
|
@ -10,9 +10,22 @@ import Foundation
|
|||
|
||||
/// Mainly differs from the API `Keyboard` by having an associated language.
|
||||
public struct InstallableKeyboard: Codable, LanguageResource {
|
||||
// Details what properties are coded and decoded re: serialization.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case lgCode = "languageID" // The original name of the property, which we maintain for serialization.
|
||||
case languageName
|
||||
case version
|
||||
case isRTL
|
||||
case font
|
||||
case oskFont
|
||||
case isCustom
|
||||
}
|
||||
|
||||
public private(set) var id: String
|
||||
public var name: String
|
||||
public private(set) var languageID: String
|
||||
public private(set) var lgCode: String
|
||||
public var languageName: String
|
||||
public var version: String
|
||||
public var isRTL: Bool
|
||||
|
|
@ -20,6 +33,10 @@ public struct InstallableKeyboard: Codable, LanguageResource {
|
|||
public var oskFont: Font?
|
||||
public var isCustom: Bool
|
||||
|
||||
public var languageID: String {
|
||||
return lgCode.lowercased()
|
||||
}
|
||||
|
||||
public var fullID: FullKeyboardID {
|
||||
return FullKeyboardID(keyboardID: id, languageID: languageID)
|
||||
}
|
||||
|
|
@ -35,7 +52,7 @@ public struct InstallableKeyboard: Codable, LanguageResource {
|
|||
isCustom: Bool) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.languageID = languageID
|
||||
self.lgCode = languageID
|
||||
self.languageName = languageName
|
||||
self.version = version
|
||||
self.isRTL = isRTL
|
||||
|
|
@ -47,7 +64,7 @@ public struct InstallableKeyboard: Codable, LanguageResource {
|
|||
public init(keyboard: Keyboard, language: Language, isCustom: Bool) {
|
||||
self.id = keyboard.id
|
||||
self.name = keyboard.name
|
||||
self.languageID = language.id
|
||||
self.lgCode = language.id
|
||||
self.languageName = language.name
|
||||
self.version = keyboard.version
|
||||
self.isRTL = keyboard.isRTL
|
||||
|
|
|
|||
|
|
@ -14,11 +14,24 @@ struct InstallableConstants {
|
|||
|
||||
/// Mainly differs from the API `LexicalModel` by having an associated language.
|
||||
public struct InstallableLexicalModel: Codable, LanguageResource {
|
||||
// Details what properties are coded and decoded re: serialization.
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id
|
||||
case name
|
||||
case lgCode = "languageID" // Redirects the old plain-property to something we can wrap with accessors.
|
||||
case version
|
||||
case isCustom
|
||||
}
|
||||
|
||||
public private(set) var id: String
|
||||
public var name: String
|
||||
public private(set) var languageID: String
|
||||
private var lgCode: String
|
||||
public var version: String
|
||||
public var isCustom: Bool
|
||||
|
||||
public var languageID: String {
|
||||
return lgCode.lowercased()
|
||||
}
|
||||
|
||||
public var fullID: FullLexicalModelID {
|
||||
return FullLexicalModelID(lexicalModelID: id, languageID: languageID)
|
||||
|
|
@ -31,8 +44,7 @@ public struct InstallableLexicalModel: Codable, LanguageResource {
|
|||
isCustom: Bool) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.languageID = languageID
|
||||
// self.languageName = languageName
|
||||
self.lgCode = languageID
|
||||
self.version = version
|
||||
self.isCustom = isCustom
|
||||
}
|
||||
|
|
@ -40,8 +52,7 @@ public struct InstallableLexicalModel: Codable, LanguageResource {
|
|||
public init(lexicalModel: LexicalModel, languageID: String, isCustom: Bool) {
|
||||
self.id = lexicalModel.id
|
||||
self.name = lexicalModel.name
|
||||
self.languageID = languageID
|
||||
// self.languageName = language.name
|
||||
self.lgCode = languageID
|
||||
self.version = lexicalModel.version ?? InstallableConstants.defaultVersion
|
||||
self.isCustom = isCustom
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,10 +13,11 @@ class PopoverView: UIView {
|
|||
let arrowWidth: CGFloat = 21.0
|
||||
let arrowHeight: CGFloat = 7.0
|
||||
let borderRadius: CGFloat = 5.0
|
||||
var borderColor = UIColor(red: 125.0 / 255.0, green: 133.0 / 255.0, blue: 145.0 / 255.0, alpha: 1.0)
|
||||
private var bgColor = UIColor(red: 175.0 / 255.0, green: 175.0 / 255.0, blue: 175.0 / 255.0, alpha: 0.75)
|
||||
var backgroundColor2 = UIColor(red: 105.0 / 255.0,
|
||||
green: 105.0 / 255.0, blue: 105.0 / 255.0, alpha: 0.75)
|
||||
var borderColor = Colors.popupBorder
|
||||
|
||||
// A default color. This class's current only use will override these values.
|
||||
private var bgColor = Colors.systemBackground
|
||||
var backgroundColor2 = Colors.systemBackground
|
||||
private var _arrowPosX: CGFloat = 0.0
|
||||
|
||||
override init(frame: CGRect) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
//
|
||||
// PackageInstallViewController.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Joshua Horton on 12/18/19.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
public class PackageInstallViewController: UIViewController {
|
||||
public typealias CompletionHandler = (Error?) -> Void
|
||||
|
||||
let package: KeymanPackage
|
||||
var wkWebView: WKWebView?
|
||||
let completionHandler: CompletionHandler
|
||||
|
||||
public init(for package: KeymanPackage, completionHandler: @escaping CompletionHandler) {
|
||||
self.package = package
|
||||
self.completionHandler = completionHandler
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
|
||||
_ = view
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadView() {
|
||||
wkWebView = WKWebView.init(frame: .zero)
|
||||
wkWebView!.backgroundColor = .white
|
||||
view = wkWebView!
|
||||
|
||||
// Ensure the web view fills its available space.
|
||||
wkWebView?.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
let cancelBtn = UIBarButtonItem(title: "Cancel", style: .plain,
|
||||
target: self,
|
||||
action: #selector(cancelBtnHandler))
|
||||
let installBtn = UIBarButtonItem(title: "Install", style: .plain,
|
||||
target: self,
|
||||
action: #selector(installBtnHandler))
|
||||
|
||||
navigationItem.leftBarButtonItem = cancelBtn
|
||||
navigationItem.rightBarButtonItem = installBtn
|
||||
}
|
||||
|
||||
override public func viewWillAppear(_ animated: Bool) {
|
||||
wkWebView?.loadHTMLString(package.infoHtml(), baseURL: nil)
|
||||
}
|
||||
|
||||
@objc func cancelBtnHandler() {
|
||||
dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func installBtnHandler() {
|
||||
dismiss(animated: true, completion: {
|
||||
ResourceFileManager.shared.finalizePackageInstall(self.package, completionHandler: self.completionHandler)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -581,4 +581,8 @@ public class ResourceDownloadManager {
|
|||
|
||||
return updateQueue
|
||||
}
|
||||
|
||||
public func installLexicalModelPackage(at packageURL: URL) -> InstallableLexicalModel? {
|
||||
return downloader.installLexicalModelPackage(downloadedPackageFile: packageURL)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
//
|
||||
// ResourceFileManager.swift
|
||||
// KeymanEngine
|
||||
//
|
||||
// Created by Joshua Horton on December 18, 2019.
|
||||
// Copyright © 2019 SIL International. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
* This class stores common methods used for installing language resources, regardless of source.
|
||||
*
|
||||
* It also contains methods for general-purpose installation of language resources from .kmp files.
|
||||
*/
|
||||
public class ResourceFileManager {
|
||||
public static let shared = ResourceFileManager()
|
||||
|
||||
fileprivate init() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Apple doesn't provide a method that performs copy-and-overwrite functionality. This function fills in that gap.
|
||||
*/
|
||||
private func copyWithOverwrite(from source: URL, to destination: URL) throws {
|
||||
let fileManager = FileManager.default
|
||||
|
||||
// For now, we'll always allow overwriting.
|
||||
if fileManager.fileExists(atPath: destination.path) {
|
||||
try fileManager.removeItem(at: destination)
|
||||
}
|
||||
|
||||
// Throws an error if the destination file already exists, and there's no
|
||||
// built-in override parameter. Hence, the previous if-block.
|
||||
try fileManager.copyItem(at: source, to: destination)
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to "import" a file from outside the app's designated file system area to a new location within,
|
||||
* copying the original. It will be placed within the app's Documents folder.
|
||||
*
|
||||
* Returns the app-owned destination path, usable for subsequent file operations.
|
||||
*/
|
||||
public func importFile(_ url: URL) -> URL? {
|
||||
var destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
destinationUrl.appendPathComponent(url.lastPathComponent)
|
||||
|
||||
// Since it's possible to request an install from a KMP in our owned document space,
|
||||
// we need to check that it's not already in place where we want it.
|
||||
if url == destinationUrl {
|
||||
return url
|
||||
}
|
||||
|
||||
do {
|
||||
try copyWithOverwrite(from: url, to: destinationUrl)
|
||||
return destinationUrl
|
||||
} catch {
|
||||
log.error(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this function to "install" external KMP files to within the Keyman app's alloted iOS file management domain.
|
||||
* Note that we don't request permissions to support opening/modifying files "in place," so .kmps should already be
|
||||
* located in app-space (by use of `importFile`) before unzipping them.
|
||||
*/
|
||||
public func prepareKMPInstall(from url: URL, completionHandler: @escaping (KeymanPackage?, Error?) -> Void) {
|
||||
// Once selected, start the standard install process.
|
||||
log.info("Installing KMP from \(url)")
|
||||
|
||||
// Step 1: Copy it to a temporary location, making it a .zip in the process
|
||||
let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
var archiveUrl = cacheDirectory
|
||||
archiveUrl.appendPathComponent("\(url.lastPathComponent).zip")
|
||||
|
||||
do {
|
||||
try copyWithOverwrite(from: url, to: archiveUrl)
|
||||
} catch {
|
||||
log.error(error)
|
||||
completionHandler(nil, KMPError.copyFiles)
|
||||
return
|
||||
}
|
||||
|
||||
var extractionFolder = cacheDirectory
|
||||
extractionFolder.appendPathComponent("temp/\(archiveUrl.lastPathComponent)")
|
||||
|
||||
KeymanPackage.extract(fileUrl: archiveUrl, destination: extractionFolder, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
completionHandler(kmp, nil)
|
||||
} else {
|
||||
log.error(KMPError.invalidPackage)
|
||||
completionHandler(nil, KMPError.invalidPackage)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* A utility version of `prepareKMPInstall` that displays default UI alerts if errors occur when preparing a KMP for installation.
|
||||
*/
|
||||
public func prepareKMPInstall(from url: URL, alertHost: UIViewController, completionHandler: @escaping (KeymanPackage) -> Void) {
|
||||
self.prepareKMPInstall(from: url, completionHandler: { package, error in
|
||||
if error != nil {
|
||||
let alert = self.buildKMPError(KMPError.copyFiles)
|
||||
alertHost.present(alert, animated: true, completion: nil)
|
||||
} else {
|
||||
completionHandler(package!)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func promptPackageInstall(of package: KeymanPackage,
|
||||
in rootVC: UIViewController,
|
||||
successHandler: ((KeymanPackage) -> Void)? = nil) {
|
||||
let vc = PackageInstallViewController(for: package, completionHandler: { error in
|
||||
if let err = error {
|
||||
if let kmpError = err as? KMPError {
|
||||
let alert = self.buildKMPError(kmpError)
|
||||
rootVC.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
} else {
|
||||
let alert = self.buildSimpleAlert(title: "Success", message: "Installed successfully.", completionHandler: {
|
||||
successHandler?(package)
|
||||
})
|
||||
rootVC.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
})
|
||||
|
||||
let nvc = UINavigationController.init(rootViewController: vc)
|
||||
rootVC.present(nvc, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func buildKMPError(_ error: KMPError) -> UIAlertController {
|
||||
return buildSimpleAlert(title: "Error", message: error.rawValue)
|
||||
}
|
||||
|
||||
public func buildSimpleAlert(title: String, message: String, completionHandler: (() -> Void)? = nil ) -> UIAlertController {
|
||||
let alertController = UIAlertController(title: title, message: message,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: { _ in
|
||||
completionHandler?()
|
||||
}))
|
||||
|
||||
//UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
|
||||
return alertController
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the actual installation of a package's resources once confirmation has been received from the user.
|
||||
*/
|
||||
public func finalizePackageInstall(_ package: KeymanPackage, completionHandler: (Error?) -> Void) {
|
||||
do {
|
||||
// Time to pass the package off to the final installers - the parse__KMP methods.
|
||||
// TODO: (14.0+) These functions should probably be refactored to within this class eventually.
|
||||
if package.isKeyboard() {
|
||||
try Manager.shared.parseKbdKMP(package.sourceFolder)
|
||||
} else {
|
||||
try Manager.parseLMKMP(package.sourceFolder)
|
||||
}
|
||||
completionHandler(nil)
|
||||
} catch {
|
||||
log.error(error as! KMPError)
|
||||
completionHandler(error)
|
||||
}
|
||||
|
||||
//this can fail gracefully and not show errors to users
|
||||
do {
|
||||
try FileManager.default.removeItem(at: package.sourceFolder)
|
||||
} catch {
|
||||
log.error("unable to delete temp files: \(error)")
|
||||
completionHandler(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -37,7 +37,7 @@ public class ResourceDownloadStatusToolbar: UIToolbar {
|
|||
}
|
||||
|
||||
private func setup() {
|
||||
barTintColor = UIColor(red: 0.5, green: 0.75, blue: 0.25, alpha: 0.9)
|
||||
barTintColor = Colors.statusToolbar
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -78,7 +78,7 @@ public class ResourceDownloadStatusToolbar: UIToolbar {
|
|||
button.frame = CGRect(x: frame.origin.x, y: frame.origin.y,
|
||||
width: frame.width * 0.95, height: frame.height * 0.7)
|
||||
button.center = CGPoint(x: frame.width / 2, y: frame.height / 2)
|
||||
button.tintColor = UIColor(red: 0.75, green: 1.0, blue: 0.5, alpha: 1.0)
|
||||
button.tintColor = Colors.statusResourceUpdateButton
|
||||
button.setTitleColor(UIColor.white, for: .normal)
|
||||
button.setTitle(text, for: .normal)
|
||||
button.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
|
||||
|
|
|
|||
|
|
@ -204,8 +204,13 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
|
|||
cell = reusedCell
|
||||
} else {
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0,
|
||||
blue: 34.0 / 255.0, alpha: 1.0)
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
selectionColor.backgroundColor = UIColor(named: "SelectionPrimary")
|
||||
} else {
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
}
|
||||
|
||||
if keyboards.count < 2 {
|
||||
cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
} else {
|
||||
|
|
@ -393,7 +398,7 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
|
|||
view.isUserInteractionEnabled = false
|
||||
let indicatorView = UIActivityIndicatorView(style: .whiteLarge)
|
||||
let activityView = UIView(frame: indicatorView.bounds.insetBy(dx: -10.0, dy: -10.0))
|
||||
activityView.backgroundColor = UIColor(white: 0.5, alpha: 0.8)
|
||||
activityView.backgroundColor = Colors.spinnerBackground
|
||||
activityView.layer.cornerRadius = 6.0
|
||||
activityView.center = view.center
|
||||
activityView.tag = activityViewTag
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class LanguageSettingsViewController: UITableViewController {
|
|||
}
|
||||
}
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 95.0 / 255.0, green: 196.0 / 255.0, blue: 217.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionPrimary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
cell.textLabel?.font = cell.textLabel?.font?.withSize(16.0)
|
||||
return cell
|
||||
|
|
|
|||
|
|
@ -27,11 +27,12 @@ open class SettingsViewController: UITableViewController {
|
|||
action: #selector(self.doneClicked))
|
||||
navigationItem.leftBarButtonItem = doneButton
|
||||
|
||||
navigationController?.toolbar?.barTintColor = UIColor(red: 0.5, green: 0.75,
|
||||
blue: 0.25, alpha: 0.9)
|
||||
navigationController?.toolbar?.barTintColor = Colors.statusToolbar
|
||||
}
|
||||
|
||||
@objc func doneClicked(_ sender: Any) {
|
||||
// While the called method might should be renamed, it does the job well enough.
|
||||
// This resets KMW so that any new and/or updated resources can be properly loaded.
|
||||
Manager.shared.dismissKeyboardPicker(self)
|
||||
}
|
||||
|
||||
|
|
@ -70,6 +71,16 @@ open class SettingsViewController: UITableViewController {
|
|||
"subtitle": "",
|
||||
"reuseid" : "showgetstarted"
|
||||
])
|
||||
|
||||
// The iOS Files app is only available with 11.0+.
|
||||
if #available(iOS 11.0, *) {
|
||||
itemsArray.append([
|
||||
"title": "Install From File",
|
||||
"subtitle": "Browse for .kmp files",
|
||||
"reuseid" : "installfile"
|
||||
])
|
||||
}
|
||||
|
||||
_ = view
|
||||
}
|
||||
|
||||
|
|
@ -85,8 +96,7 @@ open class SettingsViewController: UITableViewController {
|
|||
}
|
||||
|
||||
override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
// #warning Incomplete implementation, return the number of rows
|
||||
return 3
|
||||
return itemsArray.count
|
||||
}
|
||||
|
||||
public func frameAtRightOfCell(cell cellFrame: CGRect, controlSize: CGSize) -> CGRect {
|
||||
|
|
@ -149,6 +159,8 @@ open class SettingsViewController: UITableViewController {
|
|||
showAgainSwitch.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true
|
||||
showAgainSwitch.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true
|
||||
}
|
||||
case "installfile":
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
default:
|
||||
log.error("unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")")
|
||||
cell.accessoryType = .none
|
||||
|
|
@ -188,6 +200,8 @@ open class SettingsViewController: UITableViewController {
|
|||
|
||||
if indexPath.row == 0 {
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
} else if indexPath.row == 3 {
|
||||
cell.accessoryType = .disclosureIndicator
|
||||
} else {
|
||||
cell.textLabel?.isEnabled = true
|
||||
cell.detailTextLabel?.isEnabled = false
|
||||
|
|
@ -208,7 +222,18 @@ open class SettingsViewController: UITableViewController {
|
|||
private func performAction(for indexPath: IndexPath) {
|
||||
switch indexPath.section {
|
||||
case 0:
|
||||
showLanguages()
|
||||
switch indexPath.row {
|
||||
case 0:
|
||||
showLanguages()
|
||||
case 3:
|
||||
if let block = Manager.shared.fileBrowserLauncher {
|
||||
block(navigationController!)
|
||||
} else {
|
||||
log.info("Listener for framework signal to launch file browser is missing")
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,9 @@ class SubKeysView: UIView {
|
|||
private var rows: Int = 0
|
||||
|
||||
private let scale = UIScreen.main.scale
|
||||
private let bgColor = UIColor.white
|
||||
private let bgColor2 = UIColor.white
|
||||
private let borderColor = UIColor(red: 145.0 / 255.0, green: 148.0 / 255.0, blue: 152.0 / 255.0, alpha: 1.0)
|
||||
private let bgColor = Colors.keyboardBackground
|
||||
private let bgColor2 = Colors.keyboardBackground
|
||||
private let borderColor = Colors.popupBorder
|
||||
|
||||
let containerView: UIView
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0xFD",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0xFE",
|
||||
"green" : "0xFD"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0x3D",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0x3E",
|
||||
"green" : "0x3D"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.000",
|
||||
"green" : "0.000"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "1.000",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "1.000",
|
||||
"green" : "1.000"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
},
|
||||
"colors" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.824",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.863",
|
||||
"green" : "0.839"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "srgb",
|
||||
"components" : {
|
||||
"red" : "0.059",
|
||||
"alpha" : "1.000",
|
||||
"blue" : "0.098",
|
||||
"green" : "0.075"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@
|
|||
<key>teamID</key>
|
||||
<string>3YE4W86L3G</string>
|
||||
<key>signingCertificate</key>
|
||||
<string>B5388B19BC7040BB9CCBFBA1ED7B685D5733CF7C</string>
|
||||
<string>75E268DBF9EA1618EBC047C82F279E9D027A132F</string>
|
||||
<key>installerSigningCertificate</key>
|
||||
<string>B5388B19BC7040BB9CCBFBA1ED7B685D5733CF7C</string>
|
||||
<string>75E268DBF9EA1618EBC047C82F279E9D027A132F</string>
|
||||
<key>provisioningProfiles</key>
|
||||
<dict>
|
||||
<key>Tavultesoft.Keyman</key>
|
||||
|
|
|
|||
|
|
@ -2,9 +2,15 @@
|
|||
|
||||
## 13.0 alpha
|
||||
* Start version 13.0
|
||||
* Adds file browsing for installable KMPs and makes KMPs for resources installed this way available to the Files app (#2457)
|
||||
* Adds support for iOS 13.0's dark mode feature
|
||||
* Testing for upcoming patch to stable:
|
||||
* Fixes for deprecated code, improving maintainability (#2282)
|
||||
|
||||
## 2019-12-09 12.0.62 stable
|
||||
* Bug Fix: Mismatch for BCP 47 codes caused keyboard downloads to fail (#2403)
|
||||
* Bug Fix: If malformed data was returned from an online API, Keyman could crash (#2368)
|
||||
|
||||
## 2019-09-20 12.0.39 beta
|
||||
* App info / help now points to the equivalent pages on help.keyman.com in a multi-page format (#2088)
|
||||
* Offline help now uses a mirrored copy of help.keyman.com and is also multi-page (#2102)
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -134,7 +134,9 @@
|
|||
CE2B1E4C21B6112B007D092E /* DeviceKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE2B1E4B21B6112B007D092E /* DeviceKit.framework */; };
|
||||
CE6138011FB99538009D0EF2 /* KeymanEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE6137FF1FB99538009D0EF2 /* KeymanEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
CE6138021FB999C8009D0EF2 /* KeymanEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6138031FB999C8009D0EF2 /* KeymanEngine.framework */; };
|
||||
CE79CDB52370111200010C06 /* Themes+Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = CE79CDB42370111200010C06 /* Themes+Colors.xcassets */; };
|
||||
CE7C1AE2236925D800100C2C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE7C1AE1236925D800100C2C /* LaunchScreen.storyboard */; };
|
||||
CE7FF1F0239A0293007859D9 /* PackageBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
|
|
@ -297,7 +299,10 @@
|
|||
CE2B1E4B21B6112B007D092E /* DeviceKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = DeviceKit.framework; path = ../../Carthage/Build/iOS/DeviceKit.framework; sourceTree = "<group>"; };
|
||||
CE6137FF1FB99538009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE6138031FB999C8009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
CE79CDB42370111200010C06 /* Themes+Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Themes+Colors.xcassets"; sourceTree = "<group>"; };
|
||||
CE7A26D523CEEB8D0005955C /* UIColors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = UIColors.swift; path = ../../../engine/KMEI/KeymanEngine/Classes/UIColors.swift; sourceTree = "<group>"; };
|
||||
CE7C1AE1236925D800100C2C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageBrowserViewController.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
|
@ -511,6 +516,8 @@
|
|||
162E2C9420926C8600F40769 /* Classes */,
|
||||
9845A7BE1A439A9200544E2E /* GetStartedViewController */,
|
||||
98C893F119F0811A000B9AC8 /* Images.xcassets */,
|
||||
CE79CDB42370111200010C06 /* Themes+Colors.xcassets */,
|
||||
CE7A26D523CEEB8D0005955C /* UIColors.swift */,
|
||||
9845A7C21A439A9200544E2E /* InfoViewController */,
|
||||
984FA1E21974B06C0037EE5D /* Keyman.entitlements */,
|
||||
9845A7B71A4398B200544E2E /* DropDownList */,
|
||||
|
|
@ -521,6 +528,7 @@
|
|||
9845A7C71A439A9200544E2E /* SetUpViewController */,
|
||||
98ABADBA176935E400B62590 /* Supporting Files */,
|
||||
C0FF769D1F5D4ECB00BD23C3 /* ActivityItemProvider.swift */,
|
||||
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */,
|
||||
C0FF769C1F5D4ECA00BD23C3 /* Keyman-Bridging-Header.h */,
|
||||
C0E943F71F61234C00E7D98C /* UIImage+Helpers.swift */,
|
||||
C0E943F91F6124E100E7D98C /* AppDelegate.swift */,
|
||||
|
|
@ -825,6 +833,7 @@
|
|||
9845A8D91A439F1000544E2E /* UIButtonBarStop@2x.png in Resources */,
|
||||
9845A8D31A439F1000544E2E /* 786-browser@2x.png in Resources */,
|
||||
981AFACF19EF44DE006706BF /* textsize_selected@2x.png in Resources */,
|
||||
CE79CDB52370111200010C06 /* Themes+Colors.xcassets in Resources */,
|
||||
CE1F5ECE23331DA400141F3E /* OfflineHelp.bundle in Resources */,
|
||||
981AFAD319EF44DE006706BF /* navbar-Landscape-568h@2x.png in Resources */,
|
||||
9845A8CC1A439F1000544E2E /* 715-globe-toolbar.png in Resources */,
|
||||
|
|
@ -924,6 +933,7 @@
|
|||
C055F0B11F60E8D400140735 /* GetStartedViewController.swift in Sources */,
|
||||
C055F0B51F610FB200140735 /* DropDownListView.swift in Sources */,
|
||||
C059FCC01FD927EF00BD1A64 /* Log.swift in Sources */,
|
||||
CE7FF1F0239A0293007859D9 /* PackageBrowserViewController.swift in Sources */,
|
||||
C0E943F81F61234C00E7D98C /* UIImage+Helpers.swift in Sources */,
|
||||
C0E943FE1F61377900E7D98C /* MainViewController.swift in Sources */,
|
||||
162E2C9920926C8600F40769 /* UIView+Extensions.swift in Sources */,
|
||||
|
|
|
|||
|
|
@ -22,17 +22,27 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
|
||||
func application(_ app: UIApplication, open url: URL,
|
||||
options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool {
|
||||
// .kmp package install, Keyman 10 onwards
|
||||
var destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
destinationUrl.appendPathComponent("\(url.lastPathComponent).zip")
|
||||
do {
|
||||
try FileManager.default.copyItem(at: url, to: destinationUrl)
|
||||
installAdhocKeyboard(url: destinationUrl)
|
||||
return true
|
||||
} catch {
|
||||
showKMPError(KMPError.copyFiles)
|
||||
// We really should validate that it is a .kmp first... but the app doesn't yet
|
||||
// process URL links, so it's fine for now. (Will change with QR code stuff.)
|
||||
|
||||
let rfm = ResourceFileManager.shared
|
||||
guard let destinationUrl = rfm.importFile(url) else {
|
||||
return false
|
||||
}
|
||||
|
||||
if let vc = window?.rootViewController {
|
||||
rfm.prepareKMPInstall(from: destinationUrl,
|
||||
alertHost: vc,
|
||||
completionHandler: { package in
|
||||
// We choose to prompt the user for comfirmation, rather
|
||||
// than automatically installing the package.
|
||||
rfm.promptPackageInstall(of: package, in: vc)
|
||||
})
|
||||
} else {
|
||||
log.error("Cannot find app's root UIViewController")
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication,
|
||||
|
|
@ -70,13 +80,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
return true
|
||||
}
|
||||
|
||||
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
|
||||
NotificationCenter.default.post(name: launchedFromUrlNotification, object: self,
|
||||
userInfo: [urlKey: url]
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
_overlayWindow = nil
|
||||
FontManager.shared.unregisterCustomFonts()
|
||||
|
|
@ -113,103 +116,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||
return _overlayWindow!
|
||||
}
|
||||
|
||||
public func installAdhocKeyboard(url: URL) {
|
||||
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
|
||||
var destination = documentsDirectory
|
||||
destination.appendPathComponent("temp/\(url.lastPathComponent)")
|
||||
|
||||
KeymanPackage.extract(fileUrl: url, destination: destination, complete: { kmp in
|
||||
if let kmp = kmp {
|
||||
self.promptAdHocInstall(kmp)
|
||||
} else {
|
||||
self.showKMPError(KMPError.invalidPackage)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public func showKMPError(_ error: KMPError) {
|
||||
showSimpleAlert(title: "Error", message: error.rawValue)
|
||||
}
|
||||
|
||||
public func showSimpleAlert(title: String, message: String) {
|
||||
let alertController = UIAlertController(title: title, message: message,
|
||||
preferredStyle: UIAlertController.Style.alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK",
|
||||
style: UIAlertAction.Style.default,
|
||||
handler: nil))
|
||||
|
||||
self.window?.rootViewController?.present(alertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
public func promptAdHocInstall(_ kmp: KeymanPackage) {
|
||||
_adhocDirectory = kmp.sourceFolder
|
||||
let isKbd = kmp.isKeyboard()
|
||||
|
||||
let vc = UIViewController()
|
||||
vc.view.backgroundColor = .red
|
||||
let wkWebView = WKWebView.init(frame: vc.view.frame)
|
||||
wkWebView.backgroundColor = .white
|
||||
vc.view.addSubview(wkWebView)
|
||||
let cancelBtn = UIBarButtonItem(title: "Cancel", style: .plain,
|
||||
target: self,
|
||||
action: #selector(cancelAdHocBtnHandler))
|
||||
let installBtn = UIBarButtonItem(title: "Install", style: .plain,
|
||||
target: self,
|
||||
action: (isKbd ? #selector(installAdHocKeyboardBtnHandler) :
|
||||
#selector(installAdHocLexicalModelBtnHandler)) )
|
||||
vc.navigationItem.leftBarButtonItem = cancelBtn
|
||||
vc.navigationItem.rightBarButtonItem = installBtn
|
||||
let nvc = UINavigationController.init(rootViewController: vc)
|
||||
|
||||
self.window?.rootViewController?.present(nvc, animated: true, completion: {
|
||||
wkWebView.loadHTMLString(kmp.infoHtml(), baseURL: nil)
|
||||
})
|
||||
}
|
||||
|
||||
@objc func installAdHocKeyboardBtnHandler() {
|
||||
if let adhocDir = _adhocDirectory {
|
||||
self.window?.rootViewController?.dismiss(animated: true, completion: {
|
||||
do {
|
||||
try Manager.shared.parseKbdKMP(adhocDir)
|
||||
self.showSimpleAlert(title: "Success", message: "Installed successfully.")
|
||||
} catch {
|
||||
self.showKMPError(error as! KMPError)
|
||||
}
|
||||
|
||||
//this can fail gracefully and not show errors to users
|
||||
do {
|
||||
try FileManager.default.removeItem(at: adhocDir)
|
||||
} catch {
|
||||
log.error("unable to delete temp files")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func installAdHocLexicalModelBtnHandler() {
|
||||
if let adhocDir = _adhocDirectory {
|
||||
self.window?.rootViewController?.dismiss(animated: true, completion: {
|
||||
do {
|
||||
try Manager.parseLMKMP(adhocDir)
|
||||
self.showSimpleAlert(title: "Success", message: "Installed successfully.")
|
||||
} catch {
|
||||
self.showKMPError(error as! KMPError)
|
||||
}
|
||||
|
||||
//this can fail gracefully and not show errors to users
|
||||
do {
|
||||
try FileManager.default.removeItem(at: adhocDir)
|
||||
} catch {
|
||||
log.error("unable to delete temp files")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@objc func cancelAdHocBtnHandler() {
|
||||
self.window?.rootViewController?.dismiss(animated: true, completion: nil)
|
||||
}
|
||||
|
||||
@objc func registerCustomFonts() {
|
||||
FontManager.shared.registerCustomFonts()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class KMNavigationBarBackgroundView: UIView {
|
|||
}
|
||||
|
||||
func setupLogo() {
|
||||
let image = UIImage(named: "keyman_logo")
|
||||
let image = UIImage(named: "Logo")
|
||||
let imageView = UIImageView(image: image)
|
||||
imageView.contentMode = .scaleAspectFit
|
||||
imageView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import KeymanEngine
|
||||
|
||||
class DropDownListView: UIView {
|
||||
init(listItems items: [UIBarButtonItem], itemSize size: CGSize, position pos: CGPoint) {
|
||||
|
|
@ -30,8 +31,8 @@ class DropDownListView: UIView {
|
|||
|
||||
for (index, item) in items.enumerated() {
|
||||
let button = UIButton(type: .custom)
|
||||
button.setTitleColor(UIColor.lightGray, for: .normal)
|
||||
button.setTitleColor(UIColor.darkGray, for: .highlighted)
|
||||
button.setTitleColor(Colors.labelNormal, for: .normal)
|
||||
button.setTitleColor(Colors.labelHighlighted, for: .highlighted)
|
||||
button.frame = CGRect(x: x, y: y, width: w, height: h)
|
||||
button.setTitle(item.title, for: .normal)
|
||||
addSubview(button)
|
||||
|
|
@ -56,7 +57,7 @@ class DropDownListView: UIView {
|
|||
|
||||
if index < count - 1 {
|
||||
let seperator = UIView(frame: CGRect(x: x + 1, y: y + h, width: w - 2, height: 1))
|
||||
seperator.backgroundColor = UIColor.lightGray
|
||||
seperator.backgroundColor = Colors.listSeparator
|
||||
addSubview(seperator)
|
||||
}
|
||||
y += h + 1
|
||||
|
|
|
|||
|
|
@ -7,17 +7,18 @@
|
|||
//
|
||||
|
||||
import UIKit
|
||||
import KeymanEngine // Defines useful color constants
|
||||
|
||||
class DropDownView: UIView {
|
||||
let strokeWidth: CGFloat = 2.0
|
||||
let borderRadius: CGFloat = 5.0
|
||||
let arrowWidth: CGFloat = 21.0
|
||||
let arrowHeight: CGFloat = 7.0
|
||||
private let borderColor = UIColor.lightGray
|
||||
private let borderColor = Colors.listSeparator
|
||||
|
||||
private var _arrowPosX: CGFloat
|
||||
private var bgColor = UIColor(white: 1.0, alpha: 1.0)
|
||||
var backgroundColor2 = UIColor(white: 1.0, alpha: 1.0)
|
||||
private var bgColor = Colors.systemBackground
|
||||
var backgroundColor2 = Colors.systemBackground
|
||||
|
||||
override init(frame: CGRect) {
|
||||
_arrowPosX = frame.width / 2.0
|
||||
|
|
|
|||
|
|
@ -68,14 +68,7 @@ class GetStartedViewController: UIViewController, UITableViewDelegate, UITableVi
|
|||
footer.addSubview(label)
|
||||
footer.addSubview(dontShowAgainSwitch)
|
||||
footer.addSubview(line)
|
||||
|
||||
if #available(iOS 13.0, *) {
|
||||
// Allows "dark mode" adjustment
|
||||
footer.backgroundColor = UIColor.systemBackground
|
||||
} else {
|
||||
// Default "light mode" styling.
|
||||
footer.backgroundColor = UIColor(white: 1.0, alpha: 1.0)
|
||||
}
|
||||
footer.backgroundColor = Colors.systemBackground
|
||||
|
||||
tableView.tableHeaderView = header
|
||||
tableView.tableFooterView = footer
|
||||
|
|
@ -100,7 +93,7 @@ class GetStartedViewController: UIViewController, UITableViewDelegate, UITableVi
|
|||
|
||||
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
||||
let selectionColor = UIView()
|
||||
selectionColor.backgroundColor = UIColor(red: 95.0 / 255.0, green: 196.0 / 255.0, blue: 217.0 / 255.0, alpha: 1.0)
|
||||
selectionColor.backgroundColor = Colors.selectionSecondary
|
||||
cell.selectedBackgroundView = selectionColor
|
||||
cell.textLabel?.font = cell.textLabel?.font?.withSize(12.0)
|
||||
cell.detailTextLabel?.font = cell.detailTextLabel?.font?.withSize(10.0)
|
||||
|
|
|
|||
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowser.imageset/786-browser-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowser.imageset/786-browser.png
vendored
Normal file
|
After Width: | Height: | Size: 232 B |
22
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowser.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "786-browser.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "786-browser-dark.png",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowserSelected.imageset/786-browser-selected-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowserSelected.imageset/786-browser-selected.png
vendored
Normal file
|
After Width: | Height: | Size: 230 B |
22
ios/keyman/Keyman/Keyman/Images.xcassets/IconBrowserSelected.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "786-browser-selected.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "786-browser-selected-dark.png",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconInfo.imageset/724-info-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconInfo.imageset/724-info.png
vendored
Normal file
|
After Width: | Height: | Size: 920 B |
22
ios/keyman/Keyman/Keyman/Images.xcassets/IconInfo.imageset/Contents.json
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "724-info.png"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "724-info-dark.png",
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
ios/keyman/Keyman/Keyman/Images.xcassets/IconInfoSelected.imageset/724-info-selected-dark.png
vendored
Normal file
|
After Width: | Height: | Size: 1.8 KiB |