Merge pull request #4503 from keymanapp/chore/beta-master-B14S6-part-2

chore: merge B14S6 beta to alpha
This commit is contained in:
Marc Durdin 2021-02-22 20:33:23 +11:00 committed by GitHub
commit dbb6074b5f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
255 changed files with 3533 additions and 6342 deletions

View file

@ -213,6 +213,23 @@
* chore: prepare 15.0 alpha (#4129)
## 14.0.243 beta 2021-02-12
* change(ios/app): Generate offline help from markdown (#4470)
* fix(windows): tsysinfox64 not signed (#4486)
* chore(android/app): Update help formatting and images (#4485)
* fix(android/engine): Display welcome.htm help within the app (#4477)
* fix(ios): tutorial's link to "Add a Keyboard" links directly to keyboard search (#4491)
* feat(linux): Improve output of km-package-list-installed (#4481)
## 14.0.242 beta 2021-02-11
* fix(android/engine): Display online keyboard help (#4462)
* fix(windows): Track modifier changes in UWP apps (#4468)
* fix(common/resources): Fix help.keyman.com path for commit (#4469)
* fix(common): create GitHub comments serially (#4472)
* fix(linux): Fix dependencies on packages (#4464)
## 14.0.241 beta 2021-02-10
* fix(common/resources): Just use master branch for help.keyman.com (#4459)

View file

@ -1 +1 @@
15.0.17
15.0.17

View file

@ -1,9 +1,8 @@
/*
* Copyright (C) 2019 SIL International. All rights reserved.
* Copyright (C) 2019-2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmapro;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
@ -11,18 +10,14 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.app.DialogFragment;
import android.app.DownloadManager;
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;
@ -31,21 +26,14 @@ import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.tavultesoft.kmea.ConfirmDialogFragment;
import com.tavultesoft.kmea.KMHelpFileActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.cloud.CloudApiTypes;
import com.tavultesoft.kmea.cloud.CloudDownloadMgr;
import com.tavultesoft.kmea.cloud.impl.CloudKeyboardPackageDownloadCallback;
import com.tavultesoft.kmea.cloud.impl.CloudLexicalPackageDownloadCallback;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.FileProviderUtils;
import com.tavultesoft.kmea.util.HelpFile;
import com.tavultesoft.kmea.util.KMLog;
import com.tavultesoft.kmea.util.MapCompat;
import com.tavultesoft.kmea.util.QRCodeUtil;
@ -177,11 +165,11 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
} else if (itemTitle.equals(getString(R.string.help_link))) {
if (FileUtils.isWelcomeFile(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);
}
Intent i = new Intent(context, KMHelpFileActivity.class);
// Have to use package ID since we don't store the package name
i.putExtra(KMManager.KMKey_PackageID, packageID);
i.putExtra(KMManager.KMKey_CustomHelpLink, customHelpLink);
startActivity(i);
} else {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(customHelpLink));

View file

@ -18,6 +18,7 @@ import java.util.List;
import java.util.Map;
import com.tavultesoft.kmea.BaseActivity;
import com.tavultesoft.kmea.KMHelpFileActivity;
import com.tavultesoft.kmea.KMKeyboardDownloaderActivity;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMManager.KeyboardType;
@ -35,7 +36,6 @@ import com.tavultesoft.kmea.util.FileProviderUtils;
import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.DownloadFileUtils;
import com.tavultesoft.kmea.util.DownloadIntentService;
import com.tavultesoft.kmea.util.HelpFile;
import com.tavultesoft.kmea.util.KMLog;
import com.tavultesoft.kmea.util.KMPLink;
@ -967,11 +967,11 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
if (customHelpLink != null && !customHelpLink.isEmpty() &&
packageID != null && !packageID.isEmpty() && FileUtils.isWelcomeFile(customHelpLink)) {
// Display local welcome.htm help file, including associated assets
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
if (FileProviderUtils.exists(context)) {
startActivity(i);
}
Intent i = new Intent(this, KMHelpFileActivity.class);
// Have to use packageID since we don't store the package name
i.putExtra(KMManager.KMKey_PackageID, packageID);
i.putExtra(KMManager.KMKey_CustomHelpLink, customHelpLink);
startActivity(i);
}
}

View file

@ -105,8 +105,6 @@
<string name="failed_to_extract" comment="Notification that keyboard package failed to unzip">Kpekọt isan̄a isibi</string>
<!-- Context: KMP Package strings -->
<string name="install_keyboard_package" comment="Title to install keyboard package">Wop Uwot-nge</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Onu me %1$s</string>
<!-- Context: KMP Package strings -->
<string name="install_predictive_text_package" comment="Title to install dictionary package">Wop Ikpa-usem</string>
<!-- Context: KMP Package strings -->

View file

@ -105,8 +105,6 @@
<string name="failed_to_extract" comment="Notification that keyboard package failed to unzip">Entpacken fehlgeschlagen</string>
<!-- Context: KMP Package strings -->
<string name="install_keyboard_package" comment="Title to install keyboard package">Tastatur installieren</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Willkommen bei %1$s</string>
<!-- Context: KMP Package strings -->
<string name="install_predictive_text_package" comment="Title to install dictionary package">Wörterbuch installieren</string>
<!-- Context: KMP Package strings -->

View file

@ -107,8 +107,6 @@
<string name="failed_to_extract" comment="Notification that keyboard package failed to unzip">Échec de l\'extraction</string>
<!-- Context: KMP Package strings -->
<string name="install_keyboard_package" comment="Title to install keyboard package">Installer un clavier</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Bienvenue à %1$s</string>
<!-- Context: KMP Package strings -->
<string name="install_predictive_text_package" comment="Title to install dictionary package">Installez un dictionnaire</string>
<!-- Context: KMP Package strings -->

View file

@ -104,8 +104,6 @@
<string name="failed_to_extract" comment="Notification that keyboard package failed to unzip">មិន​អាច​ស្រង់​ចេញបាន​ទេ</string>
<!-- Context: KMP Package strings -->
<string name="install_keyboard_package" comment="Title to install keyboard package">ដំឡើង​ក្ដារចុច</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">ស្វាគមន៍​មកកាន់ %1$s</string>
<!-- Context: KMP Package strings -->
<string name="install_predictive_text_package" comment="Title to install dictionary package">ដំឡើង​បញ្ជី​ពាក្យ</string>
<!-- Context: KMP Package strings -->

View file

@ -174,9 +174,6 @@
<!-- Context: KMP Package strings -->
<string name="install_keyboard_package" comment="Title to install keyboard package">Install Keyboard</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Welcome to %1$s</string>
<!-- Context: KMP Package strings -->
<string name="install_predictive_text_package" comment="Title to install dictionary package">Install Dictionary</string>

View file

@ -29,6 +29,12 @@
android:label=""
android:theme="@style/Theme.AppCompat.Light.Dialog">
</activity>
<activity
android:name="com.tavultesoft.kmea.KMHelpFileActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
android:label="@string/app_name"
android:theme="@style/Theme.AppCompat.Light.NoActionBar">
</activity>
<activity
android:name="com.tavultesoft.kmea.ModelPickerActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"

View file

@ -0,0 +1,175 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.widget.Toolbar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.FileProvider;
import com.tavultesoft.kmea.packages.PackageProcessor;
import com.tavultesoft.kmea.util.FileProviderUtils;
import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.HelpFile;
import java.io.File;
import static android.app.Application.getProcessName;
/**
* Display Keyboard help file
*/
public class KMHelpFileActivity extends BaseActivity {
private static final String TAG = "KMHelpFileActivity";
private WebView webView;
private Button finishButton;
private String pkgID;
@SuppressLint({"SetJavaScriptEnabled", "InflateParams"})
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context context = this;
setContentView(R.layout.activity_help_file_layout);
Bundle bundle = getIntent().getExtras();
pkgID = "";
String url = "";
if (bundle != null) {
pkgID = bundle.getString(KMManager.KMKey_PackageID);
url = bundle.getString(KMManager.KMKey_CustomHelpLink);
}
finishButton = (Button) findViewById(R.id.finishButton);
finishButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
finish();
overridePendingTransition(0, android.R.anim.fade_out);
}
});
Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar);
ActionBar actionBar = getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(null);
actionBar.setDisplayUseLogoEnabled(false);
actionBar.setDisplayShowHomeEnabled(false);
actionBar.setDisplayShowTitleEnabled(false);
actionBar.setDisplayShowCustomEnabled(true);
}
TextView packageActivityTitle = (TextView) findViewById(R.id.bar_title);
String titleStr = String.format(getString(R.string.welcome_package), pkgID);
packageActivityTitle.setText(titleStr);
packageActivityTitle.setTextColor(getResources().getColor(android.R.color.black));
webView = (WebView) findViewById(R.id.kmHelpFileWebView);
webView.getSettings().setLayoutAlgorithm(WebSettings.LayoutAlgorithm.NORMAL);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setAllowFileAccessFromFileURLs(true);
webView.getSettings().setAllowUniversalAccessFromFileURLs(true);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setBuiltInZoomControls(true);
webView.getSettings().setSupportZoom(true);
webView.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
webView.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
}
});
webView.setWebViewClient(new WebViewClient() {
@Override
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
String lowerURL = url.toLowerCase();
if (lowerURL.equals("about:blank")) {
return true; // never load a blank page, e.g. when the component initializes
} else if (FileUtils.hasPDFExtension(url)) {
// Special case to view pdf files
url = url.replace("file://", "");
Intent i = HelpFile.toActionView(context, url, pkgID);
if (FileProviderUtils.exists(context) || KMManager.isTestMode()) {
startActivity(i);
return true;
}
}
// Display URL
return false;
}
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
}
@Override
public void onPageFinished(WebView view, String url) {
// Inject a meta viewport tag into the head of the file if it doesn't exist
webView.loadUrl(
"javascript:(function() {" +
"if(!document.querySelectorAll('meta[name=viewport]').length) {"+
"let meta=document.createElement('meta');"+
"meta.name='viewport';"+
"meta.content='width=device-width, initial-scale=1';"+
"document.head.appendChild(meta);"+
"}"+
"})()"
);
}
});
webView.loadUrl("file:///" + url);
}
@Override
protected void onResume() {
super.onResume();
if (webView != null) {
webView.reload();
}
}
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onBackPressed() {
if (webView != null && webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
}

View file

@ -1,5 +1,5 @@
/**
* Copyright (C) 2017 SIL International. All rights reserved.
* Copyright (C) 2017-2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
@ -31,9 +31,9 @@ import android.widget.Toast;
import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.util.FileProviderUtils;
import com.tavultesoft.kmea.util.FileUtils;
import com.tavultesoft.kmea.util.HelpFile;
import com.tavultesoft.kmea.util.MapCompat;
import com.tavultesoft.kmea.util.QRCodeUtil;
import com.tavultesoft.kmea.KMHelpFileActivity;
// Public access is necessary to avoid IllegalAccessException
public final class KeyboardInfoActivity extends BaseActivity {
@ -127,11 +127,11 @@ public final class KeyboardInfoActivity extends BaseActivity {
if (position == 1) {
if (FileUtils.isWelcomeFile(customHelpLink)) {
// Display local welcome.htm help file, including associated assets
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
Intent i = new Intent(context, KMHelpFileActivity.class);
i.putExtra(KMManager.KMKey_PackageID, packageID);
i.putExtra(KMManager.KMKey_CustomHelpLink, customHelpLink);
if (FileProviderUtils.exists(context)|| KMManager.isTestMode()) {
startActivity(i);
}
startActivity(i);
} else {
Intent i = new Intent(Intent.ACTION_VIEW);
i.setData(Uri.parse(customHelpLink));

View file

@ -1,22 +1,16 @@
/*
* Copyright (C) 2017 SIL International. All rights reserved.
* Copyright (C) 2017-2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.app.DialogFragment;
import android.content.Context;
import android.content.Intent;
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.Window;
import android.widget.AdapterView;
@ -24,16 +18,11 @@ import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.Toast;
import com.tavultesoft.kmea.data.CloudRepository;
import com.tavultesoft.kmea.data.Dataset;
import com.tavultesoft.kmea.data.Keyboard;
import com.tavultesoft.kmea.data.LexicalModel;
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.KMHelpFileActivity;
import static com.tavultesoft.kmea.ConfirmDialogFragment.DialogType.DIALOG_TYPE_DELETE_MODEL;
@ -150,11 +139,11 @@ public final class ModelInfoActivity extends BaseActivity {
} else if (itemTitle.equals(getString(R.string.help_link))) {
if (!customHelpLink.equals("")) {
// Display local welcome.htm help file, including associated assets
Intent i = HelpFile.toActionView(context, customHelpLink, packageID);
if (FileProviderUtils.exists(context) || KMManager.isTestMode()) {
startActivity(i);
}
Intent i = new Intent(context, KMHelpFileActivity.class);
// Have to use packageID since we don't store the package name
i.putExtra(KMManager.KMKey_PackageID, packageID);
i.putExtra(KMManager.KMKey_CustomHelpLink, customHelpLink);
startActivity(i);
} else {
// We should always have a help file packaged with models.
}

View file

@ -1,3 +1,7 @@
/**
* Copyright (C) 2021 SIL International. All rights reserved.
*/
package com.tavultesoft.kmea.util;
import android.content.ClipData;
@ -18,25 +22,18 @@ 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,
"application/pdf",
"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
* Utility to pass a pdf help file for Intent.ACTION_VIEW
* @param context
* @param helpFile Full path string of the html file to view
* @param helpFile Full path string of the pdf 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()) {
if (FileUtils.hasPDFExtension(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
@ -44,41 +41,8 @@ public final class HelpFile {
final String authority = FileProviderUtils.getAuthority(context);
Uri contentUri = FileProvider.getUriForFile(
context, authority, customHelp);
i.setDataAndType(contentUri, "text/html");
i.setDataAndType(contentUri, "application/pdf");
// 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);
// Special handling for PDF
if (FileUtils.hasPDFExtension(assetUri.toString())) {
clipData.addItem(new ClipData.Item(assetUri));
} else {
clipData.addItem(new ClipData.Item(assetUri));
}
}
// Associate assets in clipData to the intent
i.setClipData(clipData);
i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
i.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/titlebar" />
<WebView
android:id="@+id/kmHelpFileWebView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/titlebar"
android:layout_above="@+id/buttonBar" />
<LinearLayout
android:id="@+id/buttonBar"
style="?android:attr/buttonBarStyle"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- Spacer to pad buttons to the right -->
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<Button
android:id="@+id/finishButton"
style="@style/Widget.AppCompat.Button.Borderless.Colored"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/label_ok"
android:elevation="1dp"
android:singleLine="true"
android:layout_marginStart="10dp" android:layout_marginEnd="15dp">
<requestFocus/>
</Button>
</LinearLayout>
</RelativeLayout>

View file

@ -63,6 +63,8 @@
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[aya] %1$s</string>
<!-- Context: Keyboard Info and Keyboard Settings -->
<string name="keyboard_qr_code" comment="QR Code description"> Gwuku iman̄ yi kè òbọkọ\nuwot-nge yi me ofifi okwukwut</string>
<!-- Context: Keyboard Help welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Onu me %1$s</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Wèek ikpa-usem òje me lek bọkọ tap</string>
<!-- Context: Model Updates -->

View file

@ -63,6 +63,8 @@
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[neu] %1$s</string>
<!-- Context: Keyboard Info and Keyboard Settings -->
<string name="keyboard_qr_code" comment="QR Code description"> Scannen Sie diesen Code, um diese\nTastatur auf einem anderen Gerät zu laden</string>
<!-- Context: KMP Package welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Willkommen bei %1$s</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Suche nach zugehörigem Wörterbuch zum Herunterladen</string>
<!-- Context: Model Updates -->

View file

@ -63,6 +63,8 @@
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[nouveau] %1$s</string>
<!-- Context: Keyboard Info and Keyboard Settings -->
<string name="keyboard_qr_code" comment="QR Code description"> Scannez ce code pour charger ce clavier\nsur un autre appareil</string>
<!-- Context: Keyboard Help welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Bienvenue à %1$s</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Vérification du dictionnaire associé à télécharger</string>
<!-- Context: Model Updates -->

View file

@ -60,6 +60,8 @@
<string name="uninstall_keyboard" comment="Label for uninstalling keyboard">លុប​ក្ដារចុច</string>
<!-- Context: Keyboard Info and Keyboard Settings -->
<string name="keyboard_picker_new_keyboard" comment="Mark a language name that's newly installed in keyboard list">[ថ្មី] %1$s</string>
<!-- Context: Keyboard Help welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">ស្វាគមន៍​មកកាន់ %1$s</string>
<!-- Context: Keyboard Info and Keyboard Settings -->
<string name="keyboard_qr_code" comment="QR Code description"> ស្កេនកូដ​នេះដើម្បី​ផ្ទុក​ក្ដារចុចនេះ\nនៅលើឧបករណ៍ផ្សេង</string>
<!-- Context: Query for associated dictionary -->

View file

@ -102,6 +102,11 @@
<string name="keyboard_qr_code" comment="QR Code description">
Scan this code to load this\nkeyboard on another device</string>
<!-- Context: Keyboard Help welcome.htm title -->
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Welcome to %1$s</string>
<!-- Context: Query for associated dictionary -->
<string name="query_associated_model" comment="Check if there's an available dictionary to download">Checking for associated dictionary to download</string>

View file

@ -9,6 +9,7 @@ import android.widget.ListView;
import androidx.test.core.app.ApplicationProvider;
import com.tavultesoft.kmea.KMManager;
import com.tavultesoft.kmea.KMHelpFileActivity;
import com.tavultesoft.kmea.KeyboardInfoActivity;
import com.tavultesoft.kmea.KeyboardPickerActivity;
import com.tavultesoft.kmea.R;
@ -183,7 +184,7 @@ public class KeyboardPickerTest {
// check result intent to be a weblink to open
Intent actual2 = Shadows.shadowOf(_info).getNextStartedActivity();
Assert.assertNotNull(actual2);
Assert.assertEquals(actual2.getAction(),Intent.ACTION_VIEW);
Assert.assertEquals("com.tavultesoft.kmea.KMHelpFileActivity", actual2.getComponent().getClassName());
}
finally {
if(_controller2!=null)

View file

@ -4,6 +4,7 @@
* Android Studio 4.1+
* Java SE Development Kit 8
* [Node.js](https://nodejs.org/) 8.9+ (for building KeymanWeb)
* [Pandoc](#Install-Pandoc) for generating offline help
## Install Java
It is recommended to use openJDK because of oracle license issues.

View file

@ -71,7 +71,7 @@ displayInfo "" \
cd $KEYMAN_ROOT/android/help
MDLUA="$THIS_DIR/htmlink.lua"
MDLUA="$KEYMAN_ROOT/resources/build/htmlink.lua"
MD=`find -name "*.md"`
DESTHTM="$THIS_DIR/KMAPro/kMAPro/src/main/assets/info"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View file

@ -42,7 +42,7 @@ This will be displayed at the confirmation to install the keyboard package.
If "readme.htm" is not included, a generic page with the package ID and package version will be shown. Click the
**"Install"** button to install the entire keyboard package.
![](../android_images/dist-readme-ap.png)
![](../android_images/khmer-readme-a.png)
Step 5) All the keyboards in the package are installed as a group. In this example, the package only has the
"Khmer Angkor" keyboard, so it becomes the active keyboard. For custom dictionary packages, only one dictionary
@ -51,7 +51,7 @@ will be installed.
If the keyboard package includes Welcome documentation, it will be displayed after the keyboard package is installed.
This documentation helps you to get started using the keyboard.
![](../android_images/dist-welcome-ap.png)
![](../android_images/khmer-welcome-a.png)
Click the **"OK"** button to finish installing the keyboard. You should be able to select it from the Keyman globe
and start typing with it in any app.

View file

@ -2,7 +2,7 @@
title: Keyman for Android (Phone Layout)
---
| | Keyboard Keys (Keyboard Present) |
| | Keyboard Keys <br/>(Keyboard Present) |
|---|---|
| ![](../android_images/globe-ap.png) | Select another language/keyboard |
| ![](../android_images/hide-keyboard-ap.png) | Hide the keyboard |
@ -11,6 +11,11 @@ title: Keyman for Android (Phone Layout)
| ![](../android_images/shift-ap.png) | Shift key. Long press this key to access the <kbd>CTRL</kbd>, <kbd>ALT</kbd> and <kbd>CTRL</kbd><kbd>ALT</kbd> keys (which can access additional key layers) |
| ![](../android_images/touch-hold-ap.png) | Keys with a small dot in the top right corner indicate a long press key. Access further functionality by long pressing the key |
| ![](../android_images/share-a.png) | Share your text with other apps like Messages, Gmail, or Twitter. Some versions of Android also let you copy your text to the clipboard. |
----
| | Toolbar Icons
|---|---|
| ![](../android_images/browser-a.png) | Open the Keyman Browser to use the web in your language |
| ![](../android_images/menu-icon-a.png) | Open the menu for additional options |
| ![](../android_images/font-size-a.png) | Adjust the font size |

View file

@ -2,7 +2,7 @@
title: Keyman for Android (Tablet Layout)
---
| | Keyboard Keys (Keyboard Present) |
| | Keyboard Keys <br/>(Keyboard Present) |
|---|---|
| ![](../android_images/globe-at.png) | Select another language/keyboard |
| ![](../android_images/hide-keyboard-at.png) | Hide the keyboard |
@ -10,6 +10,11 @@ title: Keyman for Android (Tablet Layout)
| ![](../android_images/return-at.png) | Return |
| ![](../android_images/shift-at.png) | Shift key. Long press this key to access the <kbd>CTRL</kbd>, <kbd>ALT</kbd> and <kbd>CTRL</kbd><kbd>ALT</kbd> keys (which can access additional key layers) |
| ![](../android_images/touch-hold-at.png) | Keys with a small dot in the top right corner indicate a long press key. Access further functionality by long pressing the key |
----
| | Toolbar Icons
|---|---|
| ![](../android_images/share-a.png) | Share your text with other atps like Messages, Gmail, or Twitter. Some versions of Android also let you copy your text to the clipboard. |
| ![](../android_images/browser-a.png) | Open the Keyman Browser to use the web in your language |
| ![](../android_images/menu-icon-a.png) | Open the menu for additional options |

View file

@ -288,7 +288,8 @@ namespace com.keyman.text {
this.text = text ? text : "";
var defaultLength = this.text._kmwLength();
this.caretIndex = caretPos ? caretPos : defaultLength;
// Ensures that `caretPos == 0` is handled correctly.
this.caretIndex = typeof caretPos == "number" ? caretPos : defaultLength;
}
// Clones the state of an existing EditableElement, creating a Mock version of its state.

5
ios/.gitignore vendored
View file

@ -17,9 +17,12 @@ engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keyman-sentr
Keyman 2*
Carthage
# Generated offline help
keyman/Keyman/resources/OfflineHelp.bundle/Contents/Resources
# Explicitly ignores just the SWKeyboard scheme; testing against different
# targets makes a lot of noise.
**/SWKeyboard.scheme
# Ignores exported localization files.
en.xcloc
en.xcloc

View file

@ -3,13 +3,20 @@
## Prerequisites
* Xcode 9.1
* iOS 8+
* [Node.js](https://nodejs.org/) 8.9+ (for building the embedded KeymanWeb engine)
* SwiftLint (`brew install swiftlint`)
* Carthage (`brew install carthage`)
* [Node.js](https://nodejs.org/) 8.9+ (for building KeymanWeb)
- If using Xcode 12 or later, note that you may need to use the instructions found at https://github.com/Carthage/Carthage/blob/master/Documentation/Xcode12Workaround.md
- We plan to update to use of Carthage 0.37's .xcframeworks solution early in the 15.0 development cycle once 14.0 has successfully released, which will negate the need for the workaround mentioned above.
* Pandoc (`brew install pandoc`)
- Used solely to generate offline help.
* Coreutils (`brew install coreutils`)
* sentry-cli (`brew install getsentry/tools/sentry-cli`) to utilize Sentry-based error reporting
* jq (`brew install jq`)
Note that the `brew` command mentioned above is Homebrew, which may be found at https://brew.sh/.
While not strictly necessary, it certainly simplifies installing the prerequisites above.
## Keyman App
The source for our published iOS Keyman app is found within the **keyman/** subdirectory. This project only contains

View file

@ -1,47 +1,108 @@
#!/bin/bash
# Uses the open-source `wget` utility to create an embedding-friendly offline mirror
# equivalent of the online iOS help.
# Set sensible script defaults:
# set -e: Terminate script if a command returns an error
set -e
# set -u: Terminate script if an unset variable is used
set -u
HELP_ROOT=keyman/Keyman/resources/OfflineHelp.bundle/Contents/Resources
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")"
. "$(dirname "$THIS_SCRIPT")/../resources/build/build-utils.sh"
## END STANDARD BUILD SCRIPT INCLUDE
VERSION=`cat ../resources/VERSION.md`
QUIET=0
# If TIER is set to "alpha", use the previous version. Help is usually only updated during
# the "beta" process.
if [ -z $TIER ]; then
# Prevents an error message on the next check. Also, manual runs will likely be during beta.
TIER=beta
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
THIS_DIR="$(dirname "$THIS_SCRIPT")"
display_usage() {
echo "build.sh [--no-clean] target [...target]"
echo "Builds help documentation for Keyman for iPhone and iPad"
echo "Targets:"
echo " * htm or html: convert documentation to html using pandoc"
echo
echo " --no-clean: don't clean target folder before building"
}
DO_HTM=false
DO_CLEAN=true
# Debug flags
DO_HTM_CONVERSION=true
#
# Parse args
#
shopt -s nocasematch
while [[ $# -gt 0 ]] ; do
key="$1"
case $key in
htm | html)
DO_HTM=true
;;
--no-clean)
DO_CLEAN=false
;;
*)
display_usage
exit 1
esac
shift # past argument
done
if ! $DO_HTM ; then
display_usage
exit 1
fi
if [ $TIER = "alpha" ]; then
# More readable: (major).minor
[[ "$VERSION" =~ ([0-9]+)\.[0-9]+ ]]
# Construct a decremented version string with .minor set to .0
VERSION="$((${BASH_REMATCH[1]}-1)).0"
displayInfo "" \
"DO_HTM: $DO_HTM" \
"DO_CLEAN: $DO_CLEAN" \
""
#
# Compile all .md to .html
#
cd $KEYMAN_ROOT/ios/help
MDLUA="$KEYMAN_ROOT/resources/build/htmlink.lua"
MD=`find . -name "*.md"`
DESTHTM="$THIS_DIR/keyman/Keyman/resources/OfflineHelp.bundle/Contents/Resources"
if $DO_HTM; then
#
# Clean existing folder
#
if $DO_CLEAN; then
rm -rf "$DESTHTM" || true # We don't want to die when we clean an empty folder
fi
mkdir -p "$DESTHTM"
#
# Generate HTML files from Markdown
#
if $DO_HTM_CONVERSION; then
for INFILE in $MD; do
OUTFILE="$DESTHTM/${INFILE%.md}.html"
echo "Processing $INFILE to $(basename "$OUTFILE")"
mkdir -p "$(dirname "$OUTFILE")"
pandoc -s --lua-filter="$MDLUA" -t html -o "$OUTFILE" $INFILE
done
fi
#
# Copy Images
#
cd $KEYMAN_ROOT/ios/help/
mkdir -p "$DESTHTM/ios_images"
cp ios_images/* "$DESTHTM/ios_images/"
fi
# Clear previous help file downloads (if they exist)
if [ -d "$HELP_ROOT" ]; then
rm -r "$HELP_ROOT"
fi
# Create local mirror of the help page subdirectory.
# We don't need /font/deploy folder resources, so they're excluded here.
# One of the .css files auto-includes them otherwise.
wget --mirror \
--convert-links \
--wait=2 \
--keep-session-cookies \
--page-requisites \
--no-parent \
--restrict-file-names=windows \
--exclude-directories /font/deploy \
--directory-prefix="$HELP_ROOT" \
--no-directories \
--default-page=index.php \
--adjust-extension \
"help.keyman.com/products/iphone-and-ipad/$VERSION/index.php?embed=ios"
# Results in a flat-structured mirror of the iphone-and-ipad/$VERSION folder,
# together with all needed resources within the 'site' folder.

View file

@ -439,8 +439,8 @@ public class InstalledLanguagesViewController: UITableViewController, UIAlertVie
}
extension InstalledLanguagesViewController {
@objc func addClicked(_ sender: Any) {
public func launchKeyboardSearch() {
let keyboardSearchVC = KeyboardSearchViewController(keyboardSelectionBlock: KeyboardSearchViewController.defaultDownloadClosure() { result in
switch result {
case .cancelled:
@ -459,5 +459,9 @@ extension InstalledLanguagesViewController {
navigationController!.pushViewController(keyboardSearchVC, animated: true)
}
@objc func addClicked(_ sender: Any) {
launchKeyboardSearch()
}
}

View file

@ -0,0 +1,8 @@
---
title: Version History
---
A frequent updated changelog for Keyman is kept at [help.keyman.com/version-history](https://help.keyman.com/version-history/) on the Keyman Help website.
## Related Topics
* [What's New](whatsnew)

8
ios/help/about/index.md Normal file
View file

@ -0,0 +1,8 @@
---
title: About Keyman
---
* [Welcome to Keyman](welcome)
* [What's New](whatsnew)
* [System Requirements](system-requirements)
* [Version History](history)

View file

@ -0,0 +1,9 @@
---
title: System Requirements - Keyman for iPhone and iPad Help
---
## Minimum System Requirements
### Minimum iOS Version
Keyman for iPhone and iPad will run on Apple iPhones and iPads that have a minimum version of
iOS 9.

11
ios/help/about/welcome.md Normal file
View file

@ -0,0 +1,11 @@
---
title: Welcome to Keyman
---
Thank you for installing Keyman for iPhone and iPad. Whether you're a new or returning user,
we believe you will appreciate the significant enhancements in this latest version of Keyman for iPhone and iPad.
## What is Keyman for iPhone and iPad?
Keyman for iPhone and iPad lets you type in over 600 languages on Apple iPhones and iPads.
[Click here](../start) to get started

View file

@ -0,0 +1,13 @@
---
title: What's New
---
Here are some of the new features we have added to Keyman for iPhone and iPad 14.0:
* Simpler and smoother keyboard search
* Localizable UI through [translate.keyman.com](https://translate.keyman.com)
* Consolidated crash reporting to [sentry.keyman.com](https://sentry.keyman.com)
* Choose associated language(s) when keyboard is installed (#3437)
* Improved batching of keyboard and dictionary downloads (#3458)
* Improved corrections and predictions (#3555)
* Match user input capital letters when offering suggestions (#3845)

12
ios/help/basic/index.md Normal file
View file

@ -0,0 +1,12 @@
---
title: Basic Help
---
* [Switching Between Keyboards](switching-between-keyboards)
* [Installing Custom Keyboards](installing-custom-keyboards)
* [Sharing Keyboards](sharing-keyboards)
* [Using the Keyman Browser](using-keyman-browser)
* [Removing Keyboards](uninstalling-keyboards)

View file

@ -0,0 +1,55 @@
---
title: Installing Custom Keyboards - Keyman for iPhone and iPad Help
---
The following steps can also be used to install a dictionary package.
###Download the File
If downloading a custom keyboard from the internet, click the link to your custom keyboard package file.
For this example, we'll install a custom keyboard from a link in Safari. Our example keyboard is for the GFF Amharic 7 keyboard.
![](../ios_images/dist-url-screen-i.png)
Safari will display an option to open the KMP file with Keyman.
![](../ios_images/dist-kmp-open-i.png)
Click **Open in "Keyman"** to continue.
### Finding the File (iOS 13+ only)
Installation is also possible from email clients and other sources.
Once you download the file and save it to your device, look for an
**Open in "Keyman"** option or use the
Install From File option in Keyman seen below.
![](../ios_images/add-keyboard-i2.png)
Once this option is selected, a file browser will appear. By default, it will show the most
recently downloaded files that can be installed.
![](../ios_images/add-keyboard-file-i.png)
If you have difficulty finding the right file, consider using the **Browse**
option at the bottom-right. Once you select it, you should then be greeted with the screen shown in
the section below.
### Installing the Package
Either approach seen above will start the installation process. From there, Keyman
for iPhone and iPad will parse the metadata in the package. Some packages include
documentation that will be displayed at this time.
![](../ios_images/dist-welcome-i.png)
Click the **"Install"** button at the top right.
The keyboard from the keyboard package will then be successfully installed!
![](../ios_images/dist-kmp-success-i.png)
All the keyboards in the package are installed as a group. In this example, the package only has the "GFF Amharic 7"
keyboard, so it becomes the active keyboard.
![](../ios_images/dist-install1-i.png)
To learn how to create a custom installable keyboard, [click here](http://help.keyman.com/developer/current-version/guides/distribute/).

View file

@ -0,0 +1,48 @@
---
title: Sharing Keyboards - Keyman for iPhone and iPad Help
---
### Sharing the File
If you have installed a custom keyboard or dictionary from a file, Keyman keeps a copy
of that file on your device. You can find these copies in the iOS Files app.
![](../ios_images/file-share-1i.png)
Simply select the "Keyman" folder to view these files.
![](../ios_images/file-share-2i.png)
From here, longpress the file for the keyboard or dictionary you wish to share, then
click **Share** to view sharing options.
### Sharing via QR code
For keyboards and dictionaries downloaded directly through the app,
you can provide friends with a scannable QR code. First, find the resource
through the Settings menu under Installed Languages.
![](../ios_images/add-keyboard-i2.png)
![](../ios_images/add-keyboard-i3.png)
![](../ios_images/qr-code-share-1i.png)
When you select the keyboard, a page like the following should appear:
![](../ios_images/qr-code-share-2i.png)
Note that QR codes are only provided for certain keyboards and dictionaries
at this time.
### Installing from QR code
To install from QR code, simply launch your device's Camera app and point
it at the code, as seen below.
![](../ios_images/qr-code-share-3i.png)
Selecting the notification seen at the top will lead you to the following page:
![](../ios_images/qr-code-share-4i.png)
Select the big **Install on iPhone** (or
**Install on iPad**) option will then download
the file for easy installation. (Refer to [Installing custom keyboards](installing-custom-keyboards) as necessary.)

View file

@ -0,0 +1,25 @@
---
title: Switching between keyboards - Keyman for iPhone and iPad Help
---
Keyboard switching is always activated by using the following key:
![](../ios_images/globe.png)
This will bring up a list of all currently installed languages (the default is English USA). If you have already downloaded additional languages, they will appear here. Simply select them and the keyboard will re-appear with the new language.
### The In-App Menu
![](../ios_images/in-app-picker-i.png)
Simply select any keyboard to activate it and return to the previous screen.
### The System Keyboard Menu
![](../ios_images/system-picker-i.png)
This menu only displays when the 'globe' key is touched and held.
The "Close Keyman" option will activate the next available non-Keyman system keyboard.
A quick tap on the "globe" key will instead transition to the next available installed keyboard
without displaying the menu.

View file

@ -0,0 +1,29 @@
---
title: Uninstalling Keyboards - Keyman for iPhone and iPad Help
---
To uninstall a keyboard, follow these steps.
### Access "Installed Languages"
First, open the "Settings" menu.
![](../ios_images/add-keyboard-i.png)
The "Installed Languages" menu found here manages your installed keyboards and dictionaries.
![](../ios_images/add-keyboard-i2.png)
You should then see the following screen:
![](../ios_images/add-keyboard-i3.png)
### Removing the Keyboard
Start by selecting the language for the keyboard you wish to uninstall.
![](../ios_images/delete-keyboard-i.png)
Swipe left on the keyboard you want to remove. You'll see a button to **Delete**.
![](../ios_images/delete-keyboard-i2.png)
Click the **Delete** button to uninstall the keyboard.

View file

@ -0,0 +1,12 @@
---
title: Using the Keyman Browser - Keyman for iPhone and iPad Help
---
Step 1) Click the Keyman Browser button in the Keyman app
Step 2) Enter the URL of a website to visit into the address bar, for example **google.com**. Keyman Browser will load
the page and detect your language if it is present, and reformat it to show your language instead of square boxes.
Step 3) Use the bookmark button to save the current page for browsing later.
Step 4) Use the Globe icon to swap between languages.

24
ios/help/context/index.md Normal file
View file

@ -0,0 +1,24 @@
---
title: Keyman for iPhone and iPad Help
---
| | Keyboard Keys <br/>(Keyboard Present) |
|---|---|
| ![](../ios_images/globe.png) | Install or select another language/keyboard |
| ![](../ios_images/hide-keyboard.png) | Hide the keyboard |
| ![](../ios_images/backspace.png) | Backspace |
| ![](../ios_images/return.png) | Return |
| ![](../ios_images/shift.png) | Shift key. Long press this key to access the <kbd>CTRL</kbd>, <kbd>ALT</kbd> and <kbd>CTRL</kbd><kbd>ALT</kbd> keys (which can access additional key layers) |
| ![](../ios_images/touch-hold.png) | Keys with a small dot in the top right corner indicate a long press key. Access further functionality by long pressing the key |
----
| | Toolbar Icons </th>
|---|---|
| ![](../ios_images/share.png) | Share your text via Mail, Text, Facebook or Twitter |
| ![](../ios_images/browser-icon.png) | Open the Keyman Browser |
| ![](../ios_images/menu-icon.png) | Open the menu for additional options |
| ![](../ios_images/font-size.png) | Adjust the font size |
| ![](../ios_images/delete.png) | Delete all current text |
| ![](../ios_images/get-started.png) | Open the initial setup screen |
| ![](../ios_images/info.png) | Load the Keyman help page |

35
ios/help/index.md Normal file
View file

@ -0,0 +1,35 @@
---
title: Keyman for iPhone and iPad 14.0 Help
---
## [About Keyman](about/)
* [Welcome to Keyman](about/welcome)
* [What's New](about/whatsnew)
* [System Requirements](about/system-requirements)
* [Version History](about/history)
## [Getting Started](start/)
* [How To - Adding a Keyboard](start/installing-keyboards)
* [How To - Installing Keyman as a System Keyboard](start/installing-system-keyboard)
## [Using the Menu](context/)
## Ask for Help
* [Ask other users in the SIL Keyman Community](https://community.software.sil.org/c/keyman)
## Help Topics
### [Troubleshooting](troubleshooting/)
* [How To - Installing fonts](troubleshooting/installing-fonts)
* [How To - Integrating Keyman for iPhone and iPad](troubleshooting/integrating)
### [Using Keyman for iPhone and iPad](basic/)
* [Switching Between Keyboards](basic/switching-between-keyboards)
* [Installing Custom Keyboards](basic/installing-custom-keyboards)
* [Sharing Keyboards](basic/sharing-keyboards)
* [Using the Keyman Browser](basic/using-keyman-browser)
* [Removing Keyboards](basic/uninstalling-keyboards)
## Further Help
For more information on Keyman, [keyman.com](https://keyman.com).
For more information on Keyman for iPhone and iPad, visit [keyman.com/iphone-and-ipad](https://keyman.com/iphone-and-ipad/)

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.9 KiB

View file

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View file

Before

Width:  |  Height:  |  Size: 2.3 KiB

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1,000 B

After

Width:  |  Height:  |  Size: 1,000 B

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

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