Merge pull request #4739 from keymanapp/chore/B14S8-beta-to-master
chore: B14S8 beta to alpha
100
HISTORY.md
|
|
@ -290,6 +290,106 @@
|
|||
|
||||
* chore: prepare 15.0 alpha (#4129)
|
||||
|
||||
## 14.0.266 beta 2021-03-19
|
||||
|
||||
* chore(android/app): Migrate launcher icon to adaptive (#4720)
|
||||
* fix(common/core/web): predictive banner activation logic (#4713)
|
||||
* fix(web): allows refresh of currently-loaded model (#4728)
|
||||
* fix(ios): kbd search now caches publishing data for result (#4729)
|
||||
* fix(linux): Fix switching to keyboard in middle of line (#4678) (#4721)
|
||||
* fix(windows): ensure profiles are reinstalled during setup (#4727)
|
||||
* fix(windows): Avoid double start during setup (#4726)
|
||||
|
||||
## 14.0.265 beta 2021-03-18
|
||||
|
||||
* fix(common/core/web): disambiguation of keys sharing same base key ID (#4703)
|
||||
* fix(windows): Download keyboard dialog TLS protocol support (#4714)
|
||||
* fix(web): first-pass workaround for popup-key corrections (#4704)
|
||||
* fix(developer): use var not let in definitions (#4718)
|
||||
* chore(windows): finalize help (#4717)
|
||||
|
||||
## 14.0.264 beta 2021-03-17
|
||||
|
||||
* fix(mac): prevent duplicate keyboards in list (#4674)
|
||||
* fix(android/app): Load system keyboard before checking overrides (#4696)
|
||||
* fix(android): Update styling and set text to black (#4662)
|
||||
|
||||
## 14.0.263 beta 2021-03-16
|
||||
|
||||
* fix(ios): keyboard search did not alert for lack of net (#4693)
|
||||
* chore(android/engine): Send KMW console errors to Sentry (#4692)
|
||||
* fix(windows): start Keyman after setup only if not running (#4681)
|
||||
* fix(developer): &CasedKeys and &MnemonicLayout are not compatible together (#4690)
|
||||
* fix(android/engine): Fix crash when download file fails to copy to cache (#4697)
|
||||
* fix(developer): validate keyboard_info should give info on non-canonical bcp47 (#4689)
|
||||
* chore(developer): upgrade bcp 47 canonicalisation to warning (#4699)
|
||||
* fix(developer): support named character codes at end of line (#4691)
|
||||
|
||||
## 14.0.262 beta 2021-03-16
|
||||
|
||||
* fix(android): Update styling and set text to black (#4662)
|
||||
* fix(mac): prevent duplicate keyboards in list (#4674)
|
||||
|
||||
## 14.0.261 beta 2021-03-15
|
||||
|
||||
* fix(windows): Trigger language sync after changes (#4663)
|
||||
* chore(linux): Ignore buildtools in keyman-config tarball (#4675)
|
||||
|
||||
## 14.0.260 beta 2021-03-15
|
||||
|
||||
* chore(windows): fully disable auto start task (#4658)
|
||||
* fix(windows): keep online update in focus (#4661)
|
||||
* fix(windows): setup must save install state for restarts (#4649)
|
||||
* fix(ios): removal of default keyboard respected when updating app (#4651)
|
||||
* feat(ios): auto-bundles the most recent version of default resources (#4656)
|
||||
* fix(mac): register configuration window on load (#4667)
|
||||
* chore(ios): Set Localizable.strings to UTF-8 (#4670)
|
||||
* fix(android/app): Fix crash when clicking QR code (#4664)
|
||||
* fix(android): fixes errors within lists used for UI (#4666)
|
||||
* chore(common): Update crowdin for km (#4671)
|
||||
* chore(android/app,ios/app): Update crowdin for de (#4672)
|
||||
* fix(mac): release window resources on close (#4669)
|
||||
* fix(mac): show local help in app (#4673)
|
||||
* chore(windows): Update whatsnew.md (#4665)
|
||||
|
||||
## 14.0.259 beta 2021-03-12
|
||||
|
||||
* chore(developer): add B11 ISO code for ABNT2 keyboard (#4654)
|
||||
* fix(web): better check for missing MutationObserver type (#4646)
|
||||
* fix(android/engine): Reset in-app context when selection changes (#4636)
|
||||
* chore(linux): Move signature files as well (#4653)
|
||||
|
||||
## 14.0.258 beta 2021-03-11
|
||||
|
||||
* fix(windows): context help links (#4626)
|
||||
* fix(windows): hotkeys correctly ignore right modifier keys (#4628)
|
||||
* fix(windows): Handle disabled profiles and invalid language ids (#4635)
|
||||
* fix(web): event handling for TouchAliasElement's blinking caret (#4638)
|
||||
* fix(common/models): prevents "undefined" reversion display string (#4648)
|
||||
* docs(common): Update npm-packages readme (#4644)
|
||||
|
||||
## 14.0.257 beta 2021-03-10
|
||||
|
||||
* fix(common/core/web): Add keyboard check for scriptObject (#4640)
|
||||
* docs(common): Update readme (#4642)
|
||||
|
||||
## 14.0.256 beta 2021-03-10
|
||||
|
||||
* chore(linux): Update readme (#4633)
|
||||
|
||||
## 14.0.255 beta 2021-03-09
|
||||
|
||||
* chore(common): Update readme (#4631)
|
||||
|
||||
## 14.0.254 beta 2021-03-09
|
||||
|
||||
* fix(web): use language code correctly in toolbar (#4620)
|
||||
* chore(linux): Fix some lintian warnings for Debian package (#4621)
|
||||
* docs(ios): adds supported l10ns to help/about/whatsnew (#4625)
|
||||
* fix(windows): represerve keys on setfocus (#4622)
|
||||
* fix(windows): show version selector for Keyman in installer (#4623)
|
||||
* fix(windows): ensure valid base layout on install (#4627)
|
||||
|
||||
## 14.0.253 beta 2021-03-05
|
||||
|
||||
* fix(android/app): Fix welcome.htm responsiveness (#4531)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
15.0.20
|
||||
15.0.20
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@
|
|||
<application
|
||||
android:allowBackup="true"
|
||||
android:hardwareAccelerated="true"
|
||||
android:icon="@drawable/ic_launcher"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@drawable/ic_launcher_round"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/AppTheme"
|
||||
android:supportsRtl="true">
|
||||
|
|
@ -234,21 +233,21 @@
|
|||
<activity
|
||||
android:name=".InfoActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name"/>
|
||||
<activity
|
||||
android:name=".BookmarksActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name"/>
|
||||
<activity
|
||||
android:name=".PackageActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name"/>
|
||||
<activity
|
||||
android:name=".GetStartedActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:launchMode="singleTask"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog"></activity>
|
||||
android:theme="@style/AppTheme.Light.Dialog"/>
|
||||
<activity
|
||||
android:name=".KeymanSettingsActivity"
|
||||
android:label="@string/keyman_settings"
|
||||
|
|
@ -282,7 +281,7 @@
|
|||
android:name=".KeyboardSettingsActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" />
|
||||
android:theme="@style/AppTheme.Light.Dialog" />
|
||||
<activity
|
||||
android:name=".SelectPackageActivity"
|
||||
android:label="@string/app_name"
|
||||
|
|
@ -302,12 +301,12 @@
|
|||
android:name=".WebBrowserActivity"
|
||||
android:process=":WebBrowserActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name"/>
|
||||
<activity
|
||||
android:name=".KMPBrowserActivity"
|
||||
android:process=":KMPBrowserActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"></activity>
|
||||
android:label="@string/app_name"/>
|
||||
|
||||
|
||||
<provider
|
||||
|
|
|
|||
BIN
android/KMAPro/kMAPro/src/main/ic_launcher-playstore.png
Normal file
|
After Width: | Height: | Size: 66 KiB |
|
|
@ -136,7 +136,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
KMManager.setMayPredictOverride(inputType);
|
||||
if (KMManager.getMayPredictOverride()) {
|
||||
KMManager.setBannerOptions(false);
|
||||
} else {
|
||||
} else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){
|
||||
// Check if predictions needs to be re-enabled per Settings preference
|
||||
Context appContext = getApplicationContext();
|
||||
Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext);
|
||||
|
|
|
|||
|
|
@ -149,7 +149,8 @@ public final class KeyboardSettingsActivity extends AppCompatActivity {
|
|||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||
HashMap<String, String> hashMap = (HashMap<String, String>) parent.getItemAtPosition(position);
|
||||
if (hashMap == null) {
|
||||
KMLog.LogError(TAG, "map is null, position is " + position);
|
||||
// Ignore null HashMap when clicking on QR code
|
||||
return;
|
||||
}
|
||||
String itemTitle = MapCompat.getOrDefault(hashMap, titleKey, "");
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 6.8 KiB |
|
Before Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 3.6 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 9.5 KiB |
|
Before Width: | Height: | Size: 8.2 KiB |
|
Before Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 26 KiB |
|
|
@ -13,16 +13,16 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@color/keyman_orange" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" >
|
||||
|
||||
<View
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@color/keyman_orange" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width = "wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
BIN
android/KMAPro/kMAPro/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
BIN
android/KMAPro/kMAPro/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
BIN
android/KMAPro/kMAPro/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 7.9 KiB |
BIN
android/KMAPro/kMAPro/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
After Width: | Height: | Size: 18 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 12 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
|
@ -43,7 +43,7 @@
|
|||
<!-- Context: Get Started menu -->
|
||||
<string name="show_get_started" comment="Show the "Get Started" menu on startup">បង្ហាញ \"%1$s\" ពេលចាប់ផ្ដើម</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages">ដើម្បីដំឡើងកញ្ចប់ក្ដារចុច សូមអនុញ្ញាតឱ្យ Keyman មានសិទ្ធិអាន/កែនៅលើឧបករណ៍ផ្ទុក។</string>
|
||||
<string name="request_storage_permission" comment="Request storage permission to access keyboard packages"> ដើម្បីដំឡើងកញ្ចប់ក្ដារចុច សូមអនុញ្ញាតឱ្យ Keyman មានសិទ្ធិអានឧបករណ៍ផ្ទុកខាងក្រៅ។</string>
|
||||
<!-- Context: Android Storage Permission -->
|
||||
<string name="storage_permission_denied" comment="Keyboard package installation may fail since Android storage permission not granted">សំណើសុំសិទ្ធិចូលប្រើឧបករណ៍ផ្ទុកត្រូវបានបដិសេធ។ អាចបរាជ័យក្នុងការដំឡើងកញ្ចប់ក្ដារចុច</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
|
|
@ -55,6 +55,8 @@
|
|||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="install_keyboard_or_dictionary" comment="Menu item to install keyboard or dictionary">ដំឡើងក្ដារចុចឬបញ្ជីពាក្យ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="change_display_language" comment="Menu action to change interface language">ប្ដូរភាសានៃការបង្ហាញ</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner" comment="text suggestions banner">បង្ហាញបដាជានិច្ច</string>
|
||||
<!-- Context: Keyman Settings menu -->
|
||||
<string name="show_banner_on" comment="Description when toggle is on">ត្រូវអនុវត្ត</string>
|
||||
|
|
@ -107,6 +109,8 @@
|
|||
<!-- Context: KMP Package strings -->
|
||||
<string name="install_predictive_text_package" comment="Title to install dictionary package">ដំឡើងបញ្ជីពាក្យ</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="not_valid_package_file" comment="Notification when invalid package file cannot be installed">%1$s មិនមែនជាកញ្ចប់ Keyman ដែលត្រឹមត្រូវទេ។\n%2$s\"</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_touch_keyboards_to_install" comment="Notification when no touch-optimized keyboards can be installed">កញ្ចប់នេះគ្មានក្ដារចុចសម្រាប់ឧបករណ៍ចល័តទេ</string>
|
||||
<!-- Context: KMP Package strings -->
|
||||
<string name="no_new_predictive_text_to_install" comment="Notification when no dictionaries can be installed">គ្មានការណែនាំពាក្យស្វ័យប្រវត្តិថ្មីសម្រាប់ដំឡើងទេ</string>
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<style name="AppTheme" parent="AppTheme.Base" />
|
||||
|
||||
<!--
|
||||
Base application theme for API 21+. This theme completely replaces
|
||||
AppTheme from res/values/styles.xml on API 21+ devices.
|
||||
-->
|
||||
<!-- Application theme. -->
|
||||
<style name="AppTheme.Base" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<item name="colorPrimary">@android:color/white</item>
|
||||
<!-- colorPrimaryDark is used for the status bar -->
|
||||
<item name="colorPrimaryDark">@color/complementary_5</item>
|
||||
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textColorHint">@color/neutral_3</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
|
||||
<item name="dialogTheme">@style/AppTheme.Dialog</item>
|
||||
<item name="alertDialogTheme">@style/AppTheme.Dialog.Alert</item>
|
||||
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay</item>
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Toolbar" parent="Base.Widget.AppCompat.Toolbar">
|
||||
<item name="android:colorPrimary">@android:color/white</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:textColorSecondary">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="android:colorAccent">@color/keyman_blue</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:colorAccent">@color/keyman_blue</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
|
|
@ -9,6 +9,8 @@
|
|||
<style name="AppTheme.Base" parent="@style/Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize the theme here -->
|
||||
<item name="colorPrimary">@android:color/white</item>
|
||||
<!-- colorPrimaryDark is used for the status bar -->
|
||||
<item name="colorPrimaryDark">@color/complementary_5</item>
|
||||
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textColorHint">@color/neutral_3</item>
|
||||
|
|
@ -30,6 +32,7 @@
|
|||
</style>
|
||||
|
||||
<style name="AppTheme.Toolbar" parent="Base.Widget.AppCompat.Toolbar">
|
||||
<item name="android:colorPrimary">@android:color/white</item>
|
||||
<item name="android:background">@android:color/white</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:textColorSecondary">@android:color/black</item>
|
||||
|
|
|
|||
|
|
@ -16,31 +16,31 @@
|
|||
android:launchMode="singleTask"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" >
|
||||
android:theme="@style/AppTheme.Light.Dialog" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.KeyboardInfoActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" >
|
||||
android:theme="@style/AppTheme.Light.Dialog" >
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.KMKeyboardDownloaderActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label=""
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
android:theme="@style/AppTheme.Light.Dialog">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.ModelPickerActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog">
|
||||
android:theme="@style/AppTheme.Light.Dialog">
|
||||
</activity>
|
||||
<activity
|
||||
android:name="com.tavultesoft.kmea.ModelInfoActivity"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.AppCompat.Light.Dialog" >
|
||||
android:theme="@style/AppTheme.Light.Dialog" >
|
||||
</activity>
|
||||
|
||||
<!-- Put other WebViewActivities in a separate process so the Keyboard WebView doesn't lag.
|
||||
|
|
|
|||
|
|
@ -149,13 +149,14 @@ final class KMKeyboard extends WebView {
|
|||
}
|
||||
}
|
||||
|
||||
// Not sending console errors to Sentry anymore since they should be handled by KMW sentryManager
|
||||
// Send console errors to Sentry in case they're missed by KMW sentryManager
|
||||
// (Ignoring spurious message "No keyboard stubs exist = ...")
|
||||
// TODO: Analyze if this error warrants reverting to default keyboard
|
||||
// TODO: Fix base error rather than trying to ignore it "No keyboard stubs exist"
|
||||
|
||||
if ((cm.messageLevel() == ConsoleMessage.MessageLevel.ERROR) && (!cm.message().startsWith("No keyboard stubs exist"))) {
|
||||
// Make Toast notification of error and send log about falling back to default keyboard (ignore language ID)
|
||||
sendKMWError(cm.lineNumber(), cm.sourceId(), cm.message());
|
||||
sendError(packageID, keyboardID, "");
|
||||
Keyboard firstKeyboard = KeyboardController.getInstance().getKeyboardInfo(0);
|
||||
if (firstKeyboard != null) {
|
||||
|
|
@ -623,6 +624,43 @@ final class KMKeyboard extends WebView {
|
|||
return keyboardPath;
|
||||
}
|
||||
|
||||
private void sendKMWError(int lineNumber, String sourceId, String message) {
|
||||
if (Sentry.isEnabled()) {
|
||||
Breadcrumb breadcrumb = new Breadcrumb();
|
||||
breadcrumb.setMessage("KMKeyboard.sendKMWError");
|
||||
breadcrumb.setCategory("KMWError");
|
||||
breadcrumb.setLevel(SentryLevel.ERROR);
|
||||
// Error info
|
||||
breadcrumb.setData("cm_lineNumber", lineNumber);
|
||||
breadcrumb.setData("cm_sourceID", sourceId);
|
||||
breadcrumb.setData("cm_message", message);
|
||||
|
||||
// Keyboard info
|
||||
if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
breadcrumb.setData("keyboardType", "INAPP");
|
||||
} else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
breadcrumb.setData("keyboardType", "SYSTEM");
|
||||
} else {
|
||||
breadcrumb.setData("keyboardType", "UNDEFINED");
|
||||
}
|
||||
|
||||
if (this.packageID != null) {
|
||||
breadcrumb.setData("packageID", this.packageID);
|
||||
}
|
||||
if (this.keyboardID != null) {
|
||||
breadcrumb.setData("keyboardID", this.keyboardID);
|
||||
}
|
||||
if (this.keyboardName != null) {
|
||||
breadcrumb.setData("keyboardName", this.keyboardName);
|
||||
}
|
||||
if (this.keyboardVersion != null) {
|
||||
breadcrumb.setData("keyboardVersion", this.keyboardVersion);
|
||||
}
|
||||
Sentry.addBreadcrumb(breadcrumb);
|
||||
Sentry.captureMessage("sendKMWError", SentryLevel.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// Extract Unicode numbers (\\uxxxx) from a layer to character string.
|
||||
// Ignores empty strings and layer names
|
||||
// Returns: String
|
||||
|
|
|
|||
|
|
@ -467,6 +467,18 @@ public final class KMManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static boolean isKeyboardLoaded(KeyboardType type) {
|
||||
if (type == KeyboardType.KEYBOARD_TYPE_INAPP) {
|
||||
return InAppKeyboardLoaded;
|
||||
} else if (type == KeyboardType.KEYBOARD_TYPE_SYSTEM) {
|
||||
return SystemKeyboardLoaded;
|
||||
} else {
|
||||
String msg = "Keyboard type undefined";
|
||||
KMLog.LogError(TAG, msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
public static View createInputView(InputMethodService inputMethodService) {
|
||||
//final Context context = appContext;
|
||||
|
|
@ -2386,22 +2398,24 @@ public final class KMManager {
|
|||
|
||||
if (dn <= 0) {
|
||||
if (start == end) {
|
||||
if (!s.isEmpty() && s.charAt(0) == '\n') {
|
||||
if (s.length() > 0 && s.charAt(0) == '\n') {
|
||||
textView.keyDownUp(KeyEvent.KEYCODE_ENTER);
|
||||
} else {
|
||||
// *** TO DO: Try to find a solution to the bug on API < 17, insert overwrites on next line
|
||||
InAppKeyboardShouldIgnoreTextChange = true;
|
||||
InAppKeyboardShouldIgnoreSelectionChange = true;
|
||||
textView.getText().insert(start, s);
|
||||
if (s.length() > 0) {
|
||||
InAppKeyboardShouldIgnoreTextChange = true;
|
||||
InAppKeyboardShouldIgnoreSelectionChange = true;
|
||||
textView.getText().insert(start, s);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (!s.isEmpty() && s.charAt(0) == '\n') {
|
||||
if (s.length() > 0 && s.charAt(0) == '\n') {
|
||||
InAppKeyboardShouldIgnoreTextChange = true;
|
||||
InAppKeyboardShouldIgnoreSelectionChange = true;
|
||||
textView.getText().replace(start, end, "");
|
||||
textView.keyDownUp(KeyEvent.KEYCODE_ENTER);
|
||||
} else {
|
||||
if (s.isEmpty()) {
|
||||
if (s.length() == 0) {
|
||||
textView.getText().delete(start, end);
|
||||
} else {
|
||||
InAppKeyboardShouldIgnoreTextChange = true;
|
||||
|
|
|
|||
|
|
@ -65,8 +65,9 @@ public final class KMTextView extends AppCompatEditText {
|
|||
int selStart = textView.getSelectionStart();
|
||||
int selEnd = textView.getSelectionEnd();
|
||||
KMManager.updateText(KeyboardType.KEYBOARD_TYPE_INAPP, textView.getText().toString());
|
||||
KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd);
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
if (KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
|
||||
private void init(final Context context) {
|
||||
|
|
@ -165,7 +166,9 @@ public final class KMTextView extends AppCompatEditText {
|
|||
protected void onSelectionChanged(int selStart, int selEnd) {
|
||||
super.onSelectionChanged(selStart, selEnd);
|
||||
if (activeView != null && activeView.equals(this)) {
|
||||
KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd);
|
||||
if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) {
|
||||
KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -151,13 +151,17 @@ public class CloudApiTypes {
|
|||
* @return File - handle to the downloaded file in cache
|
||||
*/
|
||||
public File cacheAndOpenDestinationFile(Context context) {
|
||||
File cachedFile = null;
|
||||
DownloadManager downloadManager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
|
||||
Uri data = downloadManager.getUriForDownloadedFile(downloadId);
|
||||
DownloadFileUtils.Info info = DownloadFileUtils.cacheDownloadFile(context, data);
|
||||
String filename = info.getFilename();
|
||||
File cachedFile = info.getFile();
|
||||
|
||||
if (filename == null || filename.isEmpty() || cachedFile == null || !cachedFile.exists()) {
|
||||
if (data != null) {
|
||||
DownloadFileUtils.Info info = DownloadFileUtils.cacheDownloadFile(context, data);
|
||||
String filename = info.getFilename();
|
||||
cachedFile = info.getFile();
|
||||
}
|
||||
|
||||
if (cachedFile == null) {
|
||||
// failed to retrieve downloaded file
|
||||
BaseActivity.makeToast(context, R.string.failed_to_retrieve_file, Toast.LENGTH_LONG);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -94,10 +94,19 @@ public class NestedAdapter<Element, A extends ArrayAdapter<Element> & ListBacked
|
|||
}
|
||||
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A, FilterArg> filter, FilterArg filterArg) {
|
||||
this(context, resource, adapter, filter, filterArg, filter.selectFrom(adapter, filterArg));
|
||||
// Ensure that the list we pass through is writeable. Errors may result otherwise.
|
||||
this(context, resource, adapter, filter, filterArg, new ArrayList<>(filter.selectFrom(adapter, filterArg)));
|
||||
}
|
||||
|
||||
public NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A, FilterArg> filter, FilterArg filterArg, List<Element> filteredList) {
|
||||
// In order for the instance to be properly constructed, the `filteredList`
|
||||
// parameter must be mutable. Unfortunately, there's no way to robustly
|
||||
// check for that, and it illegal to construct a modifiable version
|
||||
// before the super's constructor call b/c Java limitations.
|
||||
// So... we'll keep it locked behind 'protected' for now.
|
||||
//
|
||||
// Could probably throw an extra parameter on for an 'internal' version
|
||||
// and use the current signature as a 'helper' like with the constructor above?
|
||||
protected NestedAdapter(@NonNull Context context, int resource, @NonNull A adapter, AdapterFilter<Element, A, FilterArg> filter, FilterArg filterArg, List<Element> filteredList) {
|
||||
super(context, resource, filteredList);
|
||||
|
||||
this.wrappedAdapter = adapter;
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tavultesoft.kmea.util;
|
|||
import android.os.Build;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.lang.NullPointerException;
|
||||
|
||||
/**
|
||||
* HashMap.getOrDefault is only available for Android API 24+, so this method is an alternative
|
||||
|
|
@ -12,7 +13,15 @@ import java.util.HashMap;
|
|||
public final class MapCompat {
|
||||
private static final String TAG = "MapCompat";
|
||||
|
||||
public static <K, V> V getOrDefault(HashMap<K, V> map, K key, V defaultValue) {
|
||||
/**
|
||||
*
|
||||
* @param map HashMap<String, String>
|
||||
* @param key String
|
||||
* @param defaultValue String
|
||||
* @return String
|
||||
* @throws NullPointerException
|
||||
*/
|
||||
public static <K, V> V getOrDefault(HashMap<K, V> map, K key, V defaultValue) throws NullPointerException {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
return map.getOrDefault(key, defaultValue);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:state_activated="false" android:state_pressed="true" android:drawable="@color/activated_bg" />
|
||||
<item android:state_activated="false" android:state_selected="true" android:drawable="@color/selected_bg" />
|
||||
<item android:state_activated="true" android:drawable="@color/activated_bg" />
|
||||
<item android:drawable="@android:color/white" />
|
||||
<item android:state_activated="false" android:state_pressed="true" android:drawable="@color/activated_bg" />
|
||||
<item android:state_activated="false" android:state_selected="true" android:drawable="@color/selected_bg" />
|
||||
<item android:state_activated="true" android:drawable="@color/activated_bg" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<include layout="@layout/list_toolbar" />
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
android:layout_height="match_parent" >
|
||||
|
||||
<ListView
|
||||
style="@style/AppTheme.Light.ListView"
|
||||
android:id="@+id/listView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
android:background="@drawable/list_item" >
|
||||
|
||||
<LinearLayout
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:textColor="@android:color/black"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
android:background="@drawable/list_item" >
|
||||
|
||||
<LinearLayout
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
|
@ -14,6 +15,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
|
|
@ -22,6 +24,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:descendantFocusability="blocksDescendants"
|
||||
|
|
@ -7,6 +8,7 @@
|
|||
|
||||
<LinearLayout
|
||||
android:id="@+id/linearLayout1"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
|
@ -16,6 +18,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="5dp"
|
||||
|
|
@ -24,6 +27,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/text2"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
|
|
@ -39,7 +43,7 @@
|
|||
android:layout_centerVertical="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:contentDescription="@string/image_button"
|
||||
android:tint="@android:color/darker_gray"
|
||||
android:src="@drawable/ic_light_action_info" />
|
||||
android:src="@drawable/ic_light_action_info"
|
||||
app:tint="@android:color/black" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
android:background="@drawable/list_item" >
|
||||
|
||||
<LinearLayout
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
|
|
@ -15,6 +16,7 @@
|
|||
<TextView
|
||||
android:id="@+id/text1"
|
||||
android:textColor="@drawable/textview_item"
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="15dp"
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@
|
|||
<!-- optional accent -->
|
||||
<View
|
||||
android:id="@+id/bar_accent"
|
||||
style="@style/AppTheme.Divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="2dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:background="?attr/colorAccent"/>
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,8 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View style="@style/Divider.horizontal" />
|
||||
<View style="@style/AppTheme.Divider.horizontal"
|
||||
android:background="@android:color/holo_red_dark"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/qrCode"
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@
|
|||
<!-- Context: Button label -->
|
||||
<string name="label_update" comment="Dialog button to update a resource">Aktualisieren</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">\nKeine Verbindung zum Keyman Server!\n</string>
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">Kann keine Verbindung zum Keyman-Server herstellen!</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_delete_keyboard" comment="Confirmation to delete a keyboard">Möchten Sie diese Tastatur löschen?</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
|
|
@ -65,6 +65,10 @@
|
|||
<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: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">Willkommen bei %1$s</string>
|
||||
<!-- Context: Keyboard app doesn't include FileProvider library needed to view help file -->
|
||||
<string name="fileprovider_undefined" comment="FileProvider library needed to view help file"> FileProvider-Bibliothek zum Anzeigen der Hilfe-Datei: %1$s</string>
|
||||
<!-- Context: Fatal keyboard error. Will load default keyboard -->
|
||||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> Fataler Tastaturfehler mit %1$s:%2$s für %3$s Sprache. Standard-Tastatur wird geladen.</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 -->
|
||||
|
|
@ -92,11 +96,17 @@
|
|||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">Download des Wörterbuchs ist abgeschlossen.</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">Download fehlgeschlagen</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">Laden der heruntergeladenen Datei fehlgeschlagen</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">Fehler beim Zugriff auf den Server!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"Alle Ressourcen sind auf dem neuesten Stand!"</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">Eine oder mehrere Ressourcen konnten nicht aktualisiert werden!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">Ressourcen erfolgreich aktualisiert!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">Wörterbuchversion</string>
|
||||
<!-- Context: Model Info -->
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
<!-- Context: Button label -->
|
||||
<string name="label_update" comment="Dialog button to update a resource">អាប់ដេត</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">\nមិនអាចភ្ជាប់ទៅម៉ាស៊ីនមេ Keyman បានទេ!\n</string>
|
||||
<string name="cannot_connect" comment="Error message when network connection fails">មិនអាចភ្ជាប់ទៅម៉ាស៊ីនមេរបស់ Keyman បានទេ!</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
<string name="confirm_delete_keyboard" comment="Confirmation to delete a keyboard">តើអ្នកចង់លុបក្ដារចុចនេះឬ?</string>
|
||||
<!-- Context: Keyboard Updates -->
|
||||
|
|
@ -60,10 +60,14 @@
|
|||
<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: Keyboard Help welcome.htm title -->
|
||||
<string name="welcome_package" comment="Title to welcome.htm page (name and version)">ស្វាគមន៍មកកាន់ %1$s</string>
|
||||
<!-- Context: Keyboard app doesn't include FileProvider library needed to view help file -->
|
||||
<string name="fileprovider_undefined" comment="FileProvider library needed to view help file"> ត្រូវការបណ្ណាល័យ FileProvider ដើម្បីមើលឯកសារជំនួយ៖ %1$s</string>
|
||||
<!-- Context: Fatal keyboard error. Will load default keyboard -->
|
||||
<string name="fatal_keyboard_error" comment="Fatal keyboard error (keyboard ID::packageID for language). Will load default keyboard"> មានបញ្ហាធ្ងន់ធ្ងរចំពោះក្ដារចុច %1$s:%2$s សម្រាប់ភាសា %3$s។ កំពុងផ្ទុកក្ដារចុចលំនាំដើម។</string>
|
||||
<!-- Context: Query for associated dictionary -->
|
||||
<string name="query_associated_model" comment="Check if there's an available dictionary to download">កំពុងពិនិត្យរកបញ្ជីពាក្យពាក់ព័ន្ធដើម្បីទាញយក</string>
|
||||
<!-- Context: Model Updates -->
|
||||
|
|
@ -90,10 +94,18 @@
|
|||
<string name="dictionary_download_is_running_in_background" comment="Notification that a dictionary download is still running">បញ្ជីពាក្យដែលបានជ្រើសរើសនេះកំពុងទាញយកហើយ សូមព្យាយាមម្ដងទៀតក្នុងពេលឆាប់ៗ នេះ!</string>
|
||||
<!-- Context: Background download messages-->
|
||||
<string name="dictionary_download_finished" comment="Notification that a dictionary download has finished">ការទាញយកបញ្ជីពាក្យបានចប់សព្វគ្រប់ហើយ!</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="download_failed" comment="Notification that a download failed">ការទាញយកបានបរាជ័យ</string>
|
||||
<!-- Context: Background download messages -->
|
||||
<string name="failed_to_retrieve_file" comment="Failed to retrieve downloaded file">រកមិនឃើញឯកសារដែលបានទាញយក</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_unavailable" comment="Error message when a Keyman server can't be reached">មិនអាចចូលប្រើម៉ាស៊ីនមេបានទេ!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_check_current" comment="Notification that all resources are up to date">"ធនធានទាំងអស់គឺថ្មីបំផុតហើយ!"</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_failed" comment="Notification that a resource update failed">ធនធានមួយឬច្រើនបានបរាជ័យក្នុងការអាប់ដេត!</string>
|
||||
<!-- Context: General Updates -->
|
||||
<string name="update_success" comment="Notification that a resource update successfully updated">បានអាប់ដេតធនធាននានាដោយជោគជ័យ!</string>
|
||||
<!-- Context: Model Info -->
|
||||
<string name="model_version" comment="Title for a dictionary version">ជំនាន់របស់បញ្ជីពាក្យ</string>
|
||||
<!-- Context: Model Info -->
|
||||
|
|
@ -121,6 +133,8 @@
|
|||
<plurals name="keyboard_count">
|
||||
<item quantity="other">(ក្ដារចុច %1$d)</item>
|
||||
</plurals>
|
||||
<!-- Context: "Change Display Language" menu -->
|
||||
<string name="default_locale" comment="Use the device's current locale">ភាសាលំនាំដើម</string>
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Content descriptions -->
|
||||
<!-- Context: Popup menu labels -->
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<!--
|
||||
Base application theme for API 21+. This theme completely replaces
|
||||
AppTheme from res/values/styles.xml on API 21+ devices.
|
||||
-->
|
||||
<style name="AppTheme.Base" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
|
@ -11,23 +11,39 @@
|
|||
res/values-vXX/styles.xml, while customizations related to
|
||||
backward-compatibility can go here.
|
||||
-->
|
||||
<item name="android:listViewStyle">@style/AppTheme.Light.ListView</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textColorHint">@android:color/darker_gray</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<style name="AppTheme.Light.Dialog" parent="Theme.AppCompat.Light.Dialog">
|
||||
<item name="android:background">@android:color/white</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textColorSecondary">@android:color/black</item>
|
||||
<item name="android:textColorHint">@android:color/darker_gray</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert">
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
<item name="android:textColorSecondary">@android:color/black</item>
|
||||
<item name="android:textColorHint">@android:color/darker_gray</item>
|
||||
<item name="android:textColorPrimary">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Dialog.Progress" parent="AppTheme.Dialog.Alert">
|
||||
<item name="android:windowBackground">@android:color/transparent</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider">
|
||||
<item name="android:background">@android:color/holo_red_dark</item>
|
||||
<style name="AppTheme.Light.ListView" parent="android:style/Widget.ListView">
|
||||
<item name="android:listSelector">@drawable/list_item</item>
|
||||
<item name="android:textColor">@android:color/black</item>
|
||||
</style>
|
||||
|
||||
<style name="Divider.horizontal" parent="Divider">
|
||||
<style name="AppTheme.Divider">
|
||||
<item name="android:background">?attr/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.Divider.horizontal" parent="AppTheme.Divider">
|
||||
<item name="android:layout_width">match_parent</item>
|
||||
<item name="android:layout_height">1dp</item>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tavultesoft.kmea.util;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
import org.robolectric.annotation.Config;
|
||||
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import static android.os.Build.VERSION_CODES.LOLLIPOP;
|
||||
import static android.os.Build.VERSION_CODES.N;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MapCompatTest {
|
||||
private final HashMap<String, String> pets = new HashMap<String, String>();
|
||||
|
||||
// Each test sets up a HashMap
|
||||
@Before
|
||||
public void generatePets() {
|
||||
pets.put("dog", "Spot");
|
||||
pets.put("cat", "Mittens");
|
||||
}
|
||||
|
||||
@Rule
|
||||
public ExpectedException exceptionRule = ExpectedException.none();
|
||||
|
||||
@Test
|
||||
public void test_getOrDefault_nullMapThrows() throws NullPointerException {
|
||||
exceptionRule.expect(NullPointerException.class);
|
||||
|
||||
Assert.assertEquals("Peanut", MapCompat.getOrDefault(null, "elephant", "Peanut"));
|
||||
}
|
||||
|
||||
@Config(sdk=LOLLIPOP)
|
||||
@Test
|
||||
public void test_getOrDefault_LollipopKeyExists() {
|
||||
Assert.assertEquals("Spot", MapCompat.getOrDefault(pets, "dog", "none"));
|
||||
}
|
||||
|
||||
@Config(sdk=N)
|
||||
@Test
|
||||
public void test_getOrDefault_NougatKeyExists() {
|
||||
Assert.assertEquals("Spot", MapCompat.getOrDefault(pets, "dog", "none"));
|
||||
}
|
||||
|
||||
@Config(sdk=LOLLIPOP)
|
||||
@Test
|
||||
public void test_getOrDefault_LollipopReturnDefault() {
|
||||
Assert.assertEquals("Peanut", MapCompat.getOrDefault(pets, "elephant", "Peanut"));
|
||||
}
|
||||
|
||||
@Config(sdk=N)
|
||||
@Test
|
||||
public void test_getOrDefault_NougatReturnDefault() {
|
||||
Assert.assertEquals("Peanut", MapCompat.getOrDefault(pets, "elephant", "Peanut"));
|
||||
}
|
||||
|
||||
}
|
||||
4
android/build-help.sh
Normal file → Executable file
|
|
@ -71,8 +71,8 @@ displayInfo "" \
|
|||
|
||||
cd $KEYMAN_ROOT/android/help
|
||||
|
||||
MDLUA="$KEYMAN_ROOT/resources/build/htmlink.lua"
|
||||
MD=`find -name "*.md"`
|
||||
MDLUA="$KEYMAN_ROOT/resources/build/html-link.lua"
|
||||
MD=`find . -name "*.md"`
|
||||
DESTHTM="$THIS_DIR/KMAPro/kMAPro/src/main/assets/info"
|
||||
|
||||
if $DO_HTM; then
|
||||
|
|
|
|||
|
|
@ -364,8 +364,6 @@ int run_test(const km::kbp::path & source, const km::kbp::path & compiled) {
|
|||
}
|
||||
|
||||
std::string string_to_hex(const std::u16string& input) {
|
||||
static const char hex_digits[] = "0123456789ABCDEF";
|
||||
|
||||
std::ostringstream result;
|
||||
result << std::setfill('0') << std::hex << std::uppercase;
|
||||
|
||||
|
|
|
|||
|
|
@ -141,7 +141,9 @@ namespace com.keyman.text.prediction {
|
|||
// Prevents an ugly "flash of unstyled content" layout issue during keyboard load
|
||||
// on our mobile platforms when embedded.
|
||||
lp.currentModel = model;
|
||||
lp.emit('statechange', 'active');
|
||||
if(this.mayPredict) {
|
||||
lp.emit('statechange', 'active');
|
||||
}
|
||||
|
||||
return this.lmEngine.loadModel(source, specType).then(function(config: Configuration) {
|
||||
lp.configuration = config;
|
||||
|
|
@ -173,7 +175,11 @@ namespace com.keyman.text.prediction {
|
|||
return;
|
||||
}
|
||||
|
||||
if(outputTarget) {
|
||||
// Don't attempt predictions when disabled!
|
||||
// invalidateContext otherwise bypasses .predict()'s check against this.
|
||||
if(!this.isActive) {
|
||||
return;
|
||||
} else if(outputTarget) {
|
||||
let transcription = outputTarget.buildTranscriptionFrom(outputTarget, null);
|
||||
this.predict_internal(transcription, true);
|
||||
} else {
|
||||
|
|
@ -407,7 +413,13 @@ namespace com.keyman.text.prediction {
|
|||
this._mayPredict = flag;
|
||||
|
||||
if(oldVal != flag) {
|
||||
this.emit('statechange', flag ? 'active' : 'inactive');
|
||||
// If there's no model to be activated and we've reached this point,
|
||||
// the banner should remain inactive, as it already was.
|
||||
// If it there was one and we've reached this point, we're globally
|
||||
// deactivating, so we're fine.
|
||||
if(this.activeModel) {
|
||||
this.emit('statechange', flag ? 'active' : 'inactive');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,10 @@ namespace com.keyman.keyboards {
|
|||
pad: ActiveKey.DEFAULT_PAD.toString()
|
||||
};
|
||||
|
||||
/** WARNING - DO NOT USE DIRECTLY outside of @keymanapp/keyboard-processor! */
|
||||
id?: string;
|
||||
|
||||
// These are fine.
|
||||
width?: string;
|
||||
pad?: string;
|
||||
layer: string;
|
||||
|
|
@ -29,12 +32,103 @@ namespace com.keyman.keyboards {
|
|||
proportionalX: number;
|
||||
proportionalWidth: number;
|
||||
|
||||
// Keeping things simple here, as this was added LATE in 14.0 beta.
|
||||
// Could definitely extend in the future to instead return an object
|
||||
// that denotes the 'nature' of the key.
|
||||
// - isUnicode
|
||||
// - isHardwareKey
|
||||
// - etc.
|
||||
|
||||
// Reference for the terminology in the comments below:
|
||||
// https://help.keyman.com/developer/current-version/guides/develop/creating-a-touch-keyboard-layout-for-amharic-the-nitty-gritty
|
||||
|
||||
/**
|
||||
* Matches the key code as set within Keyman Developer for the layout.
|
||||
* For example, K_R or U_0020. Denotes either physical keys or virtual keys with custom output,
|
||||
* with no additional metadata like layer or active modifiers.
|
||||
*
|
||||
* Is used to determine the keycode for input events, rule-matching, and keystroke processing.
|
||||
*/
|
||||
public get baseKeyID(): string {
|
||||
if(typeof this.id === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* A unique identifier based on both the key ID & the 'desktop layer' to be used for the key.
|
||||
*
|
||||
* Allows diambiguation of scenarios where the same key ID is used twice within a layer, but
|
||||
* with different innate modifiers. (Refer to https://github.com/keymanapp/keyman/issues/4617)
|
||||
* The 'desktop layer' may be omitted if it matches the key's display layer.
|
||||
*
|
||||
* Examples, given a 'default' display layer, matching keys to Keyman keyboard language:
|
||||
*
|
||||
* ```
|
||||
* "K_Q"
|
||||
* + [K_Q]
|
||||
* "K_Q+shift"
|
||||
* + [K_Q SHIFT]
|
||||
* ```
|
||||
*
|
||||
* Useful when the active layer of an input-event is already known.
|
||||
*/
|
||||
public get coreID(): string {
|
||||
if(typeof this.id === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let baseID = this.id || '';
|
||||
|
||||
if(this.displayLayer != this.layer) {
|
||||
baseID = baseID + '+' + this.layer;
|
||||
}
|
||||
|
||||
return baseID;
|
||||
}
|
||||
|
||||
/**
|
||||
* A keyboard-unique identifier to be used for any display elements representing this key
|
||||
* in user interfaces and/or on-screen keyboards.
|
||||
*
|
||||
* Distinguishes between otherwise-identical keys on different layers of an OSK.
|
||||
* Includes identifying information about the key's display layer.
|
||||
*
|
||||
* Examples, given a 'default' display layer, matching keys to Keyman keyboard language:
|
||||
*
|
||||
* ```
|
||||
* "default-K_Q"
|
||||
* + [K_Q]
|
||||
* "default-K_Q+shift"
|
||||
* + [K_Q SHIFT]
|
||||
* ```
|
||||
*
|
||||
* Useful when only the active keyboard is known about an input event.
|
||||
*/
|
||||
public get elementID(): string {
|
||||
if(typeof this.id === 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.displayLayer + '-' + this.coreID;
|
||||
}
|
||||
|
||||
static polyfill(key: LayoutKey, layout: ActiveLayout, displayLayer: string) {
|
||||
// Add class functions to the existing layout object, allowing it to act as an ActiveLayout.
|
||||
let dummy = new ActiveKey();
|
||||
let proto = Object.getPrototypeOf(dummy);
|
||||
|
||||
for(let prop in dummy) {
|
||||
if(!key.hasOwnProperty(prop)) {
|
||||
key[prop] = dummy[prop];
|
||||
let descriptor = Object.getOwnPropertyDescriptor(proto, prop);
|
||||
if(descriptor) {
|
||||
// It's a computed property! Copy the descriptor onto the key's object.
|
||||
Object.defineProperty(key, prop, descriptor);
|
||||
} else {
|
||||
key[prop] = dummy[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -237,8 +331,8 @@ namespace com.keyman.keyboards {
|
|||
|
||||
populateKeyMap(map: {[keyId: string]: ActiveKey}) {
|
||||
this.key.forEach(function(key: ActiveKey) {
|
||||
if(key.id) {
|
||||
map[key.id] = key;
|
||||
if(key.coreID) {
|
||||
map[key.coreID] = key;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -401,12 +495,12 @@ namespace com.keyman.keyboards {
|
|||
this.row.forEach(function(row: ActiveRow): void {
|
||||
row.key.forEach(function(key: ActiveKey): void {
|
||||
// If the key lacks an ID, just skip it. Sometimes used for padding.
|
||||
if(!key.id) {
|
||||
if(!key.baseKeyID) {
|
||||
return;
|
||||
} else {
|
||||
// Attempt to filter out known non-output keys.
|
||||
// Results in a more optimized distribution.
|
||||
switch(key.id) {
|
||||
switch(key.baseKeyID) {
|
||||
case 'K_SHIFT':
|
||||
case 'K_LOPT':
|
||||
case 'K_ROPT':
|
||||
|
|
@ -418,7 +512,7 @@ namespace com.keyman.keyboards {
|
|||
// Refer to text/codes.ts - these are Keyman-custom "keycodes" used for
|
||||
// layer shifting keys. To be safe, we currently let K_TABBACK and
|
||||
// K_TABFWD through, though we might be able to drop them too.
|
||||
let code = com.keyman.text.Codes[key.id];
|
||||
let code = com.keyman.text.Codes[key.baseKeyID];
|
||||
if(code > 50000 && code < 50011) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -463,7 +557,7 @@ namespace com.keyman.keyboards {
|
|||
distY += dy * layer.rowProportionalHeight;
|
||||
|
||||
let distance = distX * distX + distY * distY;
|
||||
keyDists[key.id] = distance;
|
||||
keyDists[key.coreID] = distance;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ namespace com.keyman.keyboards {
|
|||
"pad"?: string,
|
||||
"sk"?: LayoutKey[]
|
||||
}
|
||||
|
||||
|
||||
export type LayoutRow = {
|
||||
"id": string, // represents a number, though...
|
||||
"key": LayoutKey[]
|
||||
|
|
@ -53,7 +53,7 @@ namespace com.keyman.keyboards {
|
|||
"phone"?: LayoutFormFactor,
|
||||
"tablet"?: LayoutFormFactor
|
||||
}
|
||||
|
||||
|
||||
// This class manages default layout construction for consumption by OSKs without a specified layout.
|
||||
export class Layouts {
|
||||
static dfltCodes=[
|
||||
|
|
@ -104,15 +104,14 @@ namespace com.keyman.keyboards {
|
|||
'leftctrl-shift': '*LCtrlShift*',
|
||||
'rightctrl-shift': '*RCtrlShift*'
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Build a default layout for keyboards with no explicit layout
|
||||
*
|
||||
* @param {Object} PVK keyboard object (as loaded)
|
||||
* @param {Object} kbdDevVersion object representing the version of Developer that compiled the keyboard
|
||||
* @param {number} kbdBitmask keyboard modifier bitmask
|
||||
* @param {string} formFactor
|
||||
* @return {Object}
|
||||
* @param {Object} PVK raw specifications
|
||||
* @param {Keyboard} keyboard keyboard object (as loaded)
|
||||
* @param {string} formFactor (really utils.FormFactor)
|
||||
* @return {LayoutFormFactor}
|
||||
*/
|
||||
static buildDefaultLayout(PVK, keyboard: Keyboard, formFactor: string): LayoutFormFactor {
|
||||
// Build a layout using the default for the device
|
||||
|
|
@ -175,7 +174,7 @@ namespace com.keyman.keyboards {
|
|||
// If there is no predefined layout, even touch layouts will follow the desktop's
|
||||
// setting for the displayUnderlying flag. As the desktop layout uses a different
|
||||
// format for its layout spec, that's found at the field referenced below.
|
||||
layout["displayUnderlying"] = !!keyboard.scriptObject['KDU'];
|
||||
layout["displayUnderlying"] = keyboard ? !!keyboard.scriptObject['KDU'] : false;
|
||||
|
||||
// For desktop devices, we must create all layers, even if invalid.
|
||||
if(formFactor == 'desktop') {
|
||||
|
|
@ -212,14 +211,14 @@ namespace com.keyman.keyboards {
|
|||
// Erase the legacy shifted subkey array.
|
||||
shiftKey['sk'] = [];
|
||||
|
||||
for(var layerID in keyLabels) {
|
||||
for(var layerID in keyLabels) {
|
||||
if(layerID == 'default' || layerID == 'shift') {
|
||||
// These two are accessible from the layer without subkeys.
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create a new subkey for the specified layer so that it will be accessible via OSK.
|
||||
var specialChar = Layouts.modifierSpecials[layerID];
|
||||
var specialChar = Layouts.modifierSpecials[layerID];
|
||||
let subkey: LayoutKey = {
|
||||
id: "K_" + specialChar,
|
||||
text: specialChar,
|
||||
|
|
@ -376,7 +375,7 @@ namespace com.keyman.keyboards {
|
|||
|
||||
/**
|
||||
* Generates a list of potential layer ids for the specified chirality mode.
|
||||
*
|
||||
*
|
||||
* @param {boolean} chiral // Does the keyboard use chiral modifiers or not?
|
||||
*/
|
||||
static generateLayerIds(chiral: boolean): string[] {
|
||||
|
|
@ -401,7 +400,7 @@ namespace com.keyman.keyboards {
|
|||
|
||||
/**
|
||||
* Sets a formatting property for the modifier keys when constructing a default layout for a keyboard.
|
||||
*
|
||||
*
|
||||
* @param {Object} layer // One layer specification
|
||||
* @param {boolean} chiral // Whether or not the keyboard uses chiral modifier information.
|
||||
* @param {string} formFactor // The form factor of the device the layout is being constructed for.
|
||||
|
|
@ -423,7 +422,7 @@ namespace com.keyman.keyboards {
|
|||
case 'K_RSHIFT':
|
||||
if(layerId.indexOf('shift') != -1) {
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
}
|
||||
if(formFactor != 'desktop') {
|
||||
if(layerId != 'default') {
|
||||
key['nextlayer']='default';
|
||||
|
|
@ -439,7 +438,7 @@ namespace com.keyman.keyboards {
|
|||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'K_RCTRL':
|
||||
case 'K_RCONTROL':
|
||||
if(chiral) {
|
||||
|
|
@ -451,7 +450,7 @@ namespace com.keyman.keyboards {
|
|||
case 'K_CONTROL':
|
||||
if(layerId.indexOf('ctrl') != -1) {
|
||||
if(!chiral || (layerId.indexOf('leftctrl') != -1 && layerId.indexOf('rightctrl') != -1)) {
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -461,18 +460,18 @@ namespace com.keyman.keyboards {
|
|||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'K_RALT':
|
||||
if(chiral) {
|
||||
if(layerId.indexOf('rightalt') != -1) {
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case 'K_ALT':
|
||||
if(layerId.indexOf('alt') != -1) {
|
||||
if(!chiral || (layerId.indexOf('leftalt') != -1 && layerId.indexOf('rightalt') != -1)) {
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
key['sp'] = buttonClasses['SHIFT-ON'];
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
|
@ -494,7 +493,7 @@ namespace com.keyman.keyboards {
|
|||
/**
|
||||
* Converts the legacy BK property from pre 10.0 into the KLS keyboard layer spec format,
|
||||
* sparsifying it as possible to pre-emptively check invalid layers.
|
||||
*
|
||||
*
|
||||
* @param {Array} BK keyboard object (as loaded)
|
||||
* @return {Object}
|
||||
*/
|
||||
|
|
@ -525,7 +524,7 @@ namespace com.keyman.keyboards {
|
|||
}
|
||||
}
|
||||
|
||||
// There must always be at least a plain 'default' layer. Array(65).fill('') would be preferable but isn't supported on IE,
|
||||
// There must always be at least a plain 'default' layer. Array(65).fill('') would be preferable but isn't supported on IE,
|
||||
// but buildDefaultLayer will set the defaults for these layers if no entry exists for them in the array due to length.
|
||||
if(typeof KLS['default'] == 'undefined' || ! KLS['default']) {
|
||||
KLS['default'] = [''];
|
||||
|
|
@ -840,4 +839,4 @@ namespace com.keyman.keyboards {
|
|||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,7 +487,11 @@ class ModelCompositor {
|
|||
let postContextTokenization = this.tokenize(postContext);
|
||||
if(postContextTokenization) {
|
||||
// Handles display string for reversions triggered by accepting a suggestion mid-token.
|
||||
revertedPrefix = postContextTokenization.left[postContextTokenization.left.length-1];
|
||||
if(postContextTokenization.left.length > 0) {
|
||||
revertedPrefix = postContextTokenization.left[postContextTokenization.left.length-1];
|
||||
} else {
|
||||
revertedPrefix = '';
|
||||
}
|
||||
revertedPrefix += postContextTokenization.caretSplitsToken ? postContextTokenization.right[0] : '';
|
||||
} else {
|
||||
revertedPrefix = this.wordbreak(postContext);
|
||||
|
|
|
|||
|
|
@ -49,22 +49,22 @@ export function defaultSearchTermToKey(wordform: string): string {
|
|||
*/
|
||||
export function defaultCasedSearchTermToKey(wordform: string, applyCasing: CasingFunction): string {
|
||||
// While this is a bit WET, as the basic `defaultSearchTermToKey` exists and performs some of
|
||||
// the same functions, repetition is the easiest way to allow the function to be safely compiled
|
||||
// the same functions, repetition is the easiest way to allow the function to be safely compiled
|
||||
// with ease by use of `.toString()`.
|
||||
return Array.from(wordform
|
||||
.normalize('NFKD')
|
||||
// Remove any combining diacritics (if input is in NFKD)
|
||||
.replace(/[\u0300-\u036F]/g, '')
|
||||
) // end of `Array.from`
|
||||
.map(c => applyCasing('lower', c))
|
||||
.map(function(c) { return applyCasing('lower', c)})
|
||||
.join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies default casing behavior for lexical models when `languageUsesCasing` is
|
||||
* set to true.
|
||||
* @param casing One of 'lower' (lowercased), 'upper' (uppercased), or 'initial'.
|
||||
*
|
||||
* @param casing One of 'lower' (lowercased), 'upper' (uppercased), or 'initial'.
|
||||
*
|
||||
* 'initial' is designed to cover cases like sentence-initial & proper noun capitalization in English.
|
||||
* This may be overwritten as appropriate in model-specific implementations.
|
||||
* @param text The text to be modified.
|
||||
|
|
@ -76,15 +76,15 @@ export function defaultApplyCasing(casing: CasingForm, text: string): string {
|
|||
case 'upper':
|
||||
return text.toUpperCase();
|
||||
case 'initial':
|
||||
let headCode = text.charCodeAt(0);
|
||||
var headCode = text.charCodeAt(0);
|
||||
// The length of the first code unit, as measured in code points.
|
||||
let headUnitLength = 1;
|
||||
var headUnitLength = 1;
|
||||
|
||||
// Is the first character a high surrogate, indicating possible use of UTF-16
|
||||
// Is the first character a high surrogate, indicating possible use of UTF-16
|
||||
// surrogate pairs? Also, is the string long enough for there to BE a pair?
|
||||
if(text.length > 1 && headCode >= 0xD800 && headCode <= 0xDBFF) {
|
||||
// It's possible, so now we check for low surrogates.
|
||||
let lowSurrogateCode = text.charCodeAt(1);
|
||||
var lowSurrogateCode = text.charCodeAt(1);
|
||||
|
||||
if(lowSurrogateCode >= 0xDC00 && lowSurrogateCode <= 0xDFFF) {
|
||||
// We have a surrogate pair; this pair is the 'first' character.
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { defaultApplyCasing,
|
||||
defaultCasedSearchTermToKey,
|
||||
import { defaultApplyCasing,
|
||||
defaultCasedSearchTermToKey,
|
||||
defaultSearchTermToKey
|
||||
} from "./model-defaults";
|
||||
|
||||
/**
|
||||
* Processes certain defined model behaviors in such a way that the needed closures
|
||||
* may be safely compiled to a JS file and loaded within the LMLayer.
|
||||
*
|
||||
*
|
||||
* This is accomplished by writing out a 'pseudoclosure' within the model's IIFE,
|
||||
* then used to build _actual_ closures at LMLayer load time. This 'pseudoclosure'
|
||||
* will very closely match the organizational patterns of this class in order to
|
||||
* will very closely match the organizational patterns of this class in order to
|
||||
* facilitate the maintenance of this approach.
|
||||
*/
|
||||
export class ModelDefinitions {
|
||||
|
|
@ -18,10 +18,10 @@ export class ModelDefinitions {
|
|||
* A closure fully implementing the model's defined `applyCasing` behavior with
|
||||
* the function parameter preset to the version-appropriate default.
|
||||
* `defaults.applyCasing` is captured as part of the closure.
|
||||
*
|
||||
*
|
||||
* During compilation of some models (such as Trie-based wordlist templated models),
|
||||
* this closure will be directly used as part of searchTermToKey.
|
||||
*
|
||||
*
|
||||
* In compiled code, this will instead be defined in-line as an autogenerated closure
|
||||
* using the other properties of the pseudoclosure.
|
||||
*/
|
||||
|
|
@ -34,7 +34,7 @@ export class ModelDefinitions {
|
|||
*
|
||||
* During compilation of some models (such as Trie-based wordlist templated models),
|
||||
* this closure will be directly utilized when compiling the lexicon.
|
||||
*
|
||||
*
|
||||
* In compiled code, this will instead be defined in-line as an autogenerated closure
|
||||
* using the other properties of the pseudoclosure.
|
||||
*/
|
||||
|
|
@ -42,7 +42,7 @@ export class ModelDefinitions {
|
|||
|
||||
/**
|
||||
* Contains embedded 'default' implementations that may be needed for
|
||||
* closures in the compiled version, annotated with the current version
|
||||
* closures in the compiled version, annotated with the current version
|
||||
* of Developer.
|
||||
*/
|
||||
private defaults: {
|
||||
|
|
@ -54,7 +54,7 @@ export class ModelDefinitions {
|
|||
|
||||
/**
|
||||
* Contains the model-specific definitions specified in the model's source.
|
||||
*
|
||||
*
|
||||
* These definitions may expect `defaults.applyCasing` as a parameter in
|
||||
* their final closures.
|
||||
*/
|
||||
|
|
@ -95,10 +95,10 @@ export class ModelDefinitions {
|
|||
} else if(modelSource.languageUsesCasing == false) {
|
||||
this.model.searchTermToKey = defaultSearchTermToKey;
|
||||
} else {
|
||||
// If languageUsesCasing is not defined, then we use pre-14.0 behavior,
|
||||
// If languageUsesCasing is not defined, then we use pre-14.0 behavior,
|
||||
// which expects a lowercased default.
|
||||
this.model.searchTermToKey = defaultCasedSearchTermToKey;
|
||||
// Needed to provide pre-14.0 default lowercasing as part of the
|
||||
// Needed to provide pre-14.0 default lowercasing as part of the
|
||||
// search-term keying operation.
|
||||
this.defaults.applyCasing = defaultApplyCasing;
|
||||
// For compile-time use.
|
||||
|
|
@ -120,18 +120,18 @@ export class ModelDefinitions {
|
|||
/**
|
||||
* Writes out a compiled JS version of the pseudoclosure, preserving all function
|
||||
* implementations.
|
||||
*
|
||||
*
|
||||
* This should be written to the file within the same IIFE as the model but BEFORE
|
||||
* the model itself, as the model will need to refer to the definitions herein.
|
||||
*/
|
||||
compileDefinitions(): string {
|
||||
let defn: string = '';
|
||||
defn += `let ${PSEUDOCLOSURE} = {\n`
|
||||
defn += `var ${PSEUDOCLOSURE} = {\n`
|
||||
|
||||
// ----------------------
|
||||
// START - the 'defaults', which are common within the same Developer version.
|
||||
defn += ` defaults: {\n version: "${this.defaults.version}"`;
|
||||
|
||||
|
||||
// Only write out `applyCasing` if and when it is needed.
|
||||
if(this.defaults.applyCasing) {
|
||||
defn += `,\n applyCasing: ${this.defaults.applyCasing.toString()}`;
|
||||
|
|
@ -153,7 +153,7 @@ export class ModelDefinitions {
|
|||
// END - model-specific definitions
|
||||
|
||||
// ----------------------
|
||||
// START - compiled closures. Given those definitions, write out the
|
||||
// START - compiled closures. Given those definitions, write out the
|
||||
// pseudoclosure-referencing closures for the needed methods.
|
||||
|
||||
// We should be able to define these closures in-line with the object's
|
||||
|
|
@ -163,7 +163,7 @@ export class ModelDefinitions {
|
|||
if(this.model.applyCasing) {
|
||||
// A major potential issue: if the user wants to call extra custom functions that they've written.
|
||||
//
|
||||
// `applyCasing` recursion SHOULD be fine if they write `this.applyCasing() and forward all arguments
|
||||
// `applyCasing` recursion SHOULD be fine if they write `this.applyCasing() and forward all arguments
|
||||
// appropriately, as it will be known as `applyCasing` on the runtime `this` (`model`) object.
|
||||
//
|
||||
// Similarly, as long as any helper functions are similarly compiled and stored as part of `model`,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('Model definition pseudoclosures', function () {
|
|||
// Note: not written the Turkish way. Turns out 'İ'.toLowerCase() decomposes the result,
|
||||
// which would have made for a fairly yucky test.
|
||||
['Istanbul', 'istanbul', 'istanbul'],
|
||||
|
||||
|
||||
// The DEFAULT function is NOT responsible for understanding the Turkish
|
||||
// case regarding the lowercasing of:
|
||||
// 'I' U+0048 LATIN CAPITAL LETTER I to 'ı' U+0131 LATIN SMALL LETTER DOTLESS I
|
||||
|
|
@ -29,14 +29,14 @@ describe('Model definition pseudoclosures', function () {
|
|||
|
||||
// full-width romaji has corresponding lowercased versions:
|
||||
['AESTHETIC', 'aesthetic', 'aesthetic'],
|
||||
|
||||
|
||||
// "skýlos" is Greek for dog 🇬🇷🐶
|
||||
// starts with an 's' and ends with an 's'
|
||||
// which are DIFFERENT CHARACTERS in lowercased Greek!
|
||||
['σκύλος', 'σκύλος', 'σκυλος'],
|
||||
['ΣΚΥΛΟΣ', 'σκυλος', 'σκυλοσ'], // the keyed version after lowercasing doesn't know how
|
||||
['ΣΚΥΛΟΣ', 'σκυλος', 'σκυλοσ'], // the keyed version after lowercasing doesn't know how
|
||||
// to make the distinction. Both 'Σ's have the same char-code.
|
||||
|
||||
|
||||
// Uncased syntax and numbers should pass through unscathed:
|
||||
['1234.?!', '1234.?!', '1234.?!']
|
||||
];
|
||||
|
|
@ -67,19 +67,19 @@ describe('Model definition pseudoclosures', function () {
|
|||
// Note: not written the Turkish way. Turns out 'İ'.toLowerCase() decomposes the result,
|
||||
// which would have made for a fairly yucky test.
|
||||
['Istanbul', 'Istanbul'],
|
||||
|
||||
|
||||
['DİYARBAKIR', 'DIYARBAKIR'],
|
||||
|
||||
// full-width romaji has corresponding capitalized versions:
|
||||
['AESTHETIC', 'AESTHETIC'],
|
||||
|
||||
|
||||
// "skýlos" is Greek for dog 🇬🇷🐶
|
||||
// starts with an 's' and ends with an 's'
|
||||
// which are DIFFERENT CHARACTERS in lowercased Greek!
|
||||
['σκύλος', 'σκυλος'],
|
||||
['ΣΚΥΛΟΣ', 'ΣΚΥΛΟΣ'], // the keyed version after lowercasing doesn't know how
|
||||
['ΣΚΥΛΟΣ', 'ΣΚΥΛΟΣ'], // the keyed version after lowercasing doesn't know how
|
||||
// to make the distinction. Both 'Σ's have the same char-code.
|
||||
|
||||
|
||||
// Uncased syntax and numbers should pass through unscathed:
|
||||
['1234.?!', '1234.?!']
|
||||
];
|
||||
|
|
@ -105,19 +105,19 @@ describe('Model definition pseudoclosures', function () {
|
|||
// Note: not written the Turkish way. Turns out 'İ'.toLowerCase() decomposes the result,
|
||||
// which would have made for a fairly yucky test.
|
||||
['Istanbul', 'istanbul'],
|
||||
|
||||
|
||||
['DİYARBAKIR', 'diyarbakir'],
|
||||
|
||||
// full-width romaji has corresponding capitalized versions:
|
||||
['AESTHETIC', 'aesthetic'],
|
||||
|
||||
|
||||
// "skýlos" is Greek for dog 🇬🇷🐶
|
||||
// starts with an 's' and ends with an 's'
|
||||
// which are DIFFERENT CHARACTERS in lowercased Greek!
|
||||
['σκύλος', 'σκυλος'],
|
||||
['ΣΚΥΛΟΣ', 'σκυλοσ'], // the keyed version after lowercasing doesn't know how
|
||||
['ΣΚΥΛΟΣ', 'σκυλοσ'], // the keyed version after lowercasing doesn't know how
|
||||
// to make the distinction. Both 'Σ's have the same char-code.
|
||||
|
||||
|
||||
// Uncased syntax and numbers should pass through unscathed:
|
||||
['1234.?!', '1234.?!']
|
||||
];
|
||||
|
|
@ -143,7 +143,7 @@ describe('Model definition pseudoclosures', function () {
|
|||
.replace(/ı/g, 'I')
|
||||
.replace(/i/g, 'İ'));
|
||||
case 'initial':
|
||||
return turkishCasing('upper', text.charAt(0), defaultApplyCasing) + text.substr(1);
|
||||
return turkishCasing('upper', text.charAt(0), defaultApplyCasing) + text.substr(1);
|
||||
default:
|
||||
return text;
|
||||
}
|
||||
|
|
@ -156,7 +156,7 @@ describe('Model definition pseudoclosures', function () {
|
|||
return Array.from(wordform
|
||||
.normalize('NFC') // Mostly to avoid decomposing 'İ'
|
||||
) // end of `Array.from`
|
||||
.map(c => applyCasing('lower', c)) // Will use custom `applyCasing` definition!
|
||||
.map(function(c) { return applyCasing('lower', c)}) // Will use custom `applyCasing` definition!
|
||||
.join('');
|
||||
},
|
||||
sources: [],
|
||||
|
|
@ -167,14 +167,14 @@ describe('Model definition pseudoclosures', function () {
|
|||
|
||||
const testCases: [string, string, string][] = [
|
||||
['İstanbul', 'istanbul', 'istanbul'],
|
||||
|
||||
|
||||
// The DEFAULT function is NOT responsible for understanding the Turkish
|
||||
// case regarding the lowercasing of:
|
||||
// 'I' U+0048 LATIN CAPITAL LETTER I to 'ı' U+0131 LATIN SMALL LETTER DOTLESS I
|
||||
// For Turkic languages, the recommendation is to make a
|
||||
// custom applyCasing function:
|
||||
['DİYARBAKIR', 'diyarbakır', 'diyarbakır'],
|
||||
|
||||
|
||||
// Uncased syntax and numbers should pass through unscathed:
|
||||
['1234.?!', '1234.?!', '1234.?!']
|
||||
];
|
||||
|
|
|
|||
|
|
@ -1,48 +1,48 @@
|
|||
# History and Versioning
|
||||
|
||||
In Keyman 14.0, history and versioning is controlled as part of the build process,
|
||||
In Keyman 14.0, history and versioning is controlled as part of the build process,
|
||||
and unified. We no longer have a separate version or history for each project. The
|
||||
controlling version number is in /VERSION.md and the master history is in /HISTORY.md.
|
||||
controlling version number is in `/VERSION.md` and the master history is in `/HISTORY.md`.
|
||||
|
||||
While there may be multiple versions of HISTORY.md in different branches, the
|
||||
While there may be multiple versions of `HISTORY.md` in different branches, the
|
||||
**master** branch is source of truth and is the version that is published to the
|
||||
website.
|
||||
|
||||
# VERSION.md
|
||||
## VERSION.md
|
||||
|
||||
The version shown in VERSION.md is the upcoming release version. This means:
|
||||
The version shown in `VERSION.md` is the upcoming release version. This means:
|
||||
|
||||
1. A snapshot of the repository will show the changes relevant to that version
|
||||
2. The build system will simply use that version number when building.
|
||||
3. If a release does not exist for the version shown, then you are at the
|
||||
3. If a release does not exist for the version shown, then you are at the
|
||||
bleeding edge.
|
||||
|
||||
# TIER.md
|
||||
## TIER.md
|
||||
|
||||
The release tier is updated in TIER.md?
|
||||
The release tier is updated in `TIER.md`?
|
||||
|
||||
# How history and version are updated
|
||||
## How history and version are updated
|
||||
|
||||
For Alpha versions (**master**), the version number is incremented at the end of a
|
||||
successful Nightly build. This means that during a single day, there may be multiple
|
||||
For Alpha versions (**master**), the version number is incremented at the end of a
|
||||
successful Nightly build. This means that during a single day, there may be multiple
|
||||
builds with the same version number. Only the last one of these is kept.
|
||||
|
||||
A similar process will be followed for Beta versions.
|
||||
|
||||
For Stable versions, the version number should be manually incremented in a pull
|
||||
For Stable versions, the version number should be manually incremented in a pull
|
||||
request (these are much rarer).
|
||||
|
||||
## History format
|
||||
### History format
|
||||
|
||||
```
|
||||
## <yyyy-mm-dd> <version> <tier>
|
||||
```text
|
||||
## <yyyy-mm-dd> <version> <tier>
|
||||
|
||||
<type> [(<scope>[/<sub-scope>])]: <message> [(#<PR>)]
|
||||
```
|
||||
|
||||
For example:
|
||||
|
||||
```
|
||||
```text
|
||||
## 2020-01-27 13.0.65 beta
|
||||
feat(developer/compiler): Hotkeys defined in .kmn no longer need to be quoted (#2432)
|
||||
chore: Release beta (#2555)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
npm packages
|
||||
============
|
||||
# npm packages
|
||||
|
||||
This is a guide for how the various Keyman `npm` packages are developed
|
||||
and published.
|
||||
|
|
@ -9,14 +8,12 @@ Currently, we're using TeamCity for this.
|
|||
|
||||
Packages are **never** published from a developer's machine.
|
||||
|
||||
List of current npm packages
|
||||
----------------------------
|
||||
## List of current npm packages
|
||||
|
||||
* `@keymanapp/models-types` -- located at `common/models-types`
|
||||
* `@keymanapp/lexical-model-compiler` -- located at `developer/js`
|
||||
* `@keymanapp/models-types` -- located at `common/models-types`
|
||||
* `@keymanapp/lexical-model-compiler` -- located at `developer/js`
|
||||
|
||||
For CI developers
|
||||
-----------------
|
||||
## For CI developers
|
||||
|
||||
### In general
|
||||
|
||||
|
|
@ -25,46 +22,53 @@ server.
|
|||
|
||||
This is typically done with:
|
||||
|
||||
./build.sh -test
|
||||
```bash
|
||||
./build.sh -test
|
||||
```
|
||||
|
||||
Once the build succeeds and the tests pass, the version is set in
|
||||
`package.json`:
|
||||
|
||||
./build.sh -version 12.0.${BUILD_NUMBER} -tier ${tier}
|
||||
```bash
|
||||
./build.sh -version 12.0.${BUILD_NUMBER} -tier ${tier}
|
||||
```
|
||||
|
||||
Now you can publish! Use the following script for this (again, **only on the CI server**!)
|
||||
|
||||
./build.sh -publish-to-npm -tier ${tier}
|
||||
```bash
|
||||
./build.sh -publish-to-npm -tier ${tier}
|
||||
```
|
||||
|
||||
It is then uploaded to the npm package directory. **Ensure that the
|
||||
compiled sources are included in the tarball**. For example,
|
||||
|
||||
$ npm publish
|
||||
npm notice 📦 @keymanapp/lexical-model-compiler@0.0.0
|
||||
npm notice === Tarball Contents ===
|
||||
npm notice 12.4kB dist/lexical-model-compiler/build-trie.js
|
||||
npm notice 1.2kB dist/kmlmc.js
|
||||
npm notice 3.0kB dist/kmlmi.js
|
||||
npm notice 1.4kB dist/kmlmp.js
|
||||
npm notice 5.0kB dist/package-compiler/kmp-compiler.js
|
||||
npm notice 55B dist/package-compiler/kmp-json-file.js
|
||||
npm notice 552B dist/package-compiler/kps-file.js
|
||||
npm notice 4.6kB dist/lexical-model-compiler/lexical-model-compiler.js
|
||||
npm notice 315B dist/lexical-model-compiler/lexical-model.js
|
||||
npm notice 158B dist/util/min-keyman-version.js
|
||||
npm notice 4.5kB dist/model-info-compiler/model-info-compiler.js
|
||||
npm notice 57B dist/lexical-model-compiler/model-info-file.js
|
||||
npm notice 341B dist/lexical-model-compiler/stub.js
|
||||
npm notice 115B dist/util/sysexits.js
|
||||
npm notice 2.1kB dist/util/util.js
|
||||
npm notice 1.4kB package.json
|
||||
npm notice 1.0kB README.md
|
||||
```bash
|
||||
$ npm publish
|
||||
npm notice 📦 @keymanapp/lexical-model-compiler@0.0.0
|
||||
npm notice === Tarball Contents ===
|
||||
npm notice 12.4kB dist/lexical-model-compiler/build-trie.js
|
||||
npm notice 1.2kB dist/kmlmc.js
|
||||
npm notice 3.0kB dist/kmlmi.js
|
||||
npm notice 1.4kB dist/kmlmp.js
|
||||
npm notice 5.0kB dist/package-compiler/kmp-compiler.js
|
||||
npm notice 55B dist/package-compiler/kmp-json-file.js
|
||||
npm notice 552B dist/package-compiler/kps-file.js
|
||||
npm notice 4.6kB dist/lexical-model-compiler/lexical-model-compiler.js
|
||||
npm notice 315B dist/lexical-model-compiler/lexical-model.js
|
||||
npm notice 158B dist/util/min-keyman-version.js
|
||||
npm notice 4.5kB dist/model-info-compiler/model-info-compiler.js
|
||||
npm notice 57B dist/lexical-model-compiler/model-info-file.js
|
||||
npm notice 341B dist/lexical-model-compiler/stub.js
|
||||
npm notice 115B dist/util/sysexits.js
|
||||
npm notice 2.1kB dist/util/util.js
|
||||
npm notice 1.4kB package.json
|
||||
npm notice 1.0kB README.md
|
||||
```
|
||||
|
||||
For every release of **Keyman Developer**, the CI publishes a release of
|
||||
`@keymanapp/lexical-model-compiler`. The version number is locked with
|
||||
the particular version of Keyman Developer.
|
||||
|
||||
|
||||
### `set_npm_version`
|
||||
|
||||
This helper function sets the version in the `package.json` for the
|
||||
|
|
|
|||
3
ios/.gitignore
vendored
|
|
@ -17,6 +17,9 @@ engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/keyman-sentr
|
|||
Keyman 2*
|
||||
Carthage
|
||||
|
||||
# Default resource packages (which may be downloaded automatically via build-script
|
||||
engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/*.kmp
|
||||
|
||||
# Generated offline help
|
||||
keyman/Keyman/resources/OfflineHelp.bundle/Contents/Resources
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ displayInfo "" \
|
|||
|
||||
cd $KEYMAN_ROOT/ios/help
|
||||
|
||||
MDLUA="$KEYMAN_ROOT/resources/build/htmlink.lua"
|
||||
MDLUA="$KEYMAN_ROOT/resources/build/html-link.lua"
|
||||
MD=`find . -name "*.md"`
|
||||
DESTHTM="$THIS_DIR/keyman/Keyman/resources/OfflineHelp.bundle/Contents/Resources"
|
||||
|
||||
|
|
|
|||
|
|
@ -213,6 +213,8 @@
|
|||
CEA1486C2407808F00C6ECD2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CEA1486F2407808F00C6ECD2 /* Localizable.strings */; };
|
||||
CEA1486D2407808F00C6ECD2 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CEA1486F2407808F00C6ECD2 /* Localizable.strings */; };
|
||||
CEA14870240780E100C6ECD2 /* ResourceInfoView.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEA14872240780E100C6ECD2 /* ResourceInfoView.xib */; };
|
||||
CEA31E5825FB51D500534484 /* No-defaults early 14.0.bundle in Resources */ = {isa = PBXBuildFile; fileRef = CEA31E5725FB51D400534484 /* No-defaults early 14.0.bundle */; };
|
||||
CEA31E6025FB5BC200534484 /* Early 14.0 with defaults.bundle in Resources */ = {isa = PBXBuildFile; fileRef = CEA31E5F25FB5BC200534484 /* Early 14.0 with defaults.bundle */; };
|
||||
CEA6BA9D249872E8002D44CE /* Simple 13.0 Migration.bundle in Resources */ = {isa = PBXBuildFile; fileRef = CEA6BA9C249872E7002D44CE /* Simple 13.0 Migration.bundle */; };
|
||||
CEA70CCF24CAC3AE001C12E6 /* Obsoletions.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA70CCE24CAC3AE001C12E6 /* Obsoletions.swift */; };
|
||||
CEA9670924BEC4030035AACF /* ResourceUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEA9670824BEC4030035AACF /* ResourceUpdateTests.swift */; };
|
||||
|
|
@ -514,6 +516,8 @@
|
|||
CEA14871240780E100C6ECD2 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/ResourceInfoView.xib; sourceTree = "<group>"; };
|
||||
CEA14874240780EF00C6ECD2 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
CEA148772407869200C6ECD2 /* km */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = km; path = km.lproj/ResourceInfoView.strings; sourceTree = "<group>"; };
|
||||
CEA31E5725FB51D400534484 /* No-defaults early 14.0.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "No-defaults early 14.0.bundle"; sourceTree = "<group>"; };
|
||||
CEA31E5F25FB5BC200534484 /* Early 14.0 with defaults.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "Early 14.0 with defaults.bundle"; sourceTree = "<group>"; };
|
||||
CEA6BA9C249872E7002D44CE /* Simple 13.0 Migration.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = "Simple 13.0 Migration.bundle"; sourceTree = "<group>"; };
|
||||
CEA70CCE24CAC3AE001C12E6 /* Obsoletions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Obsoletions.swift; sourceTree = "<group>"; };
|
||||
CEA9670824BEC4030035AACF /* ResourceUpdateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ResourceUpdateTests.swift; sourceTree = "<group>"; };
|
||||
|
|
@ -887,6 +891,8 @@
|
|||
CE37C38923FCC9AF007031C6 /* resources */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CEA31E5F25FB5BC200534484 /* Early 14.0 with defaults.bundle */,
|
||||
CEA31E5725FB51D400534484 /* No-defaults early 14.0.bundle */,
|
||||
CEA9670C24BEEFF80035AACF /* khmer_angkor update-base.bundle */,
|
||||
CE02EE0B24A5863100D58F92 /* Queries.bundle */,
|
||||
CEA6BA9C249872E7002D44CE /* Simple 13.0 Migration.bundle */,
|
||||
|
|
@ -1338,6 +1344,7 @@
|
|||
9ADC45A022E1895D004C78C6 /* LanguageLMDetailViewController.xib in Resources */,
|
||||
CE973D832484A5DC00F66045 /* PackageJSON.bundle in Resources */,
|
||||
CEF888D223FE6ADF00667693 /* No-defaults 10.0 Migration.bundle in Resources */,
|
||||
CEA31E5825FB51D500534484 /* No-defaults early 14.0.bundle in Resources */,
|
||||
CE9B440F23FE4E7500499CAB /* 12.0 Ad-hoc Migration.bundle in Resources */,
|
||||
CE8B5BB22491DA540075CCB0 /* 13.0 Cloud to Package Migration.bundle in Resources */,
|
||||
9A0FCA0922D7C58B00D33F86 /* Keyman.bundle in Resources */,
|
||||
|
|
@ -1345,6 +1352,7 @@
|
|||
CE02EE0C24A5863100D58F92 /* Queries.bundle in Resources */,
|
||||
CEA6BA9D249872E8002D44CE /* Simple 13.0 Migration.bundle in Resources */,
|
||||
CEF888D323FE6ADF00667693 /* Simple 10.0 Migration.bundle in Resources */,
|
||||
CEA31E6025FB5BC200534484 /* Early 14.0 with defaults.bundle in Resources */,
|
||||
CE37C38F23FCD52F007031C6 /* Lexical Models.bundle in Resources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
|
|
|||
|
|
@ -69,20 +69,28 @@ public enum Key {
|
|||
|
||||
public enum Defaults {
|
||||
private static let font = Font(family: "LatinWeb", source: ["DejaVuSans.ttf"], size: nil)
|
||||
public static let keyboard = InstallableKeyboard(id: "sil_euro_latin",
|
||||
name: "EuroLatin (SIL)",
|
||||
languageID: "en",
|
||||
languageName: "English",
|
||||
version: "1.9.1",
|
||||
isRTL: false,
|
||||
font: font,
|
||||
oskFont: nil,
|
||||
isCustom: false)
|
||||
public static let lexicalModel = InstallableLexicalModel(id: "nrc.en.mtnt",
|
||||
name: "English dictionary (MTNT)",
|
||||
languageID: "en",
|
||||
version: "0.1.4",
|
||||
isCustom: false)
|
||||
|
||||
public static let keyboardID = FullKeyboardID(keyboardID: "sil_euro_latin", languageID: "en")
|
||||
public static let lexicalModelID = FullLexicalModelID(lexicalModelID: "nrc.en.mtnt", languageID: "en")
|
||||
|
||||
public static var keyboardPackage: KeyboardKeymanPackage = {
|
||||
let bundledKMP = Resources.bundle.url(forResource: keyboardID.keyboardID, withExtension: ".kmp")!
|
||||
return try! ResourceFileManager.shared.prepareKMPInstall(from: bundledKMP) as! KeyboardKeymanPackage
|
||||
}()
|
||||
|
||||
public static let lexicalModelPackage: LexicalModelKeymanPackage = {
|
||||
let bundledKMP = Resources.bundle.url(forResource: lexicalModelID.lexicalModelID, withExtension: ".model.kmp")!
|
||||
return try! ResourceFileManager.shared.prepareKMPInstall(from: bundledKMP) as! LexicalModelKeymanPackage
|
||||
}()
|
||||
|
||||
// Must be retrieved from their packages!
|
||||
public static let keyboard: InstallableKeyboard = {
|
||||
return keyboardPackage.findResource(withID: keyboardID)!
|
||||
}()
|
||||
|
||||
public static let lexicalModel: InstallableLexicalModel = {
|
||||
return lexicalModelPackage.findResource(withID: lexicalModelID)!
|
||||
}()
|
||||
}
|
||||
|
||||
public enum Resources {
|
||||
|
|
|
|||
|
|
@ -190,6 +190,16 @@ public class KeymanPackage {
|
|||
distributionMethod = downloadURL != nil ? .cloud : .unknown
|
||||
self.downloadURL = downloadURL
|
||||
}
|
||||
|
||||
// Used to store metadata for newly-downloaded results from the keyboard search.
|
||||
// To save a version, we require also having a known URL.
|
||||
internal init(downloadURL: URL, version: Version) {
|
||||
self.timestampForLastQuery = NSDate().timeIntervalSince1970
|
||||
self.latestVersion = version.fullString
|
||||
self.downloadURL = downloadURL
|
||||
|
||||
distributionMethod = .cloud
|
||||
}
|
||||
}
|
||||
|
||||
static private let kmpFile = "kmp.json"
|
||||
|
|
|
|||
|
|
@ -16,10 +16,10 @@ private enum MigrationLevel {
|
|||
|
||||
struct VersionResourceSet {
|
||||
var version: Version
|
||||
var resources: [AnyLanguageResource]
|
||||
var resources: [AnyLanguageResourceFullID]
|
||||
// but how to handle deprecation?
|
||||
|
||||
init(version: Version, resources: [AnyLanguageResource]) {
|
||||
init(version: Version, resources: [AnyLanguageResourceFullID]) {
|
||||
self.version = version
|
||||
self.resources = resources
|
||||
}
|
||||
|
|
@ -29,54 +29,14 @@ public enum Migrations {
|
|||
static let resourceHistory: [VersionResourceSet] = {
|
||||
let font = Font(family: "LatinWeb", source: ["DejaVuSans.ttf"], size: nil)
|
||||
|
||||
let european = InstallableKeyboard(id: "european",
|
||||
name: "EuroLatin Keyboard",
|
||||
languageID: "en",
|
||||
languageName: "English",
|
||||
version: "1.7",
|
||||
isRTL: false,
|
||||
font: font,
|
||||
oskFont: nil,
|
||||
isCustom: false)
|
||||
let european = FullKeyboardID(keyboardID: "european", languageID: "en")
|
||||
|
||||
// Default keyboard in version 10.0 (and likely before)
|
||||
let european2 = InstallableKeyboard(id: "european2",
|
||||
name: "EuroLatin2 Keyboard",
|
||||
languageID: "en",
|
||||
languageName: "English",
|
||||
version: "1.6",
|
||||
isRTL: false,
|
||||
font: font,
|
||||
oskFont: nil,
|
||||
isCustom: false)
|
||||
let european2 = FullKeyboardID(keyboardID: "european2", languageID: "en")
|
||||
|
||||
let sil_euro_latin_1_8_1 = InstallableKeyboard(id: "sil_euro_latin",
|
||||
name: "EuroLatin (SIL)",
|
||||
languageID: "en",
|
||||
languageName: "English",
|
||||
version: "1.8.1",
|
||||
isRTL: false,
|
||||
font: font,
|
||||
oskFont: nil,
|
||||
isCustom: false)
|
||||
let sil_euro_latin = FullKeyboardID(keyboardID: "sil_euro_latin", languageID: "en")
|
||||
|
||||
let sil_euro_latin_1_9_1 = InstallableKeyboard(id: "sil_euro_latin",
|
||||
name: "EuroLatin (SIL)",
|
||||
languageID: "en",
|
||||
languageName: "English",
|
||||
version: "1.9.1",
|
||||
isRTL: false,
|
||||
font: font,
|
||||
oskFont: nil,
|
||||
isCustom: false)
|
||||
|
||||
let nrc_en_mtnt_0_1_2 = InstallableLexicalModel(id: "nrc.en.mtnt",
|
||||
name: "English dictionary (MTNT)",
|
||||
languageID: "en",
|
||||
version: "0.1.2",
|
||||
isCustom: false)
|
||||
|
||||
let nrc_en_mtnt = Defaults.lexicalModel
|
||||
let nrc_en_mtnt = FullLexicalModelID(lexicalModelID: "nrc.en.mtnt", languageID: "en")
|
||||
|
||||
// Unknown transition point: european
|
||||
// Before v11: european2
|
||||
|
|
@ -86,17 +46,13 @@ public enum Migrations {
|
|||
|
||||
let legacy_resources = VersionResourceSet(version: Version.fallback, resources: [european])
|
||||
let v10_resources = VersionResourceSet(version: Version("10.0")!, resources: [european2])
|
||||
let v11_resources = VersionResourceSet(version: Version("11.0")!, resources: [sil_euro_latin_1_8_1])
|
||||
let v12_resources = VersionResourceSet(version: Version("12.0")!, resources: [sil_euro_latin_1_8_1, nrc_en_mtnt_0_1_2])
|
||||
let v13_resources = VersionResourceSet(version: Version("13.0.65")!, resources: [sil_euro_latin_1_8_1, nrc_en_mtnt])
|
||||
let v14_resources = VersionResourceSet(version: Version("14.0.0")!, resources: [sil_euro_latin_1_9_1, nrc_en_mtnt])
|
||||
let v11_resources = VersionResourceSet(version: Version("11.0")!, resources: [sil_euro_latin])
|
||||
let v12_resources = VersionResourceSet(version: Version("12.0")!, resources: [sil_euro_latin, nrc_en_mtnt])
|
||||
|
||||
timeline.append(legacy_resources)
|
||||
timeline.append(v10_resources)
|
||||
timeline.append(v11_resources)
|
||||
timeline.append(v12_resources)
|
||||
timeline.append(v13_resources)
|
||||
timeline.append(v14_resources)
|
||||
|
||||
return timeline
|
||||
}()
|
||||
|
|
@ -190,9 +146,16 @@ public enum Migrations {
|
|||
|
||||
static func updateResources(storage: Storage) {
|
||||
var lastVersion = engineVersion
|
||||
if (lastVersion ?? Version.fallback) >= Version.latestFeature {
|
||||
// We're either current or have just been downgraded; no need to do modify resources.
|
||||
// If it's a downgrade, it's near-certainly a testing environment.
|
||||
|
||||
// Will always seek to update default resources on version upgrades,
|
||||
// even if only due to the build component of the version.
|
||||
//
|
||||
// This may make intentional downgrading of our default resources tedious,
|
||||
// as there's (currently) no way to detect if a user intentionally did so before
|
||||
// the app upgrade.
|
||||
if lastVersion != nil, lastVersion! > Version.currentTagged {
|
||||
// We've just been downgraded; no need to modify resources.
|
||||
// If it's a downgrade, it's near-certainly a testing environment
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -219,8 +182,21 @@ public enum Migrations {
|
|||
}
|
||||
}
|
||||
|
||||
var userKeyboards = Storage.active.userDefaults.userKeyboards ?? []
|
||||
var userModels = Storage.active.userDefaults.userLexicalModels ?? []
|
||||
|
||||
var hasVersionDefaults = true
|
||||
var oldDefaultKbd: InstallableKeyboard? = nil
|
||||
var oldDefaultLex: InstallableLexicalModel? = nil
|
||||
|
||||
// Only assigned a non-nil value if the old default's ID matches the current default's.
|
||||
// Used for version comparisons to ensure we don't unnecessarily downgrade.
|
||||
var currentDefaultKbdVersion: Version? = nil
|
||||
var currentDefaultLexVersion: Version? = nil
|
||||
|
||||
if lastVersion != nil && lastVersion != Version.freshInstall {
|
||||
// Time to deinstall the old version's resources.
|
||||
// Time to check on the old version's default resources.
|
||||
// The user may have updated them, and possibly even beyond the currently-packaged version.
|
||||
// First, find the most recent version with a listed history.
|
||||
let possibleHistories: [VersionResourceSet] = resourceHistory.compactMap { set in
|
||||
if set.version <= lastVersion! {
|
||||
|
|
@ -236,44 +212,46 @@ public enum Migrations {
|
|||
let resources = possibleHistories[possibleHistories.count-1].resources
|
||||
|
||||
resources.forEach { res in
|
||||
if let kbd = res as? InstallableKeyboard {
|
||||
var userKeyboards = Storage.active.userDefaults.userKeyboards
|
||||
|
||||
if let kbd = res as? FullKeyboardID {
|
||||
// Does not remove the deprecated keyboard's files - just the registration.
|
||||
userKeyboards?.removeAll(where: { kbd2 in
|
||||
return kbd.id == kbd2.id && kbd.languageID == kbd2.languageID
|
||||
})
|
||||
Storage.active.userDefaults.userKeyboards = userKeyboards
|
||||
} else if let lex = res as? InstallableLexicalModel {
|
||||
var userModels = Storage.active.userDefaults.userLexicalModels
|
||||
|
||||
if let match = userKeyboards.first(where: { $0.fullID == kbd }) {
|
||||
if match.fullID == Defaults.keyboard.fullID {
|
||||
currentDefaultKbdVersion = Version(match.version)
|
||||
}
|
||||
oldDefaultKbd = match
|
||||
} else {
|
||||
hasVersionDefaults = false
|
||||
}
|
||||
} else if let lex = res as? FullLexicalModelID {
|
||||
// Parallels the issue with deprecated files for keyboards.
|
||||
userModels?.removeAll(where: { lex2 in
|
||||
return lex.id == lex2.id && lex.languageID == lex2.languageID
|
||||
})
|
||||
Storage.active.userDefaults.userLexicalModels = userModels
|
||||
if let match = userModels.first(where: { $0.fullID == lex }) {
|
||||
if match.fullID == Defaults.lexicalModel.fullID {
|
||||
currentDefaultLexVersion = Version(match.version)
|
||||
}
|
||||
oldDefaultLex = match
|
||||
} else {
|
||||
hasVersionDefaults = false
|
||||
}
|
||||
} // else Not yet implemented
|
||||
}
|
||||
|
||||
// The user has customized their default resources; Keyman will refrain from
|
||||
// changing the user's customizations.
|
||||
if !hasVersionDefaults {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Now to install the new version's resources.
|
||||
var userKeyboards = Storage.active.userDefaults.userKeyboards ?? []
|
||||
var userModels = Storage.active.userDefaults.userLexicalModels ?? []
|
||||
let defaultsNeedBackup = (lastVersion ?? Version.fallback) < Version.defaultsNeedBackup
|
||||
var installKbd = false
|
||||
var installLex = false
|
||||
|
||||
// Don't add the keyboard a second time if it's already installed and backed up. Can happen
|
||||
// if multiple Keyman versions match.
|
||||
if !userKeyboards.contains(where: { kbd in
|
||||
kbd.id == Defaults.keyboard.id && kbd.languageID == Defaults.keyboard.languageID
|
||||
}) {
|
||||
// First the keyboard. If it needs an update:
|
||||
if currentDefaultKbdVersion ?? Version("0.0")! <= Version(Defaults.keyboard.version)! || defaultsNeedBackup {
|
||||
// Remove old installation.
|
||||
userKeyboards.removeAll(where: { $0.fullID == oldDefaultKbd?.fullID })
|
||||
userKeyboards = [Defaults.keyboard] + userKeyboards // Make sure the default goes in the first slot!
|
||||
Storage.active.userDefaults.userKeyboards = userKeyboards
|
||||
|
||||
installKbd = true
|
||||
}
|
||||
if(defaultsNeedBackup || installKbd) {
|
||||
do {
|
||||
try Storage.active.installDefaultKeyboard(from: Resources.bundle)
|
||||
} catch {
|
||||
|
|
@ -281,15 +259,12 @@ public enum Migrations {
|
|||
}
|
||||
}
|
||||
|
||||
if !userModels.contains(where: { lex in
|
||||
lex.id == Defaults.lexicalModel.id && lex.languageID == Defaults.lexicalModel.languageID
|
||||
}) {
|
||||
if currentDefaultLexVersion ?? Version("0.0")! < Version(Defaults.lexicalModel.version)! || defaultsNeedBackup {
|
||||
// Remove old installation
|
||||
userModels.removeAll(where: { $0.fullID == oldDefaultLex?.fullID} )
|
||||
userModels = [Defaults.lexicalModel] + userModels
|
||||
Storage.active.userDefaults.userLexicalModels = userModels
|
||||
|
||||
installLex = true
|
||||
}
|
||||
if(defaultsNeedBackup || installLex) {
|
||||
do {
|
||||
try Storage.active.installDefaultLexicalModel(from: Resources.bundle)
|
||||
} catch {
|
||||
|
|
@ -298,7 +273,7 @@ public enum Migrations {
|
|||
}
|
||||
|
||||
// Store the version we just upgraded to.
|
||||
storage.userDefaults.lastEngineVersion = Version.latestFeature
|
||||
storage.userDefaults.lastEngineVersion = Version.currentTagged
|
||||
}
|
||||
|
||||
static func migrateUserDefaultsToStructs(storage: Storage) {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,11 @@ public class Version: NSObject, Comparable {
|
|||
case alpha
|
||||
case beta
|
||||
case stable
|
||||
|
||||
// In case we wish to do any tier-based comparisons.
|
||||
public static func < (lhs: Version.Tier, rhs: Version.Tier) -> Bool {
|
||||
return lhs.rawValue < rhs.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
public static let fallback = Version("1.0")!
|
||||
|
|
|
|||
|
|
@ -157,6 +157,28 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
progressView?.isHidden = false
|
||||
}
|
||||
|
||||
private func failWithAlert(for error: Error) {
|
||||
let errorTitle = NSLocalizedString("alert-no-connection-title", bundle: engineBundle, comment: "")
|
||||
let alert = ResourceFileManager.shared.buildSimpleAlert(title: errorTitle, message: error.localizedDescription) {
|
||||
// If we are in a UINavigationViewController and are not its root view...
|
||||
if let navVC = self.navigationController, navVC.viewControllers.first != self {
|
||||
navVC.popViewController(animated: true)
|
||||
} else {
|
||||
self.dismiss(animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
self.present(alert, animated: true)
|
||||
}
|
||||
|
||||
public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
|
||||
failWithAlert(for: error)
|
||||
}
|
||||
|
||||
public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
|
||||
failWithAlert(for: error)
|
||||
}
|
||||
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
|
@ -232,6 +254,15 @@ public class KeyboardSearchViewController: UIViewController, WKNavigationDelegat
|
|||
// Illegal state - we already checked this.
|
||||
fatalError()
|
||||
}
|
||||
|
||||
// We've likely installed the package, and we already know
|
||||
// the package's current publishing state - we literally just
|
||||
// downloaded it from the cloud as part of the keyboard search.
|
||||
//
|
||||
// Why query later when we know the query's answer NOW?
|
||||
let publishState = KeymanPackage.DistributionStateMetadata(downloadURL: packageURL, version: package.version)
|
||||
|
||||
Storage.active.userDefaults.cachedPackageQueryMetadata[package.key] = publishState
|
||||
}
|
||||
|
||||
downloadManager.downloadPackage(withKey: packageKey, from: packageURL, withNotifications: true, completionBlock: downloadClosure)
|
||||
|
|
|
|||
|
|
@ -137,7 +137,7 @@ public class ResourceFileManager {
|
|||
*/
|
||||
public func prepareKMPInstall(from url: URL) throws -> KeymanPackage {
|
||||
// Once selected, start the standard install process.
|
||||
log.info("Installing KMP from \(url)")
|
||||
log.info("Opening 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]
|
||||
|
|
|
|||
|
|
@ -15,6 +15,105 @@ class MigrationTests: XCTestCase {
|
|||
TestUtils.standardTearDown()
|
||||
}
|
||||
|
||||
// // Uncomment this and set up whatever resources are needed for your test bundle in order to build one!
|
||||
// func testForMigrationBundleConstruction() {
|
||||
// do {
|
||||
// if let sil_euro_latin = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.silEuroLatinKMP) as? KeyboardKeymanPackage {
|
||||
// try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.sil_euro_latin.fullID, from: sil_euro_latin)
|
||||
// } else {
|
||||
// XCTFail()
|
||||
// }
|
||||
//
|
||||
// if let sencoten_kbd_kmp = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.Keyboards.fvSencotenKMP) as? KeyboardKeymanPackage {
|
||||
// try ResourceFileManager.shared.install(resourceWithID: TestUtils.Keyboards.fv_sencoten.fullID, from: sencoten_kbd_kmp)
|
||||
// } else {
|
||||
// XCTFail()
|
||||
// }
|
||||
//
|
||||
// if let mtntKMP = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.mtntKMP) as? LexicalModelKeymanPackage {
|
||||
// try ResourceFileManager.shared.install(resourceWithID: TestUtils.LexicalModels.mtnt.fullID,
|
||||
// from: mtntKMP)
|
||||
// } else {
|
||||
// XCTFail()
|
||||
// }
|
||||
//
|
||||
// if let sencoten_lm_kmp = try ResourceFileManager.shared.prepareKMPInstall(from: TestUtils.LexicalModels.sencotenKMP) as? LexicalModelKeymanPackage {
|
||||
// try ResourceFileManager.shared.install(resourceWithID: TestUtils.LexicalModels.sencoten.fullID,
|
||||
// from: sencoten_lm_kmp)
|
||||
// } else {
|
||||
// XCTFail()
|
||||
// }
|
||||
//
|
||||
// } catch {
|
||||
// XCTFail("File system commands failed.")
|
||||
// }
|
||||
//
|
||||
// // Set the desired version entry for your format, if different from the actual local version.
|
||||
// // Only use if you're sure the specified version has a matching file format.
|
||||
// Storage.active.userDefaults.lastEngineVersion = Version.packageBasedFileReorg
|
||||
// Storage.active.userDefaults.migrationLevel = 20 // migratedForKMP
|
||||
//
|
||||
// // Note: you may need to manually pause at a certain (marked) point within this method
|
||||
// // for everything to successfully write out!
|
||||
// if let saveState = try? TestUtils.EngineStateBundler.createBundle(withName: "Early 14.0 with defaults") {
|
||||
// self.add(saveState)
|
||||
// } else {
|
||||
// XCTFail()
|
||||
// }
|
||||
// }
|
||||
|
||||
func testSimpleEarly14Migration() {
|
||||
// A case where the user has both SENCOTEN installed for both keyboard & lexical model.
|
||||
// Mirrors `testNoDefaultsEarly14Migration`, but where sil_euro_latin
|
||||
// was not removed by the user; in contrast, the base resources
|
||||
// should be auto-updated.
|
||||
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.simple_14)
|
||||
Migrations.migrate(storage: Storage.active)
|
||||
Migrations.updateResources(storage: Storage.active)
|
||||
|
||||
let userDefaults = Storage.active.userDefaults
|
||||
let userKeyboards = userDefaults.userKeyboards ?? []
|
||||
let userLexicalModels = userDefaults.userLexicalModels ?? []
|
||||
|
||||
XCTAssertEqual(userKeyboards.count, 2)
|
||||
// Because there's a lexical model update, it installs the whole package.
|
||||
// Not exactly ideal, but correct for pre-existing behavior, which
|
||||
// installs the whole default package instead of a single language-code pairing.
|
||||
XCTAssertEqual(userLexicalModels.count, 4)
|
||||
|
||||
//[s]il_[e]uro_[l]atin
|
||||
let kbdSEL = userKeyboards.first(where: { $0.fullID == TestUtils.Keyboards.sil_euro_latin.fullID })
|
||||
XCTAssertNotNil(kbdSEL)
|
||||
// Because there's a keyboard update (1.9.3 vs 1.9.1, at the time of writing).
|
||||
// we expect a more recent version than the testing version.
|
||||
XCTAssertGreaterThan(Version(kbdSEL!.version)!, Version(TestUtils.Keyboards.sil_euro_latin.version)!)
|
||||
XCTAssertTrue(userLexicalModels.contains(where: { $0.fullID == TestUtils.LexicalModels.mtnt.fullID }))
|
||||
|
||||
XCTAssertTrue(userKeyboards.contains(where: { $0.fullID == TestUtils.Keyboards.fv_sencoten.fullID }))
|
||||
XCTAssertTrue(userLexicalModels.contains(where: { $0.fullID == TestUtils.LexicalModels.sencoten.fullID }))
|
||||
}
|
||||
|
||||
func testNoDefaultsEarly14Migration() {
|
||||
// A case where the user only has SENCOTEN installed for both keyboard & lexical model.
|
||||
// sil_euro_latin was explicitly removed by the user.
|
||||
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.noDefault_14)
|
||||
Migrations.migrate(storage: Storage.active)
|
||||
Migrations.updateResources(storage: Storage.active)
|
||||
|
||||
let userDefaults = Storage.active.userDefaults
|
||||
let userKeyboards = userDefaults.userKeyboards ?? []
|
||||
let userLexicalModels = userDefaults.userLexicalModels ?? []
|
||||
|
||||
XCTAssertEqual(userKeyboards.count, 1)
|
||||
XCTAssertEqual(userLexicalModels.count, 1)
|
||||
|
||||
XCTAssertFalse(userKeyboards.contains(where: { $0.fullID == TestUtils.Keyboards.sil_euro_latin.fullID }))
|
||||
XCTAssertFalse(userLexicalModels.contains(where: { $0.fullID == TestUtils.LexicalModels.mtnt.fullID }))
|
||||
|
||||
XCTAssertTrue(userKeyboards.contains(where: { $0.fullID == TestUtils.Keyboards.fv_sencoten.fullID }))
|
||||
XCTAssertTrue(userLexicalModels.contains(where: { $0.fullID == TestUtils.LexicalModels.sencoten.fullID }))
|
||||
}
|
||||
|
||||
func testComplexVersion13Migration() {
|
||||
TestUtils.Migrations.applyBundleToFileSystem(TestUtils.Migrations.cloud_to_kmp_13)
|
||||
Migrations.migrate(storage: Storage.active)
|
||||
|
|
@ -30,7 +129,7 @@ class MigrationTests: XCTestCase {
|
|||
|
||||
sil_euro_latin_kbds.forEach {
|
||||
XCTAssertEqual($0.packageID, "sil_euro_latin")
|
||||
XCTAssertEqual($0.version, TestUtils.Keyboards.sil_euro_latin.version)
|
||||
XCTAssertEqual($0.version, Defaults.keyboard.version)
|
||||
}
|
||||
|
||||
let sil_euro_latin_package = ResourceFileManager.shared.installedPackages.first(where: { $0.id == "sil_euro_latin" }) as! KeyboardKeymanPackage
|
||||
|
|
@ -52,7 +151,7 @@ class MigrationTests: XCTestCase {
|
|||
|
||||
let userDefaults = Storage.active.userDefaults
|
||||
|
||||
// SIL EuroLatin should be updated to 1.9.1. The lexical model version should be unchanged.
|
||||
// SIL EuroLatin should be updated to the currently-bundled version. The lexical model version should be unchanged.
|
||||
|
||||
let defaultKbd = userDefaults.userKeyboards![0]
|
||||
XCTAssertEqual(defaultKbd.id, Defaults.keyboard.id)
|
||||
|
|
|
|||
|
|
@ -47,14 +47,21 @@ extension TestUtils {
|
|||
try FileManager.default.copyItem(at: storageDirectory, to: bundleConstructionURL.appendingPathComponent("Library").appendingPathComponent("keyman"))
|
||||
|
||||
log.info("Preferences directory source: \(preferencesDirectory)")
|
||||
let pListPath = bundleConstructionURL.appendingPathComponent("Library").appendingPathComponent("Preferences")
|
||||
|
||||
let testEngineFilename = "com.keyman.testing.KeymanEngineTestHost.plist"
|
||||
let appGroupFilename = "group.KM4I.plist"
|
||||
// If trying to ensure that UserDefaults is properly synchronized for this copy operation,
|
||||
// go to the folder for the link above in Finder and watch the file to ensure it's properly
|
||||
// fleshed out.
|
||||
//
|
||||
// Set a breakpoint to the following line, only resuming when the plist is fully flushed to
|
||||
// the file system. You may need to rename it in the resulting bundle.
|
||||
try FileManager.default.copyItem(at: preferencesDirectory, to:
|
||||
bundleConstructionURL.appendingPathComponent("Library").appendingPathComponent("Preferences"))
|
||||
try FileManager.default.copyItem(at: preferencesDirectory, to: pListPath)
|
||||
|
||||
// sleep(1000)
|
||||
|
||||
try FileManager.default.moveItem(at: pListPath.appendingPathComponent(testEngineFilename), to: pListPath.appendingPathComponent(appGroupFilename))
|
||||
}
|
||||
|
||||
let attachmentFile = try Zip.quickZipFiles([bundleConstructionURL], fileName: "bundleArchive")
|
||||
|
|
|
|||
|
|
@ -17,5 +17,13 @@ extension TestUtils {
|
|||
languageID: "en",
|
||||
version: "0.1.4",
|
||||
isCustom: false)
|
||||
|
||||
static let sencotenKMP = TestUtils.lexicalModelsBundle.url(forResource: "nrc.str.sencoten.model",
|
||||
withExtension: "kmp")!
|
||||
static let sencoten = InstallableLexicalModel(id: "nrc.str.sencoten",
|
||||
name: "SENĆOŦEN (Saanich Dialect) Lexical Model",
|
||||
languageID: "str",
|
||||
version: "1.0.5",
|
||||
isCustom: false)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -29,6 +29,8 @@ extension TestUtils {
|
|||
static let adhoc_12 = TestUtils.findSubBundle(forResource: "12.0 Ad-hoc Migration", ofType: ".bundle")
|
||||
static let simple_10 = TestUtils.findSubBundle(forResource: "Simple 10.0 Migration", ofType: ".bundle")
|
||||
static let noDefault_10 = TestUtils.findSubBundle(forResource: "No-defaults 10.0 Migration", ofType: ".bundle")
|
||||
static let noDefault_14 = TestUtils.findSubBundle(forResource: "No-defaults early 14.0", ofType: "bundle")
|
||||
static let simple_14 = TestUtils.findSubBundle(forResource: "Early 14.0 with defaults", ofType: "bundle")
|
||||
|
||||
// The default keyboard in version 10.0.
|
||||
static let european2_font = Font(family: "LatinWeb", source: ["DejaVuSans.ttf"], size: nil)
|
||||
|
|
@ -81,10 +83,10 @@ extension TestUtils {
|
|||
}
|
||||
}
|
||||
|
||||
static func getVersionHistory(for version: Version) -> [AnyLanguageResource] {
|
||||
static func getVersionHistory(for version: Version) -> [AnyLanguageResourceFullID] {
|
||||
let resourceHistory = KeymanEngine.Migrations.resourceHistory
|
||||
|
||||
let match: [[AnyLanguageResource]] = resourceHistory.compactMap() { entry in
|
||||
let match: [[AnyLanguageResourceFullID]] = resourceHistory.compactMap() { entry in
|
||||
if entry.version == version {
|
||||
return entry.resources
|
||||
} else {
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 4.7 KiB |
|
After Width: | Height: | Size: 21 KiB |