Merge branch 'master' into fix/developer/2380-data-loss-with-unicode-in-ansi-document

This commit is contained in:
Marc Durdin 2020-01-20 07:07:57 +11:00 • committed by GitHub
commit bfd77ebf63
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
270 changed files with 11143 additions and 1251 deletions

3
.gitignore vendored
View file

@ -241,6 +241,9 @@
# VS Code
.vscode
# IDE files
**/.idea/**/*.xml
**/.idea/**/*.iml
.DS_Store

View file

@ -20,5 +20,6 @@ allprojects {
google()
jcenter()
mavenCentral()
maven { url "https://jitpack.io" }
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -9,6 +9,7 @@ public interface LanguageResource {
String getLanguageName();
String getVersion();
String getPackage();
String getCustomHelpLink();
Bundle buildDownloadBundle();
}

View file

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

View file

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

View file

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

View file

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

View file

@ -10,4 +10,5 @@
<include layout="@layout/list_layout" />
<include layout="@layout/qr_layout" />
</LinearLayout>

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

View file

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

View file

@ -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&#8230;</string>

View file

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

View file

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

View file

@ -14,5 +14,6 @@ allprojects {
repositories {
google()
jcenter()
maven { url "https://jitpack.io" }
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -70,3 +70,4 @@ typings/
# Intentional JavaScript files.
!testing/**/*.js
!unit_tests/**/*.js
!polyfills/**/*.js

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -168,8 +168,8 @@ describe('LMLayerWorker', function() {
sinon.assert.calledWithMatch(fakePostMessage, {
message: 'ready',
configuration: {
leftContextCodeUnits: maxCodeUnits,
rightContextCodeUnits: 0,
leftContextCodePoints: maxCodeUnits,
rightContextCodePoints: 0,
}
});
});

View file

@ -25,7 +25,7 @@ _.createMessageEventWithData = function createMessageEventWithData(data) {
*/
_.capabilities = function capabilities() {
return {
maxLeftContextCodeUnits: 64
maxLeftContextCodePoints: 64
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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** &lt;anna@addaleax.net&gt; (she/her)
* [apapirovski](https://github.com/apapirovski) -
**Anatoli Papirovski** &lt;apapirovski@mac.com&gt; (he/him)
* [BethGriggs](https://github.com/BethGriggs) -
**Beth Griggs** &lt;Bethany.Griggs@uk.ibm.com&gt; (she/her)
* [ChALkeR](https://github.com/ChALkeR) -
**Сковорода Никита Андреевич** &lt;chalkerx@gmail.com&gt; (he/him)
* [cjihrig](https://github.com/cjihrig) -
@ -183,10 +168,10 @@ For information about the governance of the Node.js project, see
**Jeremiah Senkpiel** &lt;fishrock123@rocketmail.com&gt;
* [gabrielschulhof](https://github.com/gabrielschulhof) -
**Gabriel Schulhof** &lt;gabriel.schulhof@intel.com&gt;
* [jasnell](https://github.com/jasnell) -
**James M Snell** &lt;jasnell@gmail.com&gt; (he/him)
* [gireeshpunathil](https://github.com/gireeshpunathil) -
**Gireesh Punathil** &lt;gpunathi@in.ibm.com&gt; (he/him)
* [jasnell](https://github.com/jasnell) -
**James M Snell** &lt;jasnell@gmail.com&gt; (he/him)
* [joyeecheung](https://github.com/joyeecheung) -
**Joyee Cheung** &lt;joyeec9h3@gmail.com&gt; (she/her)
* [mcollina](https://github.com/mcollina) -
@ -195,14 +180,14 @@ For information about the governance of the Node.js project, see
**Michael Dawson** &lt;michael_dawson@ca.ibm.com&gt; (he/him)
* [MylesBorins](https://github.com/MylesBorins) -
**Myles Borins** &lt;myles.borins@gmail.com&gt; (he/him)
* [ofrobots](https://github.com/ofrobots) -
**Ali Ijaz Sheikh** &lt;ofrobots@google.com&gt; (he/him)
* [rvagg](https://github.com/rvagg) -
**Rod Vagg** &lt;rod@vagg.org&gt;
* [sam-github](https://github.com/sam-github) -
**Sam Roberts** &lt;vieuxtech@gmail.com&gt;
* [targos](https://github.com/targos) -
**Michaël Zasso** &lt;targos@protonmail.com&gt; (he/him)
* [thefourtheye](https://github.com/thefourtheye) -
**Sakthipriyan Vairamani** &lt;thechargingvolcano@gmail.com&gt; (he/him)
* [tniessen](https://github.com/tniessen) -
**Tobias Nießen** &lt;tniessen@tnie.de&gt;
* [Trott](https://github.com/Trott) -
**Rich Trott** &lt;rtrott@gmail.com&gt; (he/him)
@ -226,10 +211,14 @@ For information about the governance of the Node.js project, see
**Brian White** &lt;mscdex@mscdex.net&gt;
* [nebrius](https://github.com/nebrius) -
**Bryan Hughes** &lt;bryan@nebri.us&gt;
* [ofrobots](https://github.com/ofrobots) -
**Ali Ijaz Sheikh** &lt;ofrobots@google.com&gt; (he/him)
* [orangemocha](https://github.com/orangemocha) -
**Alexis Campailla** &lt;orangemocha@nodejs.org&gt;
* [piscisaureus](https://github.com/piscisaureus) -
**Bert Belder** &lt;bertbelder@gmail.com&gt;
* [rvagg](https://github.com/rvagg) -
**Rod Vagg** &lt;r@va.gg&gt;
* [shigeki](https://github.com/shigeki) -
**Shigeki Ohtsu** &lt;ohtsu@ohtsu.org&gt; (he/him)
* [TimothyGu](https://github.com/TimothyGu) -
@ -243,12 +232,8 @@ For information about the governance of the Node.js project, see
**Anna Henningsen** &lt;anna@addaleax.net&gt; (she/her)
* [ak239](https://github.com/ak239) -
**Aleksei Koziatinskii** &lt;ak239spb@gmail.com&gt;
* [andrasq](https://github.com/andrasq) -
**Andras** &lt;andras@kinvey.com&gt;
* [AndreasMadsen](https://github.com/AndreasMadsen) -
**Andreas Madsen** &lt;amwebdk@gmail.com&gt; (he/him)
* [AnnaMag](https://github.com/AnnaMag) -
**Anna M. Kedzierska** &lt;anna.m.kedzierska@gmail.com&gt;
* [antsmartian](https://github.com/antsmartian) -
**Anto Aravinth** &lt;anto.aravinth.cse@gmail.com&gt; (he/him)
* [apapirovski](https://github.com/apapirovski) -
@ -279,10 +264,10 @@ For information about the governance of the Node.js project, see
**Bartosz Sosnowski** &lt;bartosz@janeasystems.com&gt;
* [calvinmetcalf](https://github.com/calvinmetcalf) -
**Calvin Metcalf** &lt;calvin.metcalf@gmail.com&gt;
* [cclauss](https://github.com/cclauss) -
**Christian Clauss** &lt;cclauss@me.com&gt; (he/him)
* [ChALkeR](https://github.com/ChALkeR) -
**Сковорода Никита Андреевич** &lt;chalkerx@gmail.com&gt; (he/him)
* [chrisdickinson](https://github.com/chrisdickinson) -
**Chris Dickinson** &lt;christopher.s.dickinson@gmail.com&gt;
* [cjihrig](https://github.com/cjihrig) -
**Colin Ihrig** &lt;cjihrig@gmail.com&gt; (he/him)
* [claudiorodriguez](https://github.com/claudiorodriguez) -
@ -295,6 +280,8 @@ For information about the governance of the Node.js project, see
**David Cai** &lt;davidcai1993@yahoo.com&gt; (he/him)
* [davisjam](https://github.com/davisjam) -
**Jamie Davis** &lt;davisjam@vt.edu&gt; (he/him)
* [devnexen](https://github.com/devnexen) -
**David Carlier** &lt;devnexen@gmail.com&gt;
* [devsnek](https://github.com/devsnek) -
**Gus Caplan** &lt;me@gus.host&gt; (he/him)
* [digitalinfinity](https://github.com/digitalinfinity) -
@ -303,16 +290,12 @@ For information about the governance of the Node.js project, see
**Adrian Estrada** &lt;edsadr@gmail.com&gt; (he/him)
* [eljefedelrodeodeljefe](https://github.com/eljefedelrodeodeljefe) -
**Robert Jefe Lindstaedt** &lt;robert.lindstaedt@gmail.com&gt;
* [estliberitas](https://github.com/estliberitas) -
**Alexander Makarenko** &lt;estliberitas@gmail.com&gt;
* [eugeneo](https://github.com/eugeneo) -
**Eugene Ostroukhov** &lt;eostroukhov@google.com&gt;
* [evanlucas](https://github.com/evanlucas) -
**Evan Lucas** &lt;evanlucas@me.com&gt; (he/him)
* [fhinkel](https://github.com/fhinkel) -
**Franziska Hinkelmann** &lt;franziska.hinkelmann@gmail.com&gt; (she/her)
* [firedfox](https://github.com/firedfox) -
**Daniel Wang** &lt;wangyang0123@gmail.com&gt;
* [Fishrock123](https://github.com/Fishrock123) -
**Jeremiah Senkpiel** &lt;fishrock123@rocketmail.com&gt;
* [gabrielschulhof](https://github.com/gabrielschulhof) -
@ -321,6 +304,8 @@ For information about the governance of the Node.js project, see
**George Adams** &lt;george.adams@uk.ibm.com&gt; (he/him)
* [geek](https://github.com/geek) -
**Wyatt Preul** &lt;wpreul@gmail.com&gt;
* [gengjiawen](https://github.com/gengjiawen) -
**Jiawen Geng** &lt;technicalcute@gmail.com&gt;
* [gibfahn](https://github.com/gibfahn) -
**Gibson Fahnestock** &lt;gibfahn@gmail.com&gt; (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** &lt;bugs@bergstroem.nu&gt;
* [jdalton](https://github.com/jdalton) -
**John-David Dalton** &lt;john.david.dalton@gmail.com&gt;
* [jhamhader](https://github.com/jhamhader) -
**Yuval Brik** &lt;yuval@brik.org.il&gt;
* [jkrems](https://github.com/jkrems) -
**Jan Krems** &lt;jan.krems@gmail.com&gt; (he/him)
* [joaocgreis](https://github.com/joaocgreis) -
**João Reis** &lt;reis@janeasystems.com&gt;
* [joshgav](https://github.com/joshgav) -
**Josh Gavant** &lt;josh.gavant@outlook.com&gt;
* [joyeecheung](https://github.com/joyeecheung) -
**Joyee Cheung** &lt;joyeec9h3@gmail.com&gt; (she/her)
* [julianduque](https://github.com/julianduque) -
**Julian Duque** &lt;julianduquej@gmail.com&gt; (he/him)
* [JungMinu](https://github.com/JungMinu) -
**Minwoo Jung** &lt;minwoo@nodesource.com&gt; (he/him)
**Minwoo Jung** &lt;nodecorelab@gmail.com&gt; (he/him)
* [kfarnung](https://github.com/kfarnung) -
**Kyle Farnung** &lt;kfarnung@microsoft.com&gt; (he/him)
* [kunalspathak](https://github.com/kunalspathak) -
**Kunal Pathak** &lt;kunal.pathak@microsoft.com&gt;
* [lance](https://github.com/lance) -
**Lance Ball** &lt;lball@redhat.com&gt; (he/him)
* [legendecas](https://github.com/legendecas) -
**Chengzhong Wu** &lt;legendecas@gmail.com&gt; (he/him)
* [Leko](https://github.com/Leko) -
**Shingo Inoue** &lt;leko.noor@gmail.com&gt; (he/him)
* [lpinca](https://github.com/lpinca) -
**Luigi Pinca** &lt;luigipinca@gmail.com&gt; (he/him)
* [lucamaraschi](https://github.com/lucamaraschi) -
**Luca Maraschi** &lt;luca.maraschi@gmail.com&gt; (he/him)
* [lundibundi](https://github.com/lundibundi) -
**Denys Otrishko** &lt;shishugi@gmail.com&gt; (he/him)
* [maclover7](https://github.com/maclover7) -
@ -399,14 +378,8 @@ For information about the governance of the Node.js project, see
**Teddy Katz** &lt;teddy.katz@gmail.com&gt; (he/him)
* [ofrobots](https://github.com/ofrobots) -
**Ali Ijaz Sheikh** &lt;ofrobots@google.com&gt; (he/him)
* [orangemocha](https://github.com/orangemocha) -
**Alexis Campailla** &lt;orangemocha@nodejs.org&gt;
* [othiym23](https://github.com/othiym23) -
**Forrest L Norvell** &lt;ogd@aoaioxxysz.net&gt; (he/him)
* [oyyd](https://github.com/oyyd) -
**Ouyang Yadong** &lt;oyydoibh@gmail.com&gt; (he/him)
* [pmq20](https://github.com/pmq20) -
**Minqi Pan** &lt;pmq2001@gmail.com&gt;
* [princejwesley](https://github.com/princejwesley) -
**Prince John Wesley** &lt;princejohnwesley@gmail.com&gt;
* [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** &lt;admin@stephenbelanger.com&gt; (he/him)
* [refack](https://github.com/refack) -
**Refael Ackermann** &lt;refack@gmail.com&gt; (he/him)
**Refael Ackermann (רפאל פלחי)** &lt;refack@gmail.com&gt; (he/him/הוא/אתה)
* [richardlau](https://github.com/richardlau) -
**Richard Lau** &lt;riclau@uk.ibm.com&gt;
* [ronkorving](https://github.com/ronkorving) -
@ -447,16 +420,12 @@ For information about the governance of the Node.js project, see
**Steven R Loomis** &lt;srloomis@us.ibm.com&gt;
* [starkwang](https://github.com/starkwang) -
**Weijia Wang** &lt;starkwang@126.com&gt;
* [stefanmb](https://github.com/stefanmb) -
**Stefan Budeanu** &lt;stefan@budeanu.com&gt;
* [targos](https://github.com/targos) -
**Michaël Zasso** &lt;targos@protonmail.com&gt; (he/him)
* [thefourtheye](https://github.com/thefourtheye) -
**Sakthipriyan Vairamani** &lt;thechargingvolcano@gmail.com&gt; (he/him)
* [thekemkid](https://github.com/thekemkid) -
**Glen Keane** &lt;glenkeane.94@gmail.com&gt; (he/him)
* [thlorenz](https://github.com/thlorenz) -
**Thorsten Lorenz** &lt;thlorenz@gmx.de&gt;
* [TimothyGu](https://github.com/TimothyGu) -
**Tiancheng "Timothy" Gu** &lt;timothygu99@gmail.com&gt; (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** &lt;vlad2t@hotmail.com&gt; (he/him)
* [vkurchatkin](https://github.com/vkurchatkin) -
**Vladimir Kurchatkin** &lt;vladimir.kurchatkin@gmail.com&gt;
* [vsemozhetbyt](https://github.com/vsemozhetbyt) -
**Vse Mozhet Byt** &lt;vsemozhetbyt@gmail.com&gt; (he/him)
* [watilde](https://github.com/watilde) -
**Daijiro Wachi** &lt;daijiro.wachi@gmail.com&gt; (he/him)
* [watson](https://github.com/watson) -
**Thomas Watson** &lt;w@tson.dk&gt;
* [whitlockjc](https://github.com/whitlockjc) -
**Jeremy Whitlock** &lt;jwhitlock@apache.org&gt;
* [XadillaX](https://github.com/XadillaX) -
**Khaidi Chu** &lt;i@2333.moe&gt; (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** &lt;andras@kinvey.com&gt;
* [AnnaMag](https://github.com/AnnaMag) -
**Anna M. Kedzierska** &lt;anna.m.kedzierska@gmail.com&gt;
* [estliberitas](https://github.com/estliberitas) -
**Alexander Makarenko** &lt;estliberitas@gmail.com&gt;
* [chrisdickinson](https://github.com/chrisdickinson) -
**Chris Dickinson** &lt;christopher.s.dickinson@gmail.com&gt;
* [firedfox](https://github.com/firedfox) -
**Daniel Wang** &lt;wangyang0123@gmail.com&gt;
* [imran-iq](https://github.com/imran-iq) -
**Imran Iqbal** &lt;imran@imraniqbal.org&gt;
* [isaacs](https://github.com/isaacs) -
**Isaac Z. Schlueter** &lt;i@izs.me&gt;
* [jhamhader](https://github.com/jhamhader) -
**Yuval Brik** &lt;yuval@brik.org.il&gt;
* [joshgav](https://github.com/joshgav) -
**Josh Gavant** &lt;josh.gavant@outlook.com&gt;
* [kunalspathak](https://github.com/kunalspathak) -
**Kunal Pathak** &lt;kunal.pathak@microsoft.com&gt;
* [lucamaraschi](https://github.com/lucamaraschi) -
**Luca Maraschi** &lt;luca.maraschi@gmail.com&gt; (he/him)
* [lxe](https://github.com/lxe) -
**Aleksey Smolenchuk** &lt;lxe@lxe.co&gt;
* [matthewloring](https://github.com/matthewloring) -
@ -508,12 +491,18 @@ For information about the governance of the Node.js project, see
**Christopher Monsanto** &lt;chris@monsan.to&gt;
* [Olegas](https://github.com/Olegas) -
**Oleg Elifantiev** &lt;oleg@elifantiev.ru&gt;
* [orangemocha](https://github.com/orangemocha) -
**Alexis Campailla** &lt;orangemocha@nodejs.org&gt;
* [othiym23](https://github.com/othiym23) -
**Forrest L Norvell** &lt;ogd@aoaioxxysz.net&gt; (he/him)
* [petkaantonov](https://github.com/petkaantonov) -
**Petka Antonov** &lt;petka_antonov@hotmail.com&gt;
* [phillipj](https://github.com/phillipj) -
**Phillip Johnsen** &lt;johphi@gmail.com&gt;
* [piscisaureus](https://github.com/piscisaureus) -
**Bert Belder** &lt;bertbelder@gmail.com&gt;
* [pmq20](https://github.com/pmq20) -
**Minqi Pan** &lt;pmq2001@gmail.com&gt;
* [rlidwka](https://github.com/rlidwka) -
**Alex Kocharin** &lt;alex@kocharin.ru&gt;
* [rmg](https://github.com/rmg) -
@ -522,10 +511,18 @@ For information about the governance of the Node.js project, see
**Robert Kowalski** &lt;rok@kowalski.gd&gt;
* [romankl](https://github.com/romankl) -
**Roman Klauke** &lt;romaaan.git@gmail.com&gt;
* [stefanmb](https://github.com/stefanmb) -
**Stefan Budeanu** &lt;stefan@budeanu.com&gt;
* [tellnes](https://github.com/tellnes) -
**Christian Tellnes** &lt;christian@tellnes.no&gt;
* [thlorenz](https://github.com/thlorenz) -
**Thorsten Lorenz** &lt;thlorenz@gmx.de&gt;
* [tunniclm](https://github.com/tunniclm) -
**Mike Tunnicliffe** &lt;m.j.tunnicliffe@gmail.com&gt;
* [vsemozhetbyt](https://github.com/vsemozhetbyt) -
**Vse Mozhet Byt** &lt;vsemozhetbyt@gmail.com&gt; (he/him)
* [whitlockjc](https://github.com/whitlockjc) -
**Jeremy Whitlock** &lt;jwhitlock@apache.org&gt;
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** &lt;tjfontaine@gmail.com&gt;
`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

Binary file not shown.

View file

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

View file

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

View file

@ -0,0 +1,6 @@
const source: LexicalModelSource = {
format: 'trie-1.0',
sources: ['wordlist.tsv'],
wordBreaker: 'default',
};
export default source;

View 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.

View file

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

View file

@ -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 */,

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -581,4 +581,8 @@ public class ResourceDownloadManager {
return updateQueue
}
public func installLexicalModelPackage(at packageURL: URL) -> InstallableLexicalModel? {
return downloader.installLexicalModelPackage(downloadedPackageFile: packageURL)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -80,7 +80,7 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<false/>
<true/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

View file

@ -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 */,

View file

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

View file

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

View file

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

View file

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

View file

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 232 B

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 920 B

View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files have changed in this diff Show more