From 4273140149904ec500c7d7bbe9c72ec679815839 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 19 Sep 2023 14:42:42 +0700 Subject: [PATCH 01/57] change(android): Add image banner Toggle from suggestion to image banner --- .../KMEA/app/src/main/assets/android-host.js | 49 +++++++++++++----- .../java/com/keyman/engine/KMKeyboard.java | 34 +++++++++++- .../engine/KMKeyboardWebViewClient.java | 2 +- .../java/com/keyman/engine/KMManager.java | 36 +++++++++++-- .../drawable-land-mdpi/banner_landscape.png | Bin 0 -> 2856 bytes .../res/drawable-mdpi/banner_portrait.png | Bin 0 -> 3142 bytes 6 files changed, 102 insertions(+), 19 deletions(-) create mode 100644 android/KMEA/app/src/main/res/drawable-land-mdpi/banner_landscape.png create mode 100644 android/KMEA/app/src/main/res/drawable-mdpi/banner_portrait.png diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index afa2825e33d..7d89018d851 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -8,6 +8,7 @@ if(window.parent && window.parent.jsInterface && !window.jsInterface) { var device = window.jsInterface.getDeviceType(); var oskHeight = Math.ceil(window.jsInterface.getKeyboardHeight() / window.devicePixelRatio); var oskWidth = 0; +var bannerHeight = 0; var fragmentToggle = 0; var sentryManager = new KeymanSentryManager({ @@ -37,11 +38,13 @@ function init() { oninserttext: insertText, root:'./' }).then(function () { // Note: For non-upgraded API 21, arrow functions will break the keyboard! - const bannerHeight = Math.ceil(window.jsInterface.getDefaultBannerHeight() / window.devicePixelRatio); + bannerHeight = Math.ceil(window.jsInterface.getDefaultBannerHeight() / window.devicePixelRatio); + if (bannerHeight > 0) { - // The OSK is not available until initialization is complete. - keyman.osk.bannerView.activeBannerHeight = bannerHeight; - keyman.refreshOskLayout(); + // The OSK is not available until initialization is complete. + keyman.osk.bannerView.activeBannerHeight = bannerHeight; + keyman.refreshOskLayout(); + } }); keyman.addEventListener('keyboardloaded', setIsChiral); @@ -53,6 +56,18 @@ function init() { notifyHost('pageLoaded'); } +function showBanner(flag) { + if (keyman.osk) { + keyman.osk.bannerController.setOptions({'alwaysShow': flag}); + } +} + +function setBannerImage(path) { + if (keyman.osk) { + keyman.osk.bannerController.setOptions({"imagePath": path}); + } +} + function notifyHost(event, params) { console_debug('notifyHost(event='+event+',params='+params+')'); // TODO: Update all other host notifications to use notifyHost instead of directly setting window.location.hash @@ -65,13 +80,21 @@ function notifyHost(event, params) { } // Update the KMW banner height +// h is in dpi (different from iOS) function setBannerHeight(h) { if (h > 0) { - var osk = keyman.osk; - osk.banner.height = Math.ceil(h / window.devicePixelRatio); + // The banner itself may not be loaded yet. This will preemptively help set + // its eventual display height. + bannerHeight = Math.ceil(h / window.devicePixelRatio); + + if (keyman.osk) { + keyman.osk.bannerView.activeBannerHeight = bannerHeight; + } } - // Refresh KMW OSK + + // Refresh KMW's OSK keyman.refreshOskLayout(); + doResetContext(); } function setOskHeight(h) { @@ -82,6 +105,7 @@ function setOskHeight(h) { keyman.core.activeKeyboard.refreshLayouts(); } keyman.refreshOskLayout(); + doResetContext(); } function setOskWidth(w) { @@ -168,18 +192,13 @@ function enableSuggestions(model, mayPredict, mayCorrect) { keyman.core.languageProcessor.mayPredict = mayPredict; keyman.core.languageProcessor.mayCorrect = mayCorrect; - registerModel(model); + keyman.addModel(model); } function setBannerOptions(mayPredict) { keyman.core.languageProcessor.mayPredict = mayPredict; } -function registerModel(model) { - //window.console.log('registerModel: ' + model); - keyman.addModel(model); -} - function resetContext() { keyman.resetContext(); } @@ -269,6 +288,10 @@ function hideKeyboard() { window.location.hash = 'hideKeyboard' + fragmentToggle; } +function doResetContext() { + keyman.resetContext(); +} + function showKeyboard() { // Refresh KMW OSK keyman.refreshOskLayout(); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 6471e875101..708e6db6aa5 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -88,15 +88,25 @@ final class KMKeyboard extends WebView { * Banner state value: "blank" - no banner available. */ protected static final String KM_BANNER_STATE_BLANK = "blank"; + + /** + * Banner state value: "image" - display an image in the banner + */ + protected static final String KM_BANNER_STATE_IMAGE = "image"; + /** * Banner state value: "suggestion" - dictionary suggestions are shown. */ protected static final String KM_BANNER_STATE_SUGGESTION = "suggestion"; + // Tiling black background generated from https://elmah.io/tools/base64-image-encoder/ + // TODO: Replace with image/css? + protected static final String KM_BANNER_BLANK_IMAGE_PATH = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; /** * Current banner state. */ - protected static String currentBanner = KM_BANNER_STATE_BLANK; + protected static String currentBanner = KM_BANNER_STATE_IMAGE; private static String txtFont = ""; private static String oskFont = null; @@ -105,6 +115,10 @@ final class KMKeyboard extends WebView { private GestureDetector gestureDetector; private static ArrayList kbEventListeners = null; + // Stores the current image for use by the Banner + // when predictive text is not active + private String bannerImgPath; + // Facilitates a 'lazy init' - we'll only check the preference when it matters, // rather than at construction time. private Boolean _shouldShowHelpBubble = null; @@ -400,6 +414,9 @@ public void onConfigurationChanged(Configuration newConfig) { int bannerHeight = KMManager.getBannerHeight(context); int oskHeight = KMManager.getKeyboardHeight(context); + if (this.bannerImgPath != null) { + loadJavascript(KMString.format("setBannerImage('%s')", this.bannerImgPath)); + } loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); loadJavascript(KMString.format("setOskWidth(%d)", newConfig.screenWidthDp)); loadJavascript(KMString.format("setOskHeight(%d)", oskHeight)); @@ -435,7 +452,7 @@ protected void toggleSuggestionBanner(HashMap associatedLexicalM //reset banner state if new language has no lexical model if (currentBanner != null && currentBanner.equals(KM_BANNER_STATE_SUGGESTION) && associatedLexicalModel == null) { - setCurrentBanner(KMKeyboard.KM_BANNER_STATE_BLANK); + setCurrentBanner(KMKeyboard.KM_BANNER_STATE_IMAGE); } if(keyboardChanged) { @@ -653,6 +670,19 @@ public boolean setKeyboard(String packageID, String keyboardID, String languageI return retVal; } + public void showBanner(boolean flag) { + String jsFormat = "showBanner('%s')"; + String jsString = KMString.format(jsFormat, flag ? "true" : "false"); + loadJavascript(jsString); + } + + public void setBannerImage(String path) { + this.bannerImgPath = path; // Save the path in case delayed initialization is needed + Log.d(TAG, "Banner image path: " + path); + String jsString = KMString.format("setBannerImage('%s')", path); + loadJavascript(jsString); + } + public void setChirality(boolean flag) { this.isChiral = flag; } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java index e7188a94f96..e2670b9c619 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java @@ -174,7 +174,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true); } kmKeyboard.setCurrentBanner((isModelActive && modelPredictionPref) ? - KMKeyboard.KM_BANNER_STATE_SUGGESTION : KMKeyboard.KM_BANNER_STATE_BLANK); + KMKeyboard.KM_BANNER_STATE_SUGGESTION : KMKeyboard.KM_BANNER_STATE_IMAGE); RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); kmKeyboard.setLayoutParams(params); } else if (url.indexOf("suggestPopup") >= 0) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index cbcb28053a1..d0ed8ec9b96 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -612,11 +612,15 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) { InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP); InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); + InAppKeyboard.showBanner(true); + InAppKeyboard.setBannerImage(InAppKeyboard.KM_BANNER_BLANK_IMAGE_PATH); keyboard = InAppKeyboard; webViewClient = InAppKeyboardWebViewClient; } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) { SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM); SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); + SystemKeyboard.showBanner(true); + SystemKeyboard.setBannerImage(SystemKeyboard.KM_BANNER_BLANK_IMAGE_PATH); keyboard = SystemKeyboard; webViewClient = SystemKeyboardWebViewClient; } @@ -1370,7 +1374,13 @@ public static void deleteLexicalModel(Context context, int position, boolean sil KeyboardPickerActivity.deleteLexicalModel(context, position, silenceNotification); } - public static boolean setBannerOptions(boolean mayPredict) { + /** + * setBannerOptions - Update KMW whether to generate predictions. + * For now, also display banner + * @param mayPredict - boolean whether KMW should generate predictions + * @return boolean - Success + */ + public static boolean setBannerOptions(boolean mayPredict) { String url = KMString.format("setBannerOptions(%s)", mayPredict); if (InAppKeyboard != null) { InAppKeyboard.loadJavascript(url); @@ -1379,6 +1389,26 @@ public static boolean setBannerOptions(boolean mayPredict) { if (SystemKeyboard != null) { SystemKeyboard.loadJavascript(url); } + + // For now, always display banner + showBanner(true); + return true; + } + + /** + * showBanner - Update KMW whether to display banner. + * For now, always keep displaying banner + * @param flag - boolean whether KMW should display banner + * @return boolean - Success + */ + public static boolean showBanner(boolean flag) { + if (InAppKeyboard != null) { + InAppKeyboard.showBanner(flag); + } + + if (SystemKeyboard != null) { + SystemKeyboard.showBanner(flag); + } return true; } @@ -1845,9 +1875,9 @@ public static void removeKeyboardEventListener(OnKeyboardEventListener listener) public static int getBannerHeight(Context context) { int bannerHeight = 0; - if (InAppKeyboard != null && InAppKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_SUGGESTION)) { + if (InAppKeyboard != null && !InAppKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_BLANK)) { bannerHeight = (int) context.getResources().getDimension(R.dimen.banner_height); - } else if (SystemKeyboard != null && SystemKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_SUGGESTION)) { + } else if (SystemKeyboard != null && !SystemKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_BLANK)) { bannerHeight = (int) context.getResources().getDimension(R.dimen.banner_height); } return bannerHeight; diff --git a/android/KMEA/app/src/main/res/drawable-land-mdpi/banner_landscape.png b/android/KMEA/app/src/main/res/drawable-land-mdpi/banner_landscape.png new file mode 100644 index 0000000000000000000000000000000000000000..f4c27d6c614881a98bb40ccd78ef50c459d5dfaa GIT binary patch literal 2856 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 Date: Mon, 25 Sep 2023 13:43:18 +0700 Subject: [PATCH 02/57] fix(android/engine): Try green banner --- android/KMEA/app/src/main/assets/android-host.js | 12 ++++++++++-- .../main/java/com/keyman/engine/KMKeyboard.java | 15 +++++++++++---- .../main/java/com/keyman/engine/KMManager.java | 6 +----- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 7d89018d851..312b4a627d5 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -57,15 +57,23 @@ function init() { } function showBanner(flag) { + window.console.log("setOptions{'alwaysShow': " + flag + "}"); if (keyman.osk) { - keyman.osk.bannerController.setOptions({'alwaysShow': flag}); + keyman.osk.bannerController.setOptions({"alwaysShow": flag}); } } +// Set the path to use for the image banner +// path - String starting with "data:image/png;base64," function setBannerImage(path) { if (keyman.osk) { - keyman.osk.bannerController.setOptions({"imagePath": path}); + keyman.osk.bannerController.setOptions({ + "imagePath": path, + "alwaysShow": true}); } + + // Refresh KMW's OSK + keyman.refreshOskLayout(); } function notifyHost(event, params) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 708e6db6aa5..58b1c7d9e3f 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -102,7 +102,9 @@ final class KMKeyboard extends WebView { // Tiling black background generated from https://elmah.io/tools/base64-image-encoder/ // TODO: Replace with image/css? protected static final String KM_BANNER_BLANK_IMAGE_PATH = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; + //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; + //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCC="; + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; /** * Current banner state. */ @@ -671,14 +673,19 @@ public boolean setKeyboard(String packageID, String keyboardID, String languageI } public void showBanner(boolean flag) { - String jsFormat = "showBanner('%s')"; - String jsString = KMString.format(jsFormat, flag ? "true" : "false"); + String jsString = KMString.format("showBanner(%b)", flag); loadJavascript(jsString); } public void setBannerImage(String path) { this.bannerImgPath = path; // Save the path in case delayed initialization is needed - Log.d(TAG, "Banner image path: " + path); + String logString = ""; + if (path != null && path.contains("base64") || path.length() > 256) { + logString = ""; + } else { + logString = path; + } + KMLog.LogInfo(TAG, KMString.format("Banner image path: (%s).", logString)); String jsString = KMString.format("setBannerImage('%s')", path); loadJavascript(jsString); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index d0ed8ec9b96..2a56ce9d237 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -612,14 +612,12 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) { InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP); InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - InAppKeyboard.showBanner(true); InAppKeyboard.setBannerImage(InAppKeyboard.KM_BANNER_BLANK_IMAGE_PATH); keyboard = InAppKeyboard; webViewClient = InAppKeyboardWebViewClient; } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) { SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM); SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - SystemKeyboard.showBanner(true); SystemKeyboard.setBannerImage(SystemKeyboard.KM_BANNER_BLANK_IMAGE_PATH); keyboard = SystemKeyboard; webViewClient = SystemKeyboardWebViewClient; @@ -1381,7 +1379,7 @@ public static void deleteLexicalModel(Context context, int position, boolean sil * @return boolean - Success */ public static boolean setBannerOptions(boolean mayPredict) { - String url = KMString.format("setBannerOptions(%s)", mayPredict); + String url = KMString.format("setBannerOptions(%b)", mayPredict); // TODO? if (InAppKeyboard != null) { InAppKeyboard.loadJavascript(url); } @@ -1390,8 +1388,6 @@ public static boolean setBannerOptions(boolean mayPredict) { SystemKeyboard.loadJavascript(url); } - // For now, always display banner - showBanner(true); return true; } From d3872ee23397300306ef74d9a12bafd4e0b6caa0 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 25 Sep 2023 13:43:39 +0700 Subject: [PATCH 03/57] fix(web): Start with image banner --- web/src/engine/osk/src/banner/bannerView.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 556ec61762d..e6d0c835e17 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -45,12 +45,12 @@ interface BannerViewEventMap { * * This should help to avoid wasting computational resources. * * It will listen to ModelManager events and automatically swap Banner * instances as appropriate: - * * The option `persistentBanner == true` is designed to replicate current + * * The option `alwaysShow == true` is designed to replicate current * iOS system keyboard behavior. * * When true, an `ImageBanner` will be displayed. * * If false, it will be replaced with a `BlankBanner` of zero height, * corresponding to our current default lack of banner. - * * It will not automatically set `persistentBanner == true`; + * * It will not automatically set `alwaysShow == true`; * this must be set by the iOS app, and only under the following conditions: * * `keyman.isEmbedded == true` * * `device.OS == 'ios'` @@ -210,13 +210,13 @@ export class BannerController { * Sets options used to tweak the automatic `Banner` * control logic used by `BannerManager`. * @param optionSpec An object specifying one or more of the following options: - * * `persistentBanner` (boolean) When `true`, ensures that a `Banner` + * * `alwaysShow` (boolean) When `true`, ensures that a `Banner` * is always displayed, even when no predictive model exists * for the active language. * * Default: `false` * * `imagePath` (URL string) Specifies the file path to use for an - * `ImageBanner` when `persistentBanner` is `true` and no predictive model exists. + * `ImageBanner` when `alwaysShow` is `true` and no predictive model exists. * * Default: `''`. * * `enablePredictions` (boolean) Turns KMW predictions @@ -243,7 +243,8 @@ export class BannerController { // If no banner instance exists yet, go with a safe, blank initialization. if(!this.container.banner) { - this.selectBanner('inactive'); + //this.selectBanner('inactive'); + this.setBanner('image'); } } } From 3a785f5e68aa7d61bbf3cf7f64e7af890119abfb Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 25 Sep 2023 14:27:49 +0700 Subject: [PATCH 04/57] chore(android/app): Use white banner theme --- .../main/java/com/keyman/android/SystemKeyboard.java | 4 ++++ .../src/main/java/com/keyman/engine/KMKeyboard.java | 1 - .../src/main/java/com/keyman/engine/KMManager.java | 11 +++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 697825094bf..ff9e928a7d8 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -44,6 +44,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static final String TAG = "SystemKeyboard"; + private static final String BANNER_THEME_WHITE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j/P///38GOgAmelgyatGoRaMWjVo0atGoRQQAAD1MBB5gNLThAAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; + /** * Main initialization of the input method component. Be sure to call * to super class. @@ -71,6 +73,8 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, BANNER_THEME_WHITE); + boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 58b1c7d9e3f..12ecce012ce 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -103,7 +103,6 @@ final class KMKeyboard extends WebView { // TODO: Replace with image/css? protected static final String KM_BANNER_BLANK_IMAGE_PATH = //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; - //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCC="; "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; /** * Current banner state. diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 2a56ce9d237..9d64ca0d20d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -1391,6 +1391,17 @@ public static boolean setBannerOptions(boolean mayPredict) { return true; } + public static boolean setBannerImage(KeyboardType kType, String path) { + if (kType == KeyboardType.KEYBOARD_TYPE_INAPP) { + InAppKeyboard.setBannerImage(path); + } else if (kType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + SystemKeyboard.setBannerImage(path); + } else { + return false; + } + return true; + } + /** * showBanner - Update KMW whether to display banner. * For now, always keep displaying banner From b46d50fccf9ae067b85821608be1efea47aebeda Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 26 Sep 2023 08:06:03 +0700 Subject: [PATCH 05/57] chore(web): Revert initial banner --- web/src/engine/osk/src/banner/bannerView.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index e6d0c835e17..94b4260809a 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -243,8 +243,7 @@ export class BannerController { // If no banner instance exists yet, go with a safe, blank initialization. if(!this.container.banner) { - //this.selectBanner('inactive'); - this.setBanner('image'); + this.selectBanner('inactive'); } } } @@ -255,7 +254,6 @@ export class BannerController { * * @param type `'blank' | 'image' | 'suggestion'` - A plain-text string * representing the type of `Banner` to set active. - * @param height - Optional banner height in pixels. */ public setBanner(type: BannerType) { var banner: Banner; From 3df26683222f70df3300dc24b5071abbada66ea9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 26 Sep 2023 08:06:43 +0700 Subject: [PATCH 06/57] refactor(android/engine): Move constants to KMManager --- .../com/keyman/android/SystemKeyboard.java | 5 +- .../KMEA/app/src/main/assets/android-host.js | 9 +++ .../java/com/keyman/engine/KMKeyboard.java | 18 ++--- .../java/com/keyman/engine/KMManager.java | 71 +++++++++++++++++-- 4 files changed, 86 insertions(+), 17 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index ff9e928a7d8..48eeed6a36c 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -44,8 +44,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static final String TAG = "SystemKeyboard"; - private static final String BANNER_THEME_WHITE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j/P///38GOgAmelgyatGoRaMWjVo0atGoRQQAAD1MBB5gNLThAAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; - /** * Main initialization of the input method component. Be sure to call * to super class. @@ -73,7 +71,8 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, BANNER_THEME_WHITE); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.KM_BANNER_THEME_WHITE); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 312b4a627d5..66fb6165f96 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -63,6 +63,15 @@ function showBanner(flag) { } } +// Set KMW banner to bannerType: +// @param type 'blank' | 'image' | 'suggestion' - A plain-text string +// representing the type of Banner to set active. +function setBanner(bannerType) { + if (keyman.osk) { + keyman.osk.bannerController.setBanner(bannerType); + } +} + // Set the path to use for the image banner // path - String starting with "data:image/png;base64," function setBannerImage(path) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 12ecce012ce..0385645626b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -87,23 +87,18 @@ final class KMKeyboard extends WebView { /** * Banner state value: "blank" - no banner available. */ - protected static final String KM_BANNER_STATE_BLANK = "blank"; + public static final String KM_BANNER_STATE_BLANK = "blank"; /** * Banner state value: "image" - display an image in the banner */ - protected static final String KM_BANNER_STATE_IMAGE = "image"; + public static final String KM_BANNER_STATE_IMAGE = "image"; /** * Banner state value: "suggestion" - dictionary suggestions are shown. */ - protected static final String KM_BANNER_STATE_SUGGESTION = "suggestion"; + public static final String KM_BANNER_STATE_SUGGESTION = "suggestion"; - // Tiling black background generated from https://elmah.io/tools/base64-image-encoder/ - // TODO: Replace with image/css? - protected static final String KM_BANNER_BLANK_IMAGE_PATH = - //"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; /** * Current banner state. */ @@ -118,7 +113,7 @@ final class KMKeyboard extends WebView { // Stores the current image for use by the Banner // when predictive text is not active - private String bannerImgPath; + protected String bannerImgPath; // Facilitates a 'lazy init' - we'll only check the preference when it matters, // rather than at construction time. @@ -676,6 +671,11 @@ public void showBanner(boolean flag) { loadJavascript(jsString); } + public void setBanner(String bannerType) { + String jsString = KMString.format("setBanner(%s)", bannerType); + loadJavascript(jsString); + } + public void setBannerImage(String path) { this.bannerImgPath = path; // Save the path in case delayed initialization is needed String logString = ""; diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 9d64ca0d20d..82dab6b14a1 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -157,6 +157,31 @@ public String toString() { } }; + public enum BannerType { + BLANK, + IMAGE, + SUGGESTION; + + // Maps to enum BannerType in bannerView.ts + public static BannerType fromString(String mode) { + if (mode == null) return BLANK; + switch (mode) { + case "BLANK": + return BLANK; + case "image": + return IMAGE; + case "suggestion": + return SUGGESTION; + } + return BLANK; + } + + public String toString() { + String modes[] = { "blank", "image", "suggestion"}; + return modes[this.ordinal()]; + } + } + protected static InputMethodService IMService; private static boolean debugMode = false; @@ -175,6 +200,19 @@ public String toString() { private static KMManager.SpacebarText spacebarText = KMManager.SpacebarText.LANGUAGE_KEYBOARD; // must match default given in kmwbase.ts + // Tiling colored background generated from https://elmah.io/tools/base64-image-encoder/ + // TODO: Replace with image/css? + public static final String KM_BANNER_THEME_BLACK = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; + public static final String KM_BANNER_THEME_WHITE = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j/P///38GOgAmelgyatGoRaMWjVo0atGoRQQAAD1MBB5gNLThAAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; + + // Green banner for testing as a contrast to the Android styling. + // TODO: remove this after testing. + public static final String KM_BANNER_THEME_GREEN = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; + + protected static KMKeyboard InAppKeyboard = null; protected static KMKeyboard SystemKeyboard = null; protected static KMKeyboardWebViewClient InAppKeyboardWebViewClient = null; @@ -612,13 +650,13 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) { InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP); InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - InAppKeyboard.setBannerImage(InAppKeyboard.KM_BANNER_BLANK_IMAGE_PATH); + InAppKeyboard.setBannerImage(KM_BANNER_THEME_GREEN); keyboard = InAppKeyboard; webViewClient = InAppKeyboardWebViewClient; } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) { SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM); SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - SystemKeyboard.setBannerImage(SystemKeyboard.KM_BANNER_BLANK_IMAGE_PATH); + SystemKeyboard.setBannerImage(KM_BANNER_THEME_WHITE); keyboard = SystemKeyboard; webViewClient = SystemKeyboardWebViewClient; } @@ -1391,10 +1429,33 @@ public static boolean setBannerOptions(boolean mayPredict) { return true; } - public static boolean setBannerImage(KeyboardType kType, String path) { - if (kType == KeyboardType.KEYBOARD_TYPE_INAPP) { + /** + * Update KMW banner type + * @param {KeyboardType} keyboard + * @param {BannerType} bannerType + * @return status + */ + public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { + InAppKeyboard.setBanner(bannerType.toString()); + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + SystemKeyboard.setBanner(bannerType.toString()); + } else { + return false; + } + return true; + } + + /** + * Set the path to use with the image banner + * @param {KeyboardType} keyboard + * @param {String} path + * @return + */ + public static boolean setBannerImage(KeyboardType keyboard, String path) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { InAppKeyboard.setBannerImage(path); - } else if (kType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { SystemKeyboard.setBannerImage(path); } else { return false; From 131180c050c2ae8c89413d8ffab9dadd24268ac3 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 26 Sep 2023 09:32:40 +0700 Subject: [PATCH 07/57] refactor(android): Change set/getBanner --- .../com/tavultesoft/kmapro/MainActivity.java | 3 +- .../KMEA/app/src/main/assets/android-host.js | 11 ++++-- .../java/com/keyman/engine/KMKeyboard.java | 35 +++++-------------- .../engine/KMKeyboardWebViewClient.java | 4 +-- .../java/com/keyman/engine/KMManager.java | 10 +++--- 5 files changed, 26 insertions(+), 37 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 6b92d4c2eb5..018884040e6 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -489,7 +489,8 @@ public boolean onKeyUp(int keycode, KeyEvent e) { @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - // Do nothing + // KeymanWeb initializes with a blank banner, so try to override with image + KMManager.setBanner(keyboardType, KMManager.BannerType.IMAGE); } @Override diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 66fb6165f96..c4cb941d460 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -63,12 +63,17 @@ function showBanner(flag) { } } -// Set KMW banner to bannerType: +// If KeymanWeb banner currently blank, set to bannerType // @param type 'blank' | 'image' | 'suggestion' - A plain-text string // representing the type of Banner to set active. function setBanner(bannerType) { - if (keyman.osk) { - keyman.osk.bannerController.setBanner(bannerType); + if (keyman.osk && keyman.osk.bannerController) { + window.console.log('setBanner: current KMW banner is: ' + keyman.osk.bannerController.activeType); + if (keyman.osk.bannerController.activeType == 'blank') { + keyman.osk.bannerController.setBanner(bannerType); + } + } else { + window.console.log('Not overriding banner'); } } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 0385645626b..8a6b4abc6e8 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -84,25 +84,10 @@ final class KMKeyboard extends WebView { private static String currentKeyboard = null; - /** - * Banner state value: "blank" - no banner available. - */ - public static final String KM_BANNER_STATE_BLANK = "blank"; - - /** - * Banner state value: "image" - display an image in the banner - */ - public static final String KM_BANNER_STATE_IMAGE = "image"; - - /** - * Banner state value: "suggestion" - dictionary suggestions are shown. - */ - public static final String KM_BANNER_STATE_SUGGESTION = "suggestion"; - /** * Current banner state. */ - protected static String currentBanner = KM_BANNER_STATE_IMAGE; + protected static KMManager.BannerType currentBanner = KMManager.BannerType.IMAGE; private static String txtFont = ""; private static String oskFont = null; @@ -438,17 +423,11 @@ public static String currentKeyboard() { return currentKeyboard; } - public static void setCurrentBanner(String banner) { - currentBanner = banner; - } - - public static String currentBanner() { return currentBanner; } - protected void toggleSuggestionBanner(HashMap associatedLexicalModel, boolean keyboardChanged) { //reset banner state if new language has no lexical model - if (currentBanner != null && currentBanner.equals(KM_BANNER_STATE_SUGGESTION) + if (currentBanner == KMManager.BannerType.SUGGESTION && associatedLexicalModel == null) { - setCurrentBanner(KMKeyboard.KM_BANNER_STATE_IMAGE); + setBanner(KMManager.BannerType.IMAGE); } if(keyboardChanged) { @@ -671,8 +650,12 @@ public void showBanner(boolean flag) { loadJavascript(jsString); } - public void setBanner(String bannerType) { - String jsString = KMString.format("setBanner(%s)", bannerType); + public KMManager.BannerType getBanner() { + return currentBanner; + } + + public void setBanner(KMManager.BannerType bannerType) { + String jsString = KMString.format("setBanner('%s')", bannerType.toString()); loadJavascript(jsString); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java index e2670b9c619..9a501eadb18 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java @@ -173,8 +173,8 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { if (KMManager.currentLexicalModel != null) { modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true); } - kmKeyboard.setCurrentBanner((isModelActive && modelPredictionPref) ? - KMKeyboard.KM_BANNER_STATE_SUGGESTION : KMKeyboard.KM_BANNER_STATE_IMAGE); + kmKeyboard.setBanner((isModelActive && modelPredictionPref) ? + KMManager.BannerType.SUGGESTION : KMManager.BannerType.IMAGE); RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); kmKeyboard.setLayoutParams(params); } else if (url.indexOf("suggestPopup") >= 0) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 82dab6b14a1..e6c6eb6d153 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -1430,16 +1430,16 @@ public static boolean setBannerOptions(boolean mayPredict) { } /** - * Update KMW banner type + * Update KeymanWeb banner type * @param {KeyboardType} keyboard * @param {BannerType} bannerType * @return status */ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { - InAppKeyboard.setBanner(bannerType.toString()); + InAppKeyboard.setBanner(bannerType); } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { - SystemKeyboard.setBanner(bannerType.toString()); + SystemKeyboard.setBanner(bannerType); } else { return false; } @@ -1943,9 +1943,9 @@ public static void removeKeyboardEventListener(OnKeyboardEventListener listener) public static int getBannerHeight(Context context) { int bannerHeight = 0; - if (InAppKeyboard != null && !InAppKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_BLANK)) { + if (InAppKeyboard != null && InAppKeyboard.getBanner() != BannerType.BLANK) { bannerHeight = (int) context.getResources().getDimension(R.dimen.banner_height); - } else if (SystemKeyboard != null && !SystemKeyboard.currentBanner().equals(KMKeyboard.KM_BANNER_STATE_BLANK)) { + } else if (SystemKeyboard != null && SystemKeyboard.getBanner() != BannerType.BLANK) { bannerHeight = (int) context.getResources().getDimension(R.dimen.banner_height); } return bannerHeight; From db728dfb8aebcab98a9296f25a2da15f09b5cdf9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 26 Sep 2023 11:40:36 +0700 Subject: [PATCH 08/57] fix(android): Set image path --- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 6 ++++-- .../app/src/main/java/com/keyman/engine/KMManager.java | 8 ++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 018884040e6..d0f8a319318 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -489,8 +489,7 @@ public boolean onKeyUp(int keycode, KeyEvent e) { @Override public void onKeyboardLoaded(KeyboardType keyboardType) { - // KeymanWeb initializes with a blank banner, so try to override with image - KMManager.setBanner(keyboardType, KMManager.BannerType.IMAGE); + // Do nothing } @Override @@ -500,6 +499,9 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { + // KMManager.init not working? + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.KM_BANNER_THEME_GREEN); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index e6c6eb6d153..4c968869730 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -1436,9 +1436,9 @@ public static boolean setBannerOptions(boolean mayPredict) { * @return status */ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { - if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { InAppKeyboard.setBanner(bannerType); - } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { SystemKeyboard.setBanner(bannerType); } else { return false; @@ -1453,9 +1453,9 @@ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { * @return */ public static boolean setBannerImage(KeyboardType keyboard, String path) { - if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { InAppKeyboard.setBannerImage(path); - } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM) { + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { SystemKeyboard.setBannerImage(path); } else { return false; From b91d651829b0ca7d4280a98d97700165cd44fb39 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 27 Sep 2023 07:41:31 +0700 Subject: [PATCH 09/57] chore(web): Default to image banner on Android --- web/src/engine/osk/src/banner/bannerView.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 94b4260809a..2921b8438a4 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -178,6 +178,12 @@ export class BannerController { imagePath: "" } + // Default to black image banner for Android + public static readonly DEFAULT_ANDROID_OPTIONS: BannerOptions = { + alwaysShow: true, + imagePath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII=" + } + constructor(bannerView: BannerView, hostDevice: DeviceSpec, predictionContext?: PredictionContext) { // Step 1 - establish the container element. Must come before this.setOptions. this.hostDevice = hostDevice; @@ -186,7 +192,9 @@ export class BannerController { // Initialize with the default options - any 'manually set' options come post-construction. // This will also automatically set the default banner in place. - this.setOptions(BannerController.DEFAULT_OPTIONS); + this.setOptions( + this.hostDevice.OS != DeviceSpec.OperatingSystem.Android ? + BannerController.DEFAULT_OPTIONS : BannerController.DEFAULT_ANDROID_OPTIONS); } /** From 7d3de5ad3425bfe17e15cbe36450cbfc5bee82ba Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 2 Oct 2023 14:33:49 +0700 Subject: [PATCH 10/57] fix(web): Handle banner initialization --- web/src/engine/osk/src/banner/bannerView.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 2921b8438a4..169d2eac32e 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -248,11 +248,12 @@ export class BannerController { // Invalid option specified! } this._options[key] = optionSpec[key]; + } - // If no banner instance exists yet, go with a safe, blank initialization. - if(!this.container.banner) { - this.selectBanner('inactive'); - } + // Move out of for loop to handle standup + // If no banner instance exists yet, go with a safe, blank initialization. + if(!this.container.banner) { + this.selectBanner('inactive'); } } From 018abd4319fa04caf163c9059b9e0f6e8902878c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 6 Oct 2023 09:22:19 +0700 Subject: [PATCH 11/57] fix(android/engine): Use white and Keyman theme banners --- .../java/com/keyman/android/SystemKeyboard.java | 2 +- .../java/com/tavultesoft/kmapro/MainActivity.java | 4 ++-- android/KMEA/app/src/main/assets/android-host.js | 1 + .../main/java/com/keyman/engine/KMKeyboard.java | 12 ++++++++---- .../main/java/com/keyman/engine/KMManager.java | 15 +++++++++++++-- 5 files changed, 25 insertions(+), 9 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 48eeed6a36c..9c40dc1b7d9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -71,7 +71,7 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.KM_BANNER_THEME_WHITE); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.KM_BANNER_THEME_KEYMAN); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index d0f8a319318..54f1e9cd85e 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -499,8 +499,8 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { - // KMManager.init not working? - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.KM_BANNER_THEME_GREEN); + // Set the in-app banner image here. Doesn't work in KMManager.init + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.KM_BANNER_THEME_WHITE); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index c4cb941d460..e92ecfb8dec 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -69,6 +69,7 @@ function showBanner(flag) { function setBanner(bannerType) { if (keyman.osk && keyman.osk.bannerController) { window.console.log('setBanner: current KMW banner is: ' + keyman.osk.bannerController.activeType); + window.console.log('setBanner: options are: ' + JSON.stringify(keyman.osk.bannerController.getOptions())); if (keyman.osk.bannerController.activeType == 'blank') { keyman.osk.bannerController.setBanner(bannerType); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 8a6b4abc6e8..ce2d13483e9 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -98,7 +98,7 @@ final class KMKeyboard extends WebView { // Stores the current image for use by the Banner // when predictive text is not active - protected String bannerImgPath; + protected String bannerImagePath; // Facilitates a 'lazy init' - we'll only check the preference when it matters, // rather than at construction time. @@ -395,8 +395,8 @@ public void onConfigurationChanged(Configuration newConfig) { int bannerHeight = KMManager.getBannerHeight(context); int oskHeight = KMManager.getKeyboardHeight(context); - if (this.bannerImgPath != null) { - loadJavascript(KMString.format("setBannerImage('%s')", this.bannerImgPath)); + if (this.bannerImagePath != null) { + loadJavascript(KMString.format("setBannerImage('%s')", this.bannerImagePath)); } loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); loadJavascript(KMString.format("setOskWidth(%d)", newConfig.screenWidthDp)); @@ -659,8 +659,12 @@ public void setBanner(KMManager.BannerType bannerType) { loadJavascript(jsString); } + public String getBannerImage() { + return this.bannerImagePath; + } + public void setBannerImage(String path) { - this.bannerImgPath = path; // Save the path in case delayed initialization is needed + this.bannerImagePath = path; // Save the path in case delayed initialization is needed String logString = ""; if (path != null && path.contains("base64") || path.length() > 256) { logString = ""; diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 4c968869730..a3d2ab43a47 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -36,6 +36,7 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.os.health.SystemHealthManager; import android.text.InputType; import android.util.Log; import android.view.HapticFeedbackConstants; @@ -206,6 +207,8 @@ public String toString() { "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; public static final String KM_BANNER_THEME_WHITE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j/P///38GOgAmelgyatGoRaMWjVo0atGoRQQAAD1MBB5gNLThAAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; + public static final String KM_BANNER_THEME_KEYMAN = + "data:image/png;base64,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"; // Green banner for testing as a contrast to the Android styling. // TODO: remove this after testing. @@ -650,13 +653,11 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) if (keyboardType == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard == null) { InAppKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_INAPP); InAppKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - InAppKeyboard.setBannerImage(KM_BANNER_THEME_GREEN); keyboard = InAppKeyboard; webViewClient = InAppKeyboardWebViewClient; } else if (keyboardType == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard == null) { SystemKeyboard = new KMKeyboard(appContext, KeyboardType.KEYBOARD_TYPE_SYSTEM); SystemKeyboardWebViewClient = new KMKeyboardWebViewClient(appContext, keyboardType); - SystemKeyboard.setBannerImage(KM_BANNER_THEME_WHITE); keyboard = SystemKeyboard; webViewClient = SystemKeyboardWebViewClient; } @@ -1458,11 +1459,21 @@ public static boolean setBannerImage(KeyboardType keyboard, String path) { } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { SystemKeyboard.setBannerImage(path); } else { + Log.d(TAG, "setBannerImage but keyboard is null"); return false; } return true; } + public static String getBannerImage(KeyboardType keyboard) { + if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { + return InAppKeyboard.getBannerImage(); + } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { + return SystemKeyboard.getBannerImage(); + } + return ""; + } + /** * showBanner - Update KMW whether to display banner. * For now, always keep displaying banner From a5aef7aeae3bb758dd7e3f1f32a85bbfd85bfde0 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 6 Oct 2023 14:15:12 +0700 Subject: [PATCH 12/57] fix(web): Update image path when setting Options --- web/src/engine/osk/src/banner/bannerView.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 169d2eac32e..b0f7a08a9eb 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -243,6 +243,9 @@ export class BannerController { case 'imagePath': // Determines the image file to use for ImageBanners. this.imagePath = optionSpec[key]; + if(this.container.banner instanceof ImageBanner) { + this.container.banner.setImagePath(optionSpec[key]); + } break; default: // Invalid option specified! From 510de7244e19f0154b6c2bcc19c4386b06a8816c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 6 Oct 2023 14:15:37 +0700 Subject: [PATCH 13/57] fix(android/engine): Remove refresh that wasn't working --- android/KMEA/app/src/main/assets/android-host.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index e92ecfb8dec..608ba23d13b 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -86,9 +86,6 @@ function setBannerImage(path) { "imagePath": path, "alwaysShow": true}); } - - // Refresh KMW's OSK - keyman.refreshOskLayout(); } function notifyHost(event, params) { From d366f332b189c3c3b607c67dc16fe793bb46611c Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 6 Oct 2023 15:03:01 +0700 Subject: [PATCH 14/57] chore(web): Add Android tablet theme --- web/src/engine/osk/src/banner/bannerView.ts | 24 ++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index b0f7a08a9eb..c8d4f4d8690 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -178,12 +178,18 @@ export class BannerController { imagePath: "" } - // Default to black image banner for Android - public static readonly DEFAULT_ANDROID_OPTIONS: BannerOptions = { + // Default to black image banner for Android phone + public static readonly DEFAULT_ANDROID_PHONE_OPTIONS: BannerOptions = { alwaysShow: true, imagePath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII=" } + // Default to gray image banner for Android tablet + public static readonly DEFAULT_ANDROID_TABLET_OPTIONS: BannerOptions = { + alwaysShow: true, + imagePath: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAXCAYAAADz/ZRUAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUARnJpIDA2IE9jdCAyMDIzIDAyOjUxOjQ0IFBNICswN4WPPMwAAABRSURBVEiJ7c1BCoAwDAXRaeP9b6PQs9kkbt02CEX4sx9eu86RCWQmK7k7c95ExNL37qAAA5gZZlaGAXoU4K/qbRsNfaMtXLhw4cKFCxf+Y/wBEz4WP2Yx4QAAAAAASUVORK5CYII==" + } + constructor(bannerView: BannerView, hostDevice: DeviceSpec, predictionContext?: PredictionContext) { // Step 1 - establish the container element. Must come before this.setOptions. this.hostDevice = hostDevice; @@ -192,9 +198,17 @@ export class BannerController { // Initialize with the default options - any 'manually set' options come post-construction. // This will also automatically set the default banner in place. - this.setOptions( - this.hostDevice.OS != DeviceSpec.OperatingSystem.Android ? - BannerController.DEFAULT_OPTIONS : BannerController.DEFAULT_ANDROID_OPTIONS); + console.log(this.hostDevice); + this.setOptions(BannerController.DEFAULT_OPTIONS); + if (this.hostDevice.OS == DeviceSpec.OperatingSystem.Android) { + // Embedded Android devices will default to image banner + if (this.hostDevice.formFactor == DeviceSpec.FormFactor.Phone) { + this.setOptions(BannerController.DEFAULT_ANDROID_PHONE_OPTIONS); + } else if (this.hostDevice.formFactor == DeviceSpec.FormFactor.Tablet) { + this.setOptions(BannerController.DEFAULT_ANDROID_TABLET_OPTIONS); + } + // Desktop sticks with default options + } } /** From bb5a9f8816f8275e6bb19c7f7d7048e341138475 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 26 Oct 2023 08:21:45 +0700 Subject: [PATCH 15/57] chore(android/engine): Move banner names --- .../{banner_landscape.png => banner.png} | Bin .../banner_portrait.png => drawable/banner.png} | Bin 2 files changed, 0 insertions(+), 0 deletions(-) rename android/KMEA/app/src/main/res/drawable-land-mdpi/{banner_landscape.png => banner.png} (100%) rename android/KMEA/app/src/main/res/{drawable-mdpi/banner_portrait.png => drawable/banner.png} (100%) diff --git a/android/KMEA/app/src/main/res/drawable-land-mdpi/banner_landscape.png b/android/KMEA/app/src/main/res/drawable-land-mdpi/banner.png similarity index 100% rename from android/KMEA/app/src/main/res/drawable-land-mdpi/banner_landscape.png rename to android/KMEA/app/src/main/res/drawable-land-mdpi/banner.png diff --git a/android/KMEA/app/src/main/res/drawable-mdpi/banner_portrait.png b/android/KMEA/app/src/main/res/drawable/banner.png similarity index 100% rename from android/KMEA/app/src/main/res/drawable-mdpi/banner_portrait.png rename to android/KMEA/app/src/main/res/drawable/banner.png From a6b7b95603145c37fc0028c43f16cc7e7dc1c263 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 26 Oct 2023 09:09:48 +0700 Subject: [PATCH 16/57] feat(oem/fv/android): Add banner theme --- .../main/java/com/firstvoices/keyboards/SystemKeyboard.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java index 95d0c51b372..d921bb14dae 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java @@ -33,6 +33,8 @@ import io.sentry.Sentry; public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { + public static final String FV_BANNER_THEME_GOLD = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADsAAAApCAYAAACLO1EjAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAVGh1IDI2IE9jdCAyMDIzIDA4OjE0OjE5IEFNICswN115VJ8AAABRSURBVGiB7c8BEYAgAMBAIIspKWRMbaF3zz7BNu99PeMQ6++ALzWralbVrKpZVbOqZlXNqppVNatqVtWsqllVs6pmVc2qmlU1q2pW1azqqNkXOKcClnCmxkcAAAAASUVORK5CYII="; private View inputView = null; private static ExtractedText exText = null; @@ -64,6 +66,9 @@ public void onCreate() { interpreter = new KMHardwareKeyboardInterpreter(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM); KMManager.setInputMethodService(this); // for HW interface + + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, FV_BANNER_THEME_GOLD); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); } @Override From 926a2a2e5094aa673bcc5535b45aaefce490b3ae Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 26 Oct 2023 09:41:46 +0700 Subject: [PATCH 17/57] feat(android/samples): Add sample code for theme on KMSample2 --- .../main/java/com/keyman/kmsample2/SystemKeyboard.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java index e4ab04e50f5..b47aee9a79d 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java @@ -27,6 +27,11 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static View inputView = null; private KMHardwareKeyboardInterpreter interpreter = null; + // Theme for light-blue banner. Replace the string with your custom theme by uploading a color image to https://elmah.io/tools/base64-image-encoder/ + public static final String KM_BANNER_THEME_KMSAMPLE2 = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYYAAAAaCAYAAACzU/tpAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAVGh1IDI2IE9jdCAyMDIzIDA5OjMwOjI5IEFNICswN6UZZDEAAAB5SURBVHic7dUxAYAwAMCwMbdTiA8MsWdPFcCRKOjXa93POwDgmF8HAPAvxgBAGAMAYQwAhDEAEMYAQBgDAGEMAIQxABDGAEAYAwBhDACEMQAQxgBAGAMAYQwAhDEAEMYAQBgDAGEMAIQxABDGAEAYAwBhDACEMQAQGyB8AyU9yxQXAAAAAElFTkSuQmCC"; + + /** * Main initialization of the input method component. Be sure to call * to super class. @@ -67,6 +72,11 @@ public void onCreate() { lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0"); KMManager.addLexicalModel(this, lexicalModelInfo); KMManager.registerAssociatedLexicalModel("ta"); + + // Uncomment these two lines if keyboard doesn't use a dictionary. + // The suggestion banner is replaced with this color. + //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KMSAMPLE2); + //KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); } @Override From 86301218289b3c5c7630c933c548c4c1e21e1323 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 7 Nov 2023 10:29:48 +0700 Subject: [PATCH 18/57] fix(android/engine): Replace png's with svg's --- .../src/main/assets/svg/keyman_banner.svg | 338 ++++++++++++++++++ .../com/keyman/android/SystemKeyboard.java | 5 +- .../app/src/main/assets/svg/black_banner.svg | 1 + .../app/src/main/assets/svg/white_banner.svg | 1 + .../java/com/keyman/engine/KMManager.java | 33 +- .../main/res/drawable-land-mdpi/banner.png | Bin 2856 -> 0 bytes .../KMEA/app/src/main/res/drawable/banner.png | Bin 3142 -> 0 bytes .../app/src/main/assets/svg/green_banner.svg | 1 + .../com/keyman/kmsample1/MainActivity.java | 9 + .../app/src/main/assets/svg/gold_banner.svg | 1 + 10 files changed, 374 insertions(+), 15 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg create mode 100644 android/KMEA/app/src/main/assets/svg/black_banner.svg create mode 100644 android/KMEA/app/src/main/assets/svg/white_banner.svg delete mode 100644 android/KMEA/app/src/main/res/drawable-land-mdpi/banner.png delete mode 100644 android/KMEA/app/src/main/res/drawable/banner.png create mode 100644 android/Samples/KMSample1/app/src/main/assets/svg/green_banner.svg create mode 100644 oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg new file mode 100644 index 00000000000..84bcfc2d0ea --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg @@ -0,0 +1,338 @@ + + + +image/svg+xml \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 9c40dc1b7d9..a737068d7e3 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -42,6 +42,9 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static ExtractedText exText = null; private KMHardwareKeyboardInterpreter interpreter = null; + // Paths relative to assets folder for banner themes + public static final String KM_BANNER_THEME_KEYMAN = "svg/keyman_banner.svg"; + private static final String TAG = "SystemKeyboard"; /** @@ -71,7 +74,7 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.KM_BANNER_THEME_KEYMAN); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMEA/app/src/main/assets/svg/black_banner.svg b/android/KMEA/app/src/main/assets/svg/black_banner.svg new file mode 100644 index 00000000000..67a14e8950d --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/black_banner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg new file mode 100644 index 00000000000..01a6582a61f --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/white_banner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index a3d2ab43a47..973aa7393ee 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -201,20 +201,9 @@ public String toString() { private static KMManager.SpacebarText spacebarText = KMManager.SpacebarText.LANGUAGE_KEYBOARD; // must match default given in kmwbase.ts - // Tiling colored background generated from https://elmah.io/tools/base64-image-encoder/ - // TODO: Replace with image/css? - public static final String KM_BANNER_THEME_BLACK = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAIAAABM7ytaAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAVSURBVDhPYxgFo2AUjIJRQDpgYAAABT8AAcEGbxwAAAAASUVORK5CYII="; - public static final String KM_BANNER_THEME_WHITE = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j/P///38GOgAmelgyatGoRaMWjVo0atGoRQQAAD1MBB5gNLThAAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; - public static final String KM_BANNER_THEME_KEYMAN = - "data:image/png;base64,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"; - - // Green banner for testing as a contrast to the Android styling. - // TODO: remove this after testing. - public static final String KM_BANNER_THEME_GREEN = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAARCAYAAADDjbwNAAAABHNCSVQICAgIfAhkiAAAACNJREFUOI1j9PnP8J+BDoCJHpaMWjRq0ahFoxaNWjRqEQEAAG4mAmxMRRQ1AAAAAElFTkSuQmCCBT8AAcEGbxwAAAAASUVORK5CYII="; - + // Paths relative to assets folder for banner themes + public static final String KM_BANNER_THEME_BLACK = "svg/black_banner.svg"; + public static final String KM_BANNER_THEME_WHITE = "svg/white_banner.svg"; protected static KMKeyboard InAppKeyboard = null; protected static KMKeyboard SystemKeyboard = null; @@ -295,6 +284,8 @@ public String toString() { public static final String KMDefault_AssetPackages = "packages"; public static final String KMDefault_LexicalModelPackages = "models"; + public static final String KMDefault_AssetSVG = "svg"; + // Default Keyboard Info public static final String KMDefault_PackageID = "sil_euro_latin"; public static final String KMDefault_KeyboardID = "sil_euro_latin"; @@ -346,6 +337,10 @@ public static String getCloudDir() { return getResourceRoot() + KMDefault_UndefinedPackageID + File.separator; } + public static String getSVGDir() { + return getResourceRoot() + KMDefault_AssetSVG + File.separator; + } + public static FormFactor getFormFactor() { String device_type = appContext.getResources().getString(R.string.device_type); @@ -854,6 +849,16 @@ private static void copyAssets(Context context) { copyAsset(context, KMDefault_KeyboardFont, "", true); copyAsset(context, KMFilename_JSPolyfill, "", true); + // SVG directory for banner themes + File svgDir = new File(getSVGDir()); + if (!svgDir.exists()) { + svgDir.mkdir(); + } + String[] svgFiles = assetManager.list(KMDefault_AssetSVG); + for (String svgFile : svgFiles) { + copyAsset(context, svgFile, KMDefault_AssetSVG, true); + } + // Keyboard packages directory File packagesDir = new File(getPackagesDir()); if (!packagesDir.exists()) { diff --git a/android/KMEA/app/src/main/res/drawable-land-mdpi/banner.png b/android/KMEA/app/src/main/res/drawable-land-mdpi/banner.png deleted file mode 100644 index f4c27d6c614881a98bb40ccd78ef50c459d5dfaa..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2856 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 \ No newline at end of file diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index f56064b4594..619ba2ed8bf 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -24,6 +24,9 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi public static Context context; private KMTextView textView; + // Paths relative to assets folder for banner themes + private static String BANNER_THEME_GREEN = "svg/green_banner.svg"; + @Override protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.AppTheme); @@ -55,6 +58,7 @@ protected void onCreate(Bundle savedInstanceState) { KMManager.addKeyboard(this, kbInfo); // Add a dictionary + /* HashMaplexicalModelInfo = new HashMap(); lexicalModelInfo.put(KMManager.KMKey_PackageID, "example.ta.wordlist"); lexicalModelInfo.put(KMManager.KMKey_LanguageID, "ta"); @@ -63,6 +67,7 @@ protected void onCreate(Bundle savedInstanceState) { lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0"); KMManager.addLexicalModel(context, lexicalModelInfo); KMManager.registerAssociatedLexicalModel("ta"); + */ } @Override @@ -123,6 +128,10 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Handle Keyman keyboard shown event here if needed + + // Set the in-app banner image here. Doesn't work in KMManager.init + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, BANNER_THEME_GREEN); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); } @Override diff --git a/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg b/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg new file mode 100644 index 00000000000..d9456ba83ed --- /dev/null +++ b/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg @@ -0,0 +1 @@ + \ No newline at end of file From 1e300023921eae3e9ff4d527f17769eb5bb883e8 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 7 Nov 2023 11:02:21 +0700 Subject: [PATCH 19/57] chore(android): Cleanup --- .../src/main/java/com/keyman/engine/KMManager.java | 3 +-- .../java/com/keyman/kmsample1/MainActivity.java | 13 ++++++------- .../app/src/main/assets/svg/green_banner.svg | 1 + .../java/com/keyman/kmsample2/SystemKeyboard.java | 8 +++----- 4 files changed, 11 insertions(+), 14 deletions(-) create mode 100644 android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 973aa7393ee..1b8df59f001 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -36,7 +36,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; -import android.os.health.SystemHealthManager; import android.text.InputType; import android.util.Log; import android.view.HapticFeedbackConstants; @@ -1423,7 +1422,7 @@ public static void deleteLexicalModel(Context context, int position, boolean sil * @return boolean - Success */ public static boolean setBannerOptions(boolean mayPredict) { - String url = KMString.format("setBannerOptions(%b)", mayPredict); // TODO? + String url = KMString.format("setBannerOptions(%b)", mayPredict); if (InAppKeyboard != null) { InAppKeyboard.loadJavascript(url); } diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 619ba2ed8bf..837a23f4a22 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -24,8 +24,8 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi public static Context context; private KMTextView textView; - // Paths relative to assets folder for banner themes - private static String BANNER_THEME_GREEN = "svg/green_banner.svg"; + // Paths relative to assets folder for banner themes. Change this for custom banner + private static String BANNER_THEME_KMSAMPLE1 = "svg/green_banner.svg"; @Override protected void onCreate(Bundle savedInstanceState) { @@ -58,7 +58,6 @@ protected void onCreate(Bundle savedInstanceState) { KMManager.addKeyboard(this, kbInfo); // Add a dictionary - /* HashMaplexicalModelInfo = new HashMap(); lexicalModelInfo.put(KMManager.KMKey_PackageID, "example.ta.wordlist"); lexicalModelInfo.put(KMManager.KMKey_LanguageID, "ta"); @@ -67,7 +66,6 @@ protected void onCreate(Bundle savedInstanceState) { lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0"); KMManager.addLexicalModel(context, lexicalModelInfo); KMManager.registerAssociatedLexicalModel("ta"); - */ } @Override @@ -129,9 +127,10 @@ public void onKeyboardChanged(String newKeyboard) { public void onKeyboardShown() { // Handle Keyman keyboard shown event here if needed - // Set the in-app banner image here. Doesn't work in KMManager.init - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, BANNER_THEME_GREEN); - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); + // Uncomment these lines if the app doesn't use a dictionary + // and wants to set the in-app banner image here. + //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, BANNER_THEME_KMSAMPLE1); + //KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); } @Override diff --git a/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg b/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg new file mode 100644 index 00000000000..7bf4843cef5 --- /dev/null +++ b/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java index b47aee9a79d..8210bb3c803 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java @@ -27,10 +27,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static View inputView = null; private KMHardwareKeyboardInterpreter interpreter = null; - // Theme for light-blue banner. Replace the string with your custom theme by uploading a color image to https://elmah.io/tools/base64-image-encoder/ - public static final String KM_BANNER_THEME_KMSAMPLE2 = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAYYAAAAaCAYAAACzU/tpAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAVGh1IDI2IE9jdCAyMDIzIDA5OjMwOjI5IEFNICswN6UZZDEAAAB5SURBVHic7dUxAYAwAMCwMbdTiA8MsWdPFcCRKOjXa93POwDgmF8HAPAvxgBAGAMAYQwAhDEAEMYAQBgDAGEMAIQxABDGAEAYAwBhDACEMQAQxgBAGAMAYQwAhDEAEMYAQBgDAGEMAIQxABDGAEAYAwBhDACEMQAQGyB8AyU9yxQXAAAAAElFTkSuQmCC"; - + // Paths relative to assets folder for banner themes. Change this for custom banner + public static final String BANNER_THEME_KMSAMPLE2 = "svg/green_banner.svg"; /** * Main initialization of the input method component. Be sure to call @@ -75,7 +73,7 @@ public void onCreate() { // Uncomment these two lines if keyboard doesn't use a dictionary. // The suggestion banner is replaced with this color. - //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KMSAMPLE2); + //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, BANNER_THEME_KMSAMPLE2); //KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); } From 6535acd4ed4c6620eadae6b66038965759b2bdac Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 7 Nov 2023 13:48:06 +0700 Subject: [PATCH 20/57] chore(android/app): Adjust svg dimensions --- .../src/main/assets/svg/keyman_banner.svg | 690 +++++++++--------- .../KMEA/app/src/main/assets/imagebanner.css | 22 + .../KMEA/app/src/main/assets/keyboard.html | 1 + .../app/src/main/assets/svg/white_banner.svg | 5 +- .../java/com/keyman/engine/KMManager.java | 6 + web/src/engine/osk/src/banner/banner.ts | 1 + 6 files changed, 396 insertions(+), 329 deletions(-) create mode 100644 android/KMEA/app/src/main/assets/imagebanner.css diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg index 84bcfc2d0ea..3888a0937f9 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg +++ b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg @@ -1,338 +1,372 @@ - - image/svg+xml \ No newline at end of file + xml:space="preserve" + sodipodi:docname="keyman_banner.svg" + inkscape:version="1.0.2-2 (e86c870879, 2021-01-15)"> + + image/svg+xml diff --git a/android/KMEA/app/src/main/assets/imagebanner.css b/android/KMEA/app/src/main/assets/imagebanner.css new file mode 100644 index 00000000000..bd2c21d66f6 --- /dev/null +++ b/android/KMEA/app/src/main/assets/imagebanner.css @@ -0,0 +1,22 @@ +/*** + Keyman Engine for Android + Copyright 2023 SIL International +***/ + +/* + imagebanner.css: CSS for theming image banner (separate from KeymanWeb assets). + Image banner can be themed with .svg file +*/ + +.phone.android .kmw-banner-image { + /*background-color: #000; /* Or make this white? */ + background-image: url('svg/white_banner.svg'); + position:absolute; +} + +.tablet.android .kmw-banner-image { + background-color: #b4b4b8; + position:relative; + height: 100%; + width: 100%; +} diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index b11a21124ec..044ca3b82cd 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -22,6 +22,7 @@ + diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg index 01a6582a61f..b99513bad30 100644 --- a/android/KMEA/app/src/main/assets/svg/white_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/white_banner.svg @@ -1 +1,4 @@ - \ No newline at end of file + + + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 1b8df59f001..f9097f9b314 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -313,6 +313,9 @@ public String toString() { protected static final String KMFilename_Osk_Ttf_Font = "keymanweb-osk.ttf"; protected static final String KMFilename_JSPolyfill = "es6-shim.min.js"; + // CSS for image banner theming + protected static final String KMFilename_ImageBannerCss = "imagebanner.css"; + // Deprecated by KeyboardController.KMFilename_Installed_KeyboardsList public static final String KMFilename_KeyboardsList = "keyboards_list.dat"; @@ -848,6 +851,9 @@ private static void copyAssets(Context context) { copyAsset(context, KMDefault_KeyboardFont, "", true); copyAsset(context, KMFilename_JSPolyfill, "", true); + // Copy image banner css + copyAsset(context, KMFilename_ImageBannerCss, "", true); + // SVG directory for banner themes File svgDir = new File(getSVGDir()); if (!svgDir.exists()) { diff --git a/web/src/engine/osk/src/banner/banner.ts b/web/src/engine/osk/src/banner/banner.ts index 999f7d63486..3e295e76b7c 100644 --- a/web/src/engine/osk/src/banner/banner.ts +++ b/web/src/engine/osk/src/banner/banner.ts @@ -136,6 +136,7 @@ export class ImageBanner extends Banner { console.log("Loading img with src '" + imagePath + "'"); } this.img = document.createElement('img'); + this.img.className = "kmw-banner-image"; this.img.setAttribute('src', imagePath); let ds = this.img.style; ds.width = '100%'; From 11af5508af4e22ec43ee9d2aaf4d76b5a471ab15 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 9 Nov 2023 16:24:58 +0700 Subject: [PATCH 21/57] fix(android/engine): Try more svg --- .../kMAPro/src/main/assets/imagebanner.css | 21 +++++++++++++++++++ .../src/main/assets/svg/keyman_banner.svg | 9 ++++---- .../KMEA/app/src/main/assets/imagebanner.css | 7 ++----- .../app/src/main/assets/svg/black_banner.svg | 1 - .../app/src/main/assets/svg/gray_banner.svg | 4 ++++ .../app/src/main/assets/svg/white_banner.svg | 6 +++--- 6 files changed, 34 insertions(+), 14 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/assets/imagebanner.css delete mode 100644 android/KMEA/app/src/main/assets/svg/black_banner.svg create mode 100644 android/KMEA/app/src/main/assets/svg/gray_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/assets/imagebanner.css b/android/KMAPro/kMAPro/src/main/assets/imagebanner.css new file mode 100644 index 00000000000..351052927c8 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/assets/imagebanner.css @@ -0,0 +1,21 @@ +/*** + Keyman Engine for Android + Copyright 2023 SIL International +***/ + +/* + imagebanner.css: CSS for theming image banner (separate from KeymanWeb assets). + Image banner can be themed with .svg file +*/ + +/* +.phone.android .kmw-banner-image { + background-image: url('svg/keyman_banner.svg'); + position:absolute; +} + +.tablet.android .kmw-banner-image { + background-image: url('svg/gray_banner.svg'); + position:absolute; +} +*/ \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg index 3888a0937f9..f5d31a00187 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg +++ b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg @@ -8,8 +8,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" - width="1064.3625" - height="238.77588" + width="100%" + height="auto" id="svg2" xml:space="preserve" sodipodi:docname="keyman_banner.svg" @@ -40,12 +40,11 @@ inkscape:current-layer="svg2" /> + style="fill:#ffffff;stroke-width:0.998163" /> image/svg+xml \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/gray_banner.svg b/android/KMEA/app/src/main/assets/svg/gray_banner.svg new file mode 100644 index 00000000000..19f81f99e79 --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/gray_banner.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg index b99513bad30..f2e3d4b900d 100644 --- a/android/KMEA/app/src/main/assets/svg/white_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/white_banner.svg @@ -1,4 +1,4 @@ - - - \ No newline at end of file + + + \ No newline at end of file From 1d40beb90c98c824efde37eb84f2020ff2a31c22 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 9 Nov 2023 16:38:52 +0700 Subject: [PATCH 22/57] fix(android/engine): Get svg to fill banner --- .../kMAPro/src/main/assets/imagebanner.css | 21 ------------------- .../app/src/main/assets/svg/white_banner.svg | 2 +- 2 files changed, 1 insertion(+), 22 deletions(-) delete mode 100644 android/KMAPro/kMAPro/src/main/assets/imagebanner.css diff --git a/android/KMAPro/kMAPro/src/main/assets/imagebanner.css b/android/KMAPro/kMAPro/src/main/assets/imagebanner.css deleted file mode 100644 index 351052927c8..00000000000 --- a/android/KMAPro/kMAPro/src/main/assets/imagebanner.css +++ /dev/null @@ -1,21 +0,0 @@ -/*** - Keyman Engine for Android - Copyright 2023 SIL International -***/ - -/* - imagebanner.css: CSS for theming image banner (separate from KeymanWeb assets). - Image banner can be themed with .svg file -*/ - -/* -.phone.android .kmw-banner-image { - background-image: url('svg/keyman_banner.svg'); - position:absolute; -} - -.tablet.android .kmw-banner-image { - background-image: url('svg/gray_banner.svg'); - position:absolute; -} -*/ \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg index f2e3d4b900d..0c1d236acdf 100644 --- a/android/KMEA/app/src/main/assets/svg/white_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/white_banner.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file From 8961e9dbfa35751bc6e18953dada5e45e7e821ca Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Mon, 13 Nov 2023 10:23:01 +0700 Subject: [PATCH 23/57] fix(android/app): Revert keyman_banner.svg Also introduce banner.html to handle theme --- android/KMAPro/kMAPro/src/main/assets/svg/banner.html | 10 ++++++++++ .../kMAPro/src/main/assets/svg/keyman_banner.svg | 9 +++++---- 2 files changed, 15 insertions(+), 4 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/assets/svg/banner.html diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html new file mode 100644 index 00000000000..8120b6870e4 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html @@ -0,0 +1,10 @@ + +
+ + + + +
+
+
+
\ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg index f5d31a00187..3888a0937f9 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg +++ b/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg @@ -8,8 +8,8 @@ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" version="1.1" - width="100%" - height="auto" + width="1064.3625" + height="238.77588" id="svg2" xml:space="preserve" sodipodi:docname="keyman_banner.svg" @@ -40,11 +40,12 @@ inkscape:current-layer="svg2" /> + style="stroke-width:0.998163" /> image/svg+xml Date: Mon, 13 Nov 2023 12:49:35 +0700 Subject: [PATCH 24/57] refactor(android/engine): Pass contents of banner.html to KMW --- .../kMAPro/src/main/assets/svg/banner.html | 2 +- .../com/keyman/android/SystemKeyboard.java | 6 ++-- .../com/tavultesoft/kmapro/MainActivity.java | 2 +- .../KMEA/app/src/main/assets/keyboard.html | 4 +-- .../java/com/keyman/engine/KMKeyboard.java | 19 +++++++--- .../java/com/keyman/engine/KMManager.java | 8 ++--- .../com/keyman/engine/util/FileUtils.java | 35 +++++++++++++++++++ 7 files changed, 60 insertions(+), 16 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html index 8120b6870e4..88794c1472e 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html @@ -1,7 +1,7 @@
- +
diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index a737068d7e3..65da2284cfc 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -43,8 +43,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private KMHardwareKeyboardInterpreter interpreter = null; // Paths relative to assets folder for banner themes - public static final String KM_BANNER_THEME_KEYMAN = "svg/keyman_banner.svg"; - + public static final String KM_BANNER_THEME_KEYMAN = "svg/banner.html"; + public static final String KM_BANNER_THEME_KEYMAN_SVG = "svg/keyman_banner.svg"; private static final String TAG = "SystemKeyboard"; /** @@ -74,7 +74,7 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN, KM_BANNER_THEME_KEYMAN_SVG); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 54f1e9cd85e..061c1ff70b2 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -500,7 +500,7 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Set the in-app banner image here. Doesn't work in KMManager.init - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.KM_BANNER_THEME_WHITE); + KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_WHITE); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 044ca3b82cd..09b0326bf7a 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -19,10 +19,10 @@ - - + diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index ce2d13483e9..b44d27f3055 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -663,16 +663,25 @@ public String getBannerImage() { return this.bannerImagePath; } - public void setBannerImage(String path) { - this.bannerImagePath = path; // Save the path in case delayed initialization is needed + public void setBannerImage(String htmlPath, String svgPath) { + // Read the banner html contents + String contents = FileUtils.readContents(context, htmlPath); + + // If $BANNER string exists, replace with actual path + File bannerPath = new File(KMManager.getResourceRoot(), svgPath); + if (bannerPath.exists()) { + contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); + } + + this.bannerImagePath = contents; // Save the path in case delayed initialization is needed String logString = ""; - if (path != null && path.contains("base64") || path.length() > 256) { + if (contents != null && contents.contains("base64") || contents.length() > 256) { logString = ""; } else { - logString = path; + logString = contents; } KMLog.LogInfo(TAG, KMString.format("Banner image path: (%s).", logString)); - String jsString = KMString.format("setBannerImage('%s')", path); + String jsString = KMString.format("setBannerImage('%s')", contents); loadJavascript(jsString); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index f9097f9b314..7dca1be7c39 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -852,7 +852,7 @@ private static void copyAssets(Context context) { copyAsset(context, KMFilename_JSPolyfill, "", true); // Copy image banner css - copyAsset(context, KMFilename_ImageBannerCss, "", true); + //copyAsset(context, KMFilename_ImageBannerCss, "", true); // SVG directory for banner themes File svgDir = new File(getSVGDir()); @@ -1463,11 +1463,11 @@ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { * @param {String} path * @return */ - public static boolean setBannerImage(KeyboardType keyboard, String path) { + public static boolean setBannerImage(KeyboardType keyboard, String htmlPath, String svgPath) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { - InAppKeyboard.setBannerImage(path); + InAppKeyboard.setBannerImage(htmlPath, svgPath); } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { - SystemKeyboard.setBannerImage(path); + SystemKeyboard.setBannerImage(htmlPath, svgPath); } else { Log.d(TAG, "setBannerImage but keyboard is null"); return false; diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/FileUtils.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/FileUtils.java index d5610dedec2..6b04b974f8a 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/FileUtils.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/FileUtils.java @@ -4,16 +4,22 @@ package com.keyman.engine.util; import android.content.Context; +import android.content.res.AssetManager; +import android.util.Log; + +import com.keyman.engine.KMManager; import org.json.JSONObject; import org.json.JSONArray; import java.io.BufferedInputStream; +import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.nio.charset.StandardCharsets; @@ -248,6 +254,35 @@ public static boolean saveList(File filepath, Object obj) { return result; } + /** + * Read the contents of asset file as a string + * Reference: https://stackoverflow.com/questions/16110002/read-assets-file-as-string + * @param context + * @param path - path of file relative to assets folder + * @return String + */ + public static String readContents(Context context, String path) { + StringBuilder sb = new StringBuilder(); + String str = ""; + AssetManager assetManager = context.getAssets(); + try { + InputStream inputStream = assetManager.open(path); + if (inputStream == null) { + KMLog.LogInfo(TAG, "Unable to read contents of asset: " + path); + return str; + } + BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)); + while ((str = reader.readLine()) != null) { + sb.append(str); + } + reader.close(); + } catch (Exception e) { + KMLog.LogException(TAG, "Error reading asset file", e); + return str; + } + return sb.toString(); + } + /** * Utility to parse a URL and extract the filename * @param urlStr String From cb431fef2047f4fad4415dcc94918fec21208ee9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 10:20:18 +0700 Subject: [PATCH 25/57] refactor(android): Use showBanner --- .../kMAPro/src/main/assets/svg/banner.html | 2 +- .../com/keyman/android/SystemKeyboard.java | 5 +- .../com/tavultesoft/kmapro/MainActivity.java | 7 +-- .../KMEA/app/src/main/assets/android-host.js | 41 +++++++-------- .../KMEA/app/src/main/assets/svg/banner.html | 5 ++ .../app/src/main/assets/svg/black_banner.svg | 4 ++ .../java/com/keyman/engine/KMKeyboard.java | 51 +++++-------------- .../engine/KMKeyboardWebViewClient.java | 3 +- .../java/com/keyman/engine/KMManager.java | 44 ++++++---------- 9 files changed, 64 insertions(+), 98 deletions(-) create mode 100644 android/KMEA/app/src/main/assets/svg/banner.html create mode 100644 android/KMEA/app/src/main/assets/svg/black_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html index 88794c1472e..37a5ae3647c 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html @@ -7,4 +7,4 @@
-
\ No newline at end of file + diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 65da2284cfc..aee5670cb7d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -74,8 +74,9 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN, KM_BANNER_THEME_KEYMAN_SVG); - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); + KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN, KM_BANNER_THEME_KEYMAN_SVG); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.HTML); + KMManager.showBanner(false); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 061c1ff70b2..c10c85f41e1 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -499,9 +499,10 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { - // Set the in-app banner image here. Doesn't work in KMManager.init - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_WHITE); - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); + // Set the in-app HTML banner here. Doesn't work in KMManager.init + KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_WHITE); + KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.HTML); + KMManager.showBanner(false); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 608ba23d13b..d3a7293b11e 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -9,6 +9,8 @@ var device = window.jsInterface.getDeviceType(); var oskHeight = Math.ceil(window.jsInterface.getKeyboardHeight() / window.devicePixelRatio); var oskWidth = 0; var bannerHeight = 0; +var bannerImagePath = ''; +var bannerHTMLContents = ''; var fragmentToggle = 0; var sentryManager = new KeymanSentryManager({ @@ -57,35 +59,26 @@ function init() { } function showBanner(flag) { - window.console.log("setOptions{'alwaysShow': " + flag + "}"); - if (keyman.osk) { - keyman.osk.bannerController.setOptions({"alwaysShow": flag}); - } -} - -// If KeymanWeb banner currently blank, set to bannerType -// @param type 'blank' | 'image' | 'suggestion' - A plain-text string -// representing the type of Banner to set active. -function setBanner(bannerType) { - if (keyman.osk && keyman.osk.bannerController) { - window.console.log('setBanner: current KMW banner is: ' + keyman.osk.bannerController.activeType); - window.console.log('setBanner: options are: ' + JSON.stringify(keyman.osk.bannerController.getOptions())); - if (keyman.osk.bannerController.activeType == 'blank') { - keyman.osk.bannerController.setBanner(bannerType); + window.console.log("Setting banner display for dictionaryless keyboards to " + flag); + window.console.log("bannerHTMLContents: " + bannerHTMLContents); + var bc = keyman.osk.bannerController; + if (bc) { + if (bannerHTMLContents != '') { + bc.inactiveBanner = flag ? new bc.HTMLBanner(bannerHTMLContents) : null; + } else { + bc.inactiveBanner = flag ? new bc.ImageBanner(bannerImgPath) : null; } - } else { - window.console.log('Not overriding banner'); } } -// Set the path to use for the image banner -// path - String starting with "data:image/png;base64," function setBannerImage(path) { - if (keyman.osk) { - keyman.osk.bannerController.setOptions({ - "imagePath": path, - "alwaysShow": true}); - } + bannerImgPath = path; +} + +// Set the HTML banner to use when predictive-text is not available +// contents - HTML content to use for the banner +function setBannerHTML(contents) { + bannerHTMLContents = contents; } function notifyHost(event, params) { diff --git a/android/KMEA/app/src/main/assets/svg/banner.html b/android/KMEA/app/src/main/assets/svg/banner.html new file mode 100644 index 00000000000..5db63c61df9 --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/banner.html @@ -0,0 +1,5 @@ + +
+ + +
diff --git a/android/KMEA/app/src/main/assets/svg/black_banner.svg b/android/KMEA/app/src/main/assets/svg/black_banner.svg new file mode 100644 index 00000000000..050cea97e9a --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/black_banner.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index b44d27f3055..207f4b1ec34 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -7,13 +7,11 @@ import java.io.File; import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import com.keyman.engine.BaseActivity; import com.keyman.engine.data.Keyboard; import com.keyman.engine.data.KeyboardController; import com.keyman.engine.KMManager.KeyboardType; @@ -25,19 +23,11 @@ import com.keyman.engine.util.KMLog; import com.keyman.engine.util.KMString; -import android.Manifest; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; import android.content.res.Configuration; -import android.graphics.Color; -import android.graphics.Rect; -import android.graphics.RectF; -import android.graphics.Typeface; -import android.os.Build; -import android.os.Bundle; import android.os.Handler; import android.util.DisplayMetrics; import android.util.Log; @@ -46,7 +36,6 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; -import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; @@ -56,12 +45,9 @@ import android.webkit.WebSettings; import android.webkit.WebView; import android.widget.Button; -import android.widget.FrameLayout; -import android.widget.GridLayout; import android.widget.PopupWindow; import android.widget.PopupWindow.OnDismissListener; import android.widget.RelativeLayout; -import android.widget.TextView; import android.widget.Toast; import io.sentry.Breadcrumb; @@ -87,7 +73,7 @@ final class KMKeyboard extends WebView { /** * Current banner state. */ - protected static KMManager.BannerType currentBanner = KMManager.BannerType.IMAGE; + protected static KMManager.BannerType currentBanner = KMManager.BannerType.HTML; private static String txtFont = ""; private static String oskFont = null; @@ -96,9 +82,9 @@ final class KMKeyboard extends WebView { private GestureDetector gestureDetector; private static ArrayList kbEventListeners = null; - // Stores the current image for use by the Banner + // Stores the current html string for use by the Banner // when predictive text is not active - protected String bannerImagePath; + protected String htmlBannerString = ""; // Facilitates a 'lazy init' - we'll only check the preference when it matters, // rather than at construction time. @@ -395,8 +381,8 @@ public void onConfigurationChanged(Configuration newConfig) { int bannerHeight = KMManager.getBannerHeight(context); int oskHeight = KMManager.getKeyboardHeight(context); - if (this.bannerImagePath != null) { - loadJavascript(KMString.format("setBannerImage('%s')", this.bannerImagePath)); + if (this.htmlBannerString != null && !this.htmlBannerString.isEmpty()) { + loadJavascript(KMString.format("setBannerHTML('%s')", this.htmlBannerString)); } loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); loadJavascript(KMString.format("setOskWidth(%d)", newConfig.screenWidthDp)); @@ -427,12 +413,10 @@ protected void toggleSuggestionBanner(HashMap associatedLexicalM //reset banner state if new language has no lexical model if (currentBanner == KMManager.BannerType.SUGGESTION && associatedLexicalModel == null) { - setBanner(KMManager.BannerType.IMAGE); + currentBanner = KMManager.BannerType.HTML; } - if(keyboardChanged) { - setLayoutParams(KMManager.getKeyboardLayoutParams()); - } + // Since there's always a banner, no need to update setLayoutParams() } /** @@ -655,15 +639,14 @@ public KMManager.BannerType getBanner() { } public void setBanner(KMManager.BannerType bannerType) { - String jsString = KMString.format("setBanner('%s')", bannerType.toString()); - loadJavascript(jsString); + currentBanner = bannerType; } - public String getBannerImage() { - return this.bannerImagePath; + public String getHTMLBanner() { + return this.htmlBannerString; } - public void setBannerImage(String htmlPath, String svgPath) { + public void setHTMLBanner(String htmlPath, String svgPath) { // Read the banner html contents String contents = FileUtils.readContents(context, htmlPath); @@ -673,15 +656,9 @@ public void setBannerImage(String htmlPath, String svgPath) { contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); } - this.bannerImagePath = contents; // Save the path in case delayed initialization is needed - String logString = ""; - if (contents != null && contents.contains("base64") || contents.length() > 256) { - logString = ""; - } else { - logString = contents; - } - KMLog.LogInfo(TAG, KMString.format("Banner image path: (%s).", logString)); - String jsString = KMString.format("setBannerImage('%s')", contents); + this.htmlBannerString = contents; + KMLog.LogInfo(TAG, KMString.format("HTML Banner string: (%s).", contents)); + String jsString = KMString.format("setBannerHTML('%s')", contents); loadJavascript(jsString); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java index 9a501eadb18..b0172e7e5b7 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java @@ -173,8 +173,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { if (KMManager.currentLexicalModel != null) { modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true); } - kmKeyboard.setBanner((isModelActive && modelPredictionPref) ? - KMManager.BannerType.SUGGESTION : KMManager.BannerType.IMAGE); + kmKeyboard.showBanner(isModelActive && modelPredictionPref); RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); kmKeyboard.setLayoutParams(params); } else if (url.indexOf("suggestPopup") >= 0) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 7dca1be7c39..7d562e62798 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -25,32 +25,21 @@ import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.graphics.RectF; import android.graphics.Typeface; import android.inputmethodservice.InputMethodService; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.net.Uri; import android.os.Build; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; import android.text.InputType; import android.util.Log; -import android.view.HapticFeedbackConstants; -import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.ExtractedText; -import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; -import android.webkit.JavascriptInterface; import android.webkit.WebView; -import android.webkit.WebViewClient; import android.widget.FrameLayout; import android.widget.RelativeLayout; @@ -71,7 +60,6 @@ import com.keyman.engine.packages.LexicalModelPackageProcessor; import com.keyman.engine.packages.PackageProcessor; import com.keyman.engine.util.BCP47; -import com.keyman.engine.util.CharSequenceUtil; import com.keyman.engine.util.DependencyUtil; import com.keyman.engine.util.DependencyUtil.LibraryType; import com.keyman.engine.util.FileUtils; @@ -157,12 +145,13 @@ public String toString() { } }; + // Maps to enum BannerType in bannerView.ts public enum BannerType { BLANK, IMAGE, - SUGGESTION; + SUGGESTION, + HTML; - // Maps to enum BannerType in bannerView.ts public static BannerType fromString(String mode) { if (mode == null) return BLANK; switch (mode) { @@ -172,12 +161,14 @@ public static BannerType fromString(String mode) { return IMAGE; case "suggestion": return SUGGESTION; + case "html": + return HTML; } return BLANK; } public String toString() { - String modes[] = { "blank", "image", "suggestion"}; + String modes[] = { "blank", "image", "suggestion", "html"}; return modes[this.ordinal()]; } } @@ -202,6 +193,7 @@ public String toString() { // Paths relative to assets folder for banner themes public static final String KM_BANNER_THEME_BLACK = "svg/black_banner.svg"; + public static final String KM_BANNER_THEME_GRAY = "svg/gray_banner.svg"; public static final String KM_BANNER_THEME_WHITE = "svg/white_banner.svg"; protected static KMKeyboard InAppKeyboard = null; @@ -313,9 +305,6 @@ public String toString() { protected static final String KMFilename_Osk_Ttf_Font = "keymanweb-osk.ttf"; protected static final String KMFilename_JSPolyfill = "es6-shim.min.js"; - // CSS for image banner theming - protected static final String KMFilename_ImageBannerCss = "imagebanner.css"; - // Deprecated by KeyboardController.KMFilename_Installed_KeyboardsList public static final String KMFilename_KeyboardsList = "keyboards_list.dat"; @@ -851,10 +840,7 @@ private static void copyAssets(Context context) { copyAsset(context, KMDefault_KeyboardFont, "", true); copyAsset(context, KMFilename_JSPolyfill, "", true); - // Copy image banner css - //copyAsset(context, KMFilename_ImageBannerCss, "", true); - - // SVG directory for banner themes + // SVG directory for html banner themes File svgDir = new File(getSVGDir()); if (!svgDir.exists()) { svgDir.mkdir(); @@ -1463,23 +1449,23 @@ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { * @param {String} path * @return */ - public static boolean setBannerImage(KeyboardType keyboard, String htmlPath, String svgPath) { + public static boolean setHTMLBanner(KeyboardType keyboard, String htmlPath, String svgPath) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { - InAppKeyboard.setBannerImage(htmlPath, svgPath); + InAppKeyboard.setHTMLBanner(htmlPath, svgPath); } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { - SystemKeyboard.setBannerImage(htmlPath, svgPath); + SystemKeyboard.setHTMLBanner(htmlPath, svgPath); } else { - Log.d(TAG, "setBannerImage but keyboard is null"); + Log.d(TAG, "setHTMLBanner() but keyboard is null"); return false; } return true; } - public static String getBannerImage(KeyboardType keyboard) { + public static String getHTMLBanner(KeyboardType keyboard) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { - return InAppKeyboard.getBannerImage(); + return InAppKeyboard.getHTMLBanner(); } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { - return SystemKeyboard.getBannerImage(); + return SystemKeyboard.getHTMLBanner(); } return ""; } From 263da4092c1a572c0e52d7efbf1d20598ea5b0be Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 12:09:28 +0700 Subject: [PATCH 26/57] fix(android/engine): Handle mayPredict --- .../src/main/java/com/keyman/android/SystemKeyboard.java | 2 +- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 8 ++++++-- android/KMEA/app/src/main/assets/svg/black_banner.svg | 2 +- android/KMEA/app/src/main/assets/svg/gray_banner.svg | 4 ++-- android/KMEA/app/src/main/assets/svg/white_banner.svg | 2 +- .../java/com/keyman/engine/KMKeyboardWebViewClient.java | 2 +- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index aee5670cb7d..3d213ee2a27 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -76,7 +76,7 @@ public void onCreate() { KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN, KM_BANNER_THEME_KEYMAN_SVG); KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.HTML); - KMManager.showBanner(false); + KMManager.showBanner(true); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index c10c85f41e1..feb8466c8b1 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -500,9 +500,13 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Set the in-app HTML banner here. Doesn't work in KMManager.init - KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_WHITE); + if (KMManager.getFormFactor() == KMManager.FormFactor.PHONE) { + KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_BLACK); + } else { + KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_GRAY); + } KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.HTML); - KMManager.showBanner(false); + KMManager.showBanner(true); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/svg/black_banner.svg b/android/KMEA/app/src/main/assets/svg/black_banner.svg index 050cea97e9a..a17e7aea22e 100644 --- a/android/KMEA/app/src/main/assets/svg/black_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/black_banner.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/gray_banner.svg b/android/KMEA/app/src/main/assets/svg/gray_banner.svg index 19f81f99e79..bb55f7b6c8c 100644 --- a/android/KMEA/app/src/main/assets/svg/gray_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/gray_banner.svg @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg index 0c1d236acdf..ab73b9fb13e 100644 --- a/android/KMEA/app/src/main/assets/svg/white_banner.svg +++ b/android/KMEA/app/src/main/assets/svg/white_banner.svg @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java index b0172e7e5b7..4d458bd0616 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboardWebViewClient.java @@ -173,7 +173,7 @@ public boolean shouldOverrideUrlLoading(WebView view, String url) { if (KMManager.currentLexicalModel != null) { modelPredictionPref = prefs.getBoolean(KMManager.getLanguagePredictionPreferenceKey(KMManager.currentLexicalModel.get(KMManager.KMKey_LanguageID)), true); } - kmKeyboard.showBanner(isModelActive && modelPredictionPref); + KMManager.setBannerOptions(isModelActive && modelPredictionPref); RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); kmKeyboard.setLayoutParams(params); } else if (url.indexOf("suggestPopup") >= 0) { From 712b2a28b66a6dd5114cc9050bdf04b2cb20748e Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 13:11:13 +0700 Subject: [PATCH 27/57] refactor(android): Move banner logic to app --- .../com/keyman/android/BannerController.java | 45 +++++++++++++++++++ .../com/keyman/android/SystemKeyboard.java | 8 +--- .../com/tavultesoft/kmapro/MainActivity.java | 9 +--- .../KMEA/app/src/main/assets/svg/banner.html | 5 --- .../app/src/main/assets/svg/black_banner.svg | 4 -- .../app/src/main/assets/svg/gray_banner.svg | 4 -- .../app/src/main/assets/svg/phone_banner.html | 3 ++ .../src/main/assets/svg/tablet_banner.html | 3 ++ .../app/src/main/assets/svg/white_banner.svg | 4 -- .../java/com/keyman/engine/KMKeyboard.java | 11 +---- .../java/com/keyman/engine/KMManager.java | 17 +++---- 11 files changed, 62 insertions(+), 51 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java delete mode 100644 android/KMEA/app/src/main/assets/svg/banner.html delete mode 100644 android/KMEA/app/src/main/assets/svg/black_banner.svg delete mode 100644 android/KMEA/app/src/main/assets/svg/gray_banner.svg create mode 100644 android/KMEA/app/src/main/assets/svg/phone_banner.html create mode 100644 android/KMEA/app/src/main/assets/svg/tablet_banner.html delete mode 100644 android/KMEA/app/src/main/assets/svg/white_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java new file mode 100644 index 00000000000..d4e3480a393 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -0,0 +1,45 @@ +package com.keyman.android; + +import android.content.Context; + +import com.keyman.engine.KMManager; +import com.keyman.engine.util.FileUtils; +import com.keyman.engine.util.KMLog; +import com.keyman.engine.util.KMString; + +import java.io.File; + +public class BannerController { + + // Paths relative to assets folder for banner themes + public static final String KM_BANNER_THEME_PHONE = "svg/phone_banner.html"; + public static final String KM_BANNER_THEME_TABLET = "svg/tablet_banner.html"; + public static final String KM_BANNER_THEME_KEYMAN = "svg/banner.html"; + + // Paths relative to assets folder for banner themes + public static final String KM_BANNER_THEME_KEYMAN_SVG = "svg/keyman_banner.svg"; + + + public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { + if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { + return; + } + + String htmlPath = (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM) ? + KM_BANNER_THEME_KEYMAN : + (KMManager.getFormFactor() == KMManager.FormFactor.PHONE) ? + KM_BANNER_THEME_PHONE : KM_BANNER_THEME_TABLET; + + String contents = FileUtils.readContents(context, htmlPath); + + // If $BANNER string exists, replace with actual path + File bannerPath = new File(KMManager.getResourceRoot(), KM_BANNER_THEME_KEYMAN_SVG); + if (bannerPath.exists()) { + contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); + } + + KMManager.setHTMLBanner(keyboardType, contents); + KMManager.setBanner(keyboardType, KMManager.BannerType.HTML); + KMManager.showBanner(true); + } +} diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 3d213ee2a27..8cb0e948a32 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -6,6 +6,7 @@ import com.tavultesoft.kmapro.BuildConfig; import com.tavultesoft.kmapro.KeymanSettingsActivity; +import com.keyman.android.BannerController; import com.keyman.engine.KMManager; import com.keyman.engine.KMManager.KeyboardType; import com.keyman.engine.KMHardwareKeyboardInterpreter; @@ -42,9 +43,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static ExtractedText exText = null; private KMHardwareKeyboardInterpreter interpreter = null; - // Paths relative to assets folder for banner themes - public static final String KM_BANNER_THEME_KEYMAN = "svg/banner.html"; - public static final String KM_BANNER_THEME_KEYMAN_SVG = "svg/keyman_banner.svg"; private static final String TAG = "SystemKeyboard"; /** @@ -74,9 +72,7 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KM_BANNER_THEME_KEYMAN, KM_BANNER_THEME_KEYMAN_SVG); - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.HTML); - KMManager.showBanner(true); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); KMManager.setHapticFeedback(mayHaveHapticFeedback); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index feb8466c8b1..0a2d450eca4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -18,6 +18,7 @@ import java.util.Map; import com.keyman.android.CheckInstallReferrer; +import com.keyman.android.BannerController; import com.keyman.engine.BaseActivity; import com.keyman.engine.KMHelpFileActivity; import com.keyman.engine.KMKeyboardDownloaderActivity; @@ -500,13 +501,7 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Set the in-app HTML banner here. Doesn't work in KMManager.init - if (KMManager.getFormFactor() == KMManager.FormFactor.PHONE) { - KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_BLACK); - } else { - KMManager.setHTMLBanner(KeyboardType.KEYBOARD_TYPE_INAPP, "svg/banner.html", KMManager.KM_BANNER_THEME_GRAY); - } - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.HTML); - KMManager.showBanner(true); + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/svg/banner.html b/android/KMEA/app/src/main/assets/svg/banner.html deleted file mode 100644 index 5db63c61df9..00000000000 --- a/android/KMEA/app/src/main/assets/svg/banner.html +++ /dev/null @@ -1,5 +0,0 @@ - -
- - -
diff --git a/android/KMEA/app/src/main/assets/svg/black_banner.svg b/android/KMEA/app/src/main/assets/svg/black_banner.svg deleted file mode 100644 index a17e7aea22e..00000000000 --- a/android/KMEA/app/src/main/assets/svg/black_banner.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/gray_banner.svg b/android/KMEA/app/src/main/assets/svg/gray_banner.svg deleted file mode 100644 index bb55f7b6c8c..00000000000 --- a/android/KMEA/app/src/main/assets/svg/gray_banner.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/KMEA/app/src/main/assets/svg/phone_banner.html b/android/KMEA/app/src/main/assets/svg/phone_banner.html new file mode 100644 index 00000000000..e7bb74265c0 --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/phone_banner.html @@ -0,0 +1,3 @@ + +
+
diff --git a/android/KMEA/app/src/main/assets/svg/tablet_banner.html b/android/KMEA/app/src/main/assets/svg/tablet_banner.html new file mode 100644 index 00000000000..f086405eece --- /dev/null +++ b/android/KMEA/app/src/main/assets/svg/tablet_banner.html @@ -0,0 +1,3 @@ + +
+
diff --git a/android/KMEA/app/src/main/assets/svg/white_banner.svg b/android/KMEA/app/src/main/assets/svg/white_banner.svg deleted file mode 100644 index ab73b9fb13e..00000000000 --- a/android/KMEA/app/src/main/assets/svg/white_banner.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 207f4b1ec34..b7427f91107 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -646,16 +646,7 @@ public String getHTMLBanner() { return this.htmlBannerString; } - public void setHTMLBanner(String htmlPath, String svgPath) { - // Read the banner html contents - String contents = FileUtils.readContents(context, htmlPath); - - // If $BANNER string exists, replace with actual path - File bannerPath = new File(KMManager.getResourceRoot(), svgPath); - if (bannerPath.exists()) { - contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); - } - + public void setHTMLBanner(String contents) { this.htmlBannerString = contents; KMLog.LogInfo(TAG, KMString.format("HTML Banner string: (%s).", contents)); String jsString = KMString.format("setBannerHTML('%s')", contents); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 7d562e62798..4bc9ba3ea6b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -191,11 +191,6 @@ public String toString() { private static KMManager.SpacebarText spacebarText = KMManager.SpacebarText.LANGUAGE_KEYBOARD; // must match default given in kmwbase.ts - // Paths relative to assets folder for banner themes - public static final String KM_BANNER_THEME_BLACK = "svg/black_banner.svg"; - public static final String KM_BANNER_THEME_GRAY = "svg/gray_banner.svg"; - public static final String KM_BANNER_THEME_WHITE = "svg/white_banner.svg"; - protected static KMKeyboard InAppKeyboard = null; protected static KMKeyboard SystemKeyboard = null; protected static KMKeyboardWebViewClient InAppKeyboardWebViewClient = null; @@ -1444,16 +1439,16 @@ public static boolean setBanner(KeyboardType keyboard, BannerType bannerType) { } /** - * Set the path to use with the image banner + * Set the HTML content to use with the HTML banner * @param {KeyboardType} keyboard - * @param {String} path - * @return + * @param {String} HTMl string + * @return {boolean} */ - public static boolean setHTMLBanner(KeyboardType keyboard, String htmlPath, String svgPath) { + public static boolean setHTMLBanner(KeyboardType keyboard, String htmlContent) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { - InAppKeyboard.setHTMLBanner(htmlPath, svgPath); + InAppKeyboard.setHTMLBanner(htmlContent); } else if (keyboard == KeyboardType.KEYBOARD_TYPE_SYSTEM && SystemKeyboard != null) { - SystemKeyboard.setHTMLBanner(htmlPath, svgPath); + SystemKeyboard.setHTMLBanner(htmlContent); } else { Log.d(TAG, "setHTMLBanner() but keyboard is null"); return false; From 856ed135946e65a7d23014835ec15e50c4f17449 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 13:27:25 +0700 Subject: [PATCH 28/57] fix(android/app): Always use Keyman theme --- android/KMAPro/kMAPro/src/main/assets/svg/banner.html | 6 +++--- .../main/java/com/keyman/android/BannerController.java | 8 ++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html index 37a5ae3647c..8750ba1e05c 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html @@ -4,7 +4,7 @@ -
-
-
+
+
+
diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index d4e3480a393..12fbdd87841 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -25,12 +25,8 @@ public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboar return; } - String htmlPath = (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM) ? - KM_BANNER_THEME_KEYMAN : - (KMManager.getFormFactor() == KMManager.FormFactor.PHONE) ? - KM_BANNER_THEME_PHONE : KM_BANNER_THEME_TABLET; - - String contents = FileUtils.readContents(context, htmlPath); + // Always use Keyman banner theme + String contents = FileUtils.readContents(context, KM_BANNER_THEME_KEYMAN); // If $BANNER string exists, replace with actual path File bannerPath = new File(KMManager.getResourceRoot(), KM_BANNER_THEME_KEYMAN_SVG); From 830c282ff8533b18b4a7110c13d227b24df0c1dc Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 14:01:52 +0700 Subject: [PATCH 29/57] chore(android): Cleanup code --- .../main/java/com/keyman/android/SystemKeyboard.java | 1 + .../java/com/tavultesoft/kmapro/MainActivity.java | 5 ++++- .../src/main/java/com/keyman/engine/KMManager.java | 12 ++++++++++++ .../app/src/main/assets/svg/green_banner.svg | 1 - .../main/java/com/keyman/kmsample1/MainActivity.java | 5 ----- .../app/src/main/assets/svg/green_banner.svg | 1 - .../java/com/keyman/kmsample2/SystemKeyboard.java | 8 -------- 7 files changed, 17 insertions(+), 16 deletions(-) delete mode 100644 android/Samples/KMSample1/app/src/main/assets/svg/green_banner.svg delete mode 100644 android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 8cb0e948a32..984d0595f73 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -72,6 +72,7 @@ public void onCreate() { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); + // Set the system keyboard HTML banner BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); boolean mayHaveHapticFeedback = prefs.getBoolean(KeymanSettingsActivity.hapticFeedbackKey, false); diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 0a2d450eca4..85ad20300d5 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -187,6 +187,9 @@ protected void onCreate(Bundle savedInstanceState) { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); + // Set the in-app HTML banner (replaces the default black banner) + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); + checkHapticFeedback(); setContentView(R.layout.activity_main); @@ -501,7 +504,7 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Set the in-app HTML banner here. Doesn't work in KMManager.init - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); + //BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 4bc9ba3ea6b..bebc6a6ad8d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -305,6 +305,9 @@ public String toString() { public static final String KMFilename_LexicalModelsList = "lexical_models_list.dat"; + public static final String KMBLACK_BANNER = "
"; + public static final String KMGRAY_BANNER = "
"; + private static Context appContext; public static String getResourceRoot() { @@ -658,6 +661,15 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) keyboard.addJavascriptInterface(new KMKeyboardJSHandler(appContext, keyboard), "jsInterface"); keyboard.loadKeyboard(); + // For apps that don't specify an HTML banner, specify a default phone/tablet HTML banner + if (getFormFactor() == FormFactor.PHONE) { + keyboard.setHTMLBanner(KMBLACK_BANNER); + } else { + keyboard.setHTMLBanner(KMGRAY_BANNER); + } + keyboard.setBanner(KMManager.BannerType.HTML); + keyboard.showBanner(true); + setEngineWebViewVersionStatus(appContext, keyboard); } diff --git a/android/Samples/KMSample1/app/src/main/assets/svg/green_banner.svg b/android/Samples/KMSample1/app/src/main/assets/svg/green_banner.svg deleted file mode 100644 index 7bf4843cef5..00000000000 --- a/android/Samples/KMSample1/app/src/main/assets/svg/green_banner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 837a23f4a22..70a08b18632 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -126,11 +126,6 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { // Handle Keyman keyboard shown event here if needed - - // Uncomment these lines if the app doesn't use a dictionary - // and wants to set the in-app banner image here. - //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_INAPP, BANNER_THEME_KMSAMPLE1); - //KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_INAPP, KMManager.BannerType.IMAGE); } @Override diff --git a/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg b/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg deleted file mode 100644 index 7bf4843cef5..00000000000 --- a/android/Samples/KMSample2/app/src/main/assets/svg/green_banner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java index 8210bb3c803..e4ab04e50f5 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java @@ -27,9 +27,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven private static View inputView = null; private KMHardwareKeyboardInterpreter interpreter = null; - // Paths relative to assets folder for banner themes. Change this for custom banner - public static final String BANNER_THEME_KMSAMPLE2 = "svg/green_banner.svg"; - /** * Main initialization of the input method component. Be sure to call * to super class. @@ -70,11 +67,6 @@ public void onCreate() { lexicalModelInfo.put(KMManager.KMKey_LexicalModelVersion, "1.0"); KMManager.addLexicalModel(this, lexicalModelInfo); KMManager.registerAssociatedLexicalModel("ta"); - - // Uncomment these two lines if keyboard doesn't use a dictionary. - // The suggestion banner is replaced with this color. - //KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, BANNER_THEME_KMSAMPLE2); - //KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); } @Override From 27480ebefa8e634eb331f76e3a843e7f5b5c7aaf Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 15:31:47 +0700 Subject: [PATCH 30/57] chore(android): Additional cleanup --- .../com/keyman/android/BannerController.java | 4 ---- .../com/tavultesoft/kmapro/MainActivity.java | 4 ++-- .../KMEA/app/src/main/assets/imagebanner.css | 19 ------------------- .../app/src/main/assets/svg/phone_banner.html | 3 --- .../src/main/assets/svg/tablet_banner.html | 3 --- .../java/com/keyman/engine/KMKeyboard.java | 1 + 6 files changed, 3 insertions(+), 31 deletions(-) delete mode 100644 android/KMEA/app/src/main/assets/imagebanner.css delete mode 100644 android/KMEA/app/src/main/assets/svg/phone_banner.html delete mode 100644 android/KMEA/app/src/main/assets/svg/tablet_banner.html diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index 12fbdd87841..6ab6af05263 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -12,11 +12,7 @@ public class BannerController { // Paths relative to assets folder for banner themes - public static final String KM_BANNER_THEME_PHONE = "svg/phone_banner.html"; - public static final String KM_BANNER_THEME_TABLET = "svg/tablet_banner.html"; public static final String KM_BANNER_THEME_KEYMAN = "svg/banner.html"; - - // Paths relative to assets folder for banner themes public static final String KM_BANNER_THEME_KEYMAN_SVG = "svg/keyman_banner.svg"; diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 85ad20300d5..1b5df4015a9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -503,8 +503,8 @@ public void onKeyboardChanged(String newKeyboard) { @Override public void onKeyboardShown() { - // Set the in-app HTML banner here. Doesn't work in KMManager.init - //BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); + // Refresh banner theme + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); resizeTextView(true); } diff --git a/android/KMEA/app/src/main/assets/imagebanner.css b/android/KMEA/app/src/main/assets/imagebanner.css deleted file mode 100644 index 06cfe7f06bf..00000000000 --- a/android/KMEA/app/src/main/assets/imagebanner.css +++ /dev/null @@ -1,19 +0,0 @@ -/*** - Keyman Engine for Android - Copyright 2023 SIL International -***/ - -/* - imagebanner.css: CSS for theming image banner (separate from KeymanWeb assets). - Image banner can be themed with .svg file -*/ - -.phone.android .kmw-banner-image { - background-image: url('svg/white_banner.svg'); - position:absolute; -} - -.tablet.android .kmw-banner-image { - background-image: url('svg/gray_banner.svg'); - position:absolute; -} diff --git a/android/KMEA/app/src/main/assets/svg/phone_banner.html b/android/KMEA/app/src/main/assets/svg/phone_banner.html deleted file mode 100644 index e7bb74265c0..00000000000 --- a/android/KMEA/app/src/main/assets/svg/phone_banner.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/android/KMEA/app/src/main/assets/svg/tablet_banner.html b/android/KMEA/app/src/main/assets/svg/tablet_banner.html deleted file mode 100644 index f086405eece..00000000000 --- a/android/KMEA/app/src/main/assets/svg/tablet_banner.html +++ /dev/null @@ -1,3 +0,0 @@ - -
-
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index b7427f91107..1136cc3a4c6 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -416,6 +416,7 @@ protected void toggleSuggestionBanner(HashMap associatedLexicalM currentBanner = KMManager.BannerType.HTML; } + showBanner(true); // Since there's always a banner, no need to update setLayoutParams() } From 095b8236b4333e1fb60e6ca45461bcc482b1ba58 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 14 Nov 2023 20:10:39 +0700 Subject: [PATCH 31/57] chore(android): More cleanup * Revert original css in keyboard.html * Remove redundant call to BannerController --- .../src/main/java/com/tavultesoft/kmapro/MainActivity.java | 3 --- android/KMEA/app/src/main/assets/keyboard.html | 3 +-- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 1b5df4015a9..16899f7c2d9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -187,9 +187,6 @@ protected void onCreate(Bundle savedInstanceState) { KMManager.SpacebarText spacebarText = KMManager.SpacebarText.fromString(prefs.getString(KeymanSettingsActivity.spacebarTextKey, KMManager.SpacebarText.LANGUAGE_KEYBOARD.toString())); KMManager.setSpacebarText(spacebarText); - // Set the in-app HTML banner (replaces the default black banner) - BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_INAPP); - checkHapticFeedback(); setContentView(R.layout.activity_main); diff --git a/android/KMEA/app/src/main/assets/keyboard.html b/android/KMEA/app/src/main/assets/keyboard.html index 09b0326bf7a..b11a21124ec 100644 --- a/android/KMEA/app/src/main/assets/keyboard.html +++ b/android/KMEA/app/src/main/assets/keyboard.html @@ -19,10 +19,9 @@ - From c83410a1286bc0a67017d63a0e81a8d81a984bca Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 15 Nov 2023 08:32:36 +0700 Subject: [PATCH 32/57] fix(oem/fv/android): Apply teal banner theme --- .../com/keyman/android/BannerController.java | 2 - .../app/src/main/assets/svg/banner.html | 9 +++++ .../app/src/main/assets/svg/gold_banner.svg | 1 - .../app/src/main/assets/svg/teal-logo.svg | 39 +++++++++++++++++++ .../firstvoices/android/BannerController.java | 35 +++++++++++++++++ .../firstvoices/keyboards/SystemKeyboard.java | 8 ++-- 6 files changed, 86 insertions(+), 8 deletions(-) create mode 100644 oem/firstvoices/android/app/src/main/assets/svg/banner.html delete mode 100644 oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg create mode 100644 oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg create mode 100644 oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index 6ab6af05263..d5c48e970b4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -4,8 +4,6 @@ import com.keyman.engine.KMManager; import com.keyman.engine.util.FileUtils; -import com.keyman.engine.util.KMLog; -import com.keyman.engine.util.KMString; import java.io.File; diff --git a/oem/firstvoices/android/app/src/main/assets/svg/banner.html b/oem/firstvoices/android/app/src/main/assets/svg/banner.html new file mode 100644 index 00000000000..704b4b58515 --- /dev/null +++ b/oem/firstvoices/android/app/src/main/assets/svg/banner.html @@ -0,0 +1,9 @@ + +
+ + + + +
+ +
diff --git a/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg b/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg deleted file mode 100644 index d9456ba83ed..00000000000 --- a/oem/firstvoices/android/app/src/main/assets/svg/gold_banner.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg b/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg new file mode 100644 index 00000000000..606201acf61 --- /dev/null +++ b/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg @@ -0,0 +1,39 @@ + + + + + + + + + + diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java new file mode 100644 index 00000000000..18a664e7494 --- /dev/null +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java @@ -0,0 +1,35 @@ +package com.firstvoices.android; + +import android.content.Context; + +import com.keyman.engine.KMManager; +import com.keyman.engine.util.FileUtils; + +import java.io.File; + +public class BannerController { + + // Paths relative to assets folder for banner themes + public static final String BANNER_THEME_FV = "svg/banner.html"; + public static final String BANNER_THEME_FV_SVG = "svg/teal-logo.svg"; + + + public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { + if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { + return; + } + + // Always use FirstVoices banner theme + String contents = FileUtils.readContents(context, BANNER_THEME_FV); + + // If $BANNER string exists, replace with actual path + File bannerPath = new File(KMManager.getResourceRoot(), BANNER_THEME_FV_SVG); + if (bannerPath.exists()) { + contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); + } + + KMManager.setHTMLBanner(keyboardType, contents); + KMManager.setBanner(keyboardType, KMManager.BannerType.HTML); + KMManager.showBanner(true); + } +} diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java index d921bb14dae..8d8869a2260 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java @@ -20,6 +20,7 @@ import android.view.inputmethod.InputConnection; import android.widget.FrameLayout; +import com.firstvoices.android.BannerController; import com.keyman.engine.KMManager; import com.keyman.engine.KMManager.KeyboardType; import com.keyman.engine.KMHardwareKeyboardInterpreter; @@ -33,9 +34,6 @@ import io.sentry.Sentry; public class SystemKeyboard extends InputMethodService implements OnKeyboardEventListener { - public static final String FV_BANNER_THEME_GOLD = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADsAAAApCAYAAACLO1EjAAAABHNCSVQICAgIfAhkiAAAABl0RVh0U29mdHdhcmUAZ25vbWUtc2NyZWVuc2hvdO8Dvz4AAAAtdEVYdENyZWF0aW9uIFRpbWUAVGh1IDI2IE9jdCAyMDIzIDA4OjE0OjE5IEFNICswN115VJ8AAABRSURBVGiB7c8BEYAgAMBAIIspKWRMbaF3zz7BNu99PeMQ6++ALzWralbVrKpZVbOqZlXNqppVNatqVtWsqllVs6pmVc2qmlU1q2pW1azqqNkXOKcClnCmxkcAAAAASUVORK5CYII="; - private View inputView = null; private static ExtractedText exText = null; private KMHardwareKeyboardInterpreter interpreter = null; @@ -67,8 +65,8 @@ public void onCreate() { interpreter = new KMHardwareKeyboardInterpreter(getApplicationContext(), KeyboardType.KEYBOARD_TYPE_SYSTEM); KMManager.setInputMethodService(this); // for HW interface - KMManager.setBannerImage(KeyboardType.KEYBOARD_TYPE_SYSTEM, FV_BANNER_THEME_GOLD); - KMManager.setBanner(KeyboardType.KEYBOARD_TYPE_SYSTEM, KMManager.BannerType.IMAGE); + // Set the system keyboard HTML banner + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); } @Override From d6a14a8cab61278722e40563af36d10ac33c74a1 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Wed, 15 Nov 2023 15:24:40 +0700 Subject: [PATCH 33/57] fix(oem/fv/android): Tweak FV banner theme --- .../app/src/main/assets/svg/banner.html | 8 ++-- .../app/src/main/assets/svg/red-logo.svg | 38 +++++++++++++++++++ .../firstvoices/android/BannerController.java | 3 +- 3 files changed, 43 insertions(+), 6 deletions(-) create mode 100644 oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg diff --git a/oem/firstvoices/android/app/src/main/assets/svg/banner.html b/oem/firstvoices/android/app/src/main/assets/svg/banner.html index 704b4b58515..a320a45cbcc 100644 --- a/oem/firstvoices/android/app/src/main/assets/svg/banner.html +++ b/oem/firstvoices/android/app/src/main/assets/svg/banner.html @@ -1,9 +1,9 @@ -
+
- + - -
+ +
diff --git a/oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg b/oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg new file mode 100644 index 00000000000..cf89f67db51 --- /dev/null +++ b/oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg @@ -0,0 +1,38 @@ + + + + + + + + + + diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java index 18a664e7494..a8703e7e7d9 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java @@ -11,8 +11,7 @@ public class BannerController { // Paths relative to assets folder for banner themes public static final String BANNER_THEME_FV = "svg/banner.html"; - public static final String BANNER_THEME_FV_SVG = "svg/teal-logo.svg"; - + public static final String BANNER_THEME_FV_SVG = "svg/red-logo.svg"; public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { From d5f857c701cc3df3f286c7be4e2c3b3b4a5d8799 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Tue, 7 Nov 2023 16:13:04 +0700 Subject: [PATCH 34/57] remove marmelroy Zip and add weichsel ZIPFoundation --- ios/Cartfile | 4 ++-- ios/Cartfile.resolved | 7 +++---- .../KeymanEngine.xcodeproj/project.pbxproj | 8 +++---- .../KeymanEngine/Classes/KeymanPackage.swift | 21 ++++++++++--------- .../Keyman/Keyman.xcodeproj/project.pbxproj | 12 +++++------ oem/firstvoices/ios/Cartfile | 3 ++- oem/firstvoices/ios/Cartfile.resolved | 6 +++--- 7 files changed, 31 insertions(+), 30 deletions(-) diff --git a/ios/Cartfile b/ios/Cartfile index b265ef77af2..8dec45e2010 100644 --- a/ios/Cartfile +++ b/ios/Cartfile @@ -1,5 +1,5 @@ -github "marmelroy/Zip" -github "keymanapp/dependency-XCGLogger" "master" +github "weichsel/ZIPFoundation" ~> 0.9 +github "DaveWoodCom/XCGLogger" ~> 6.1.0 github "devicekit/DeviceKit" ~> 5.0 github "ashleymills/Reachability.swift" github "getsentry/sentry-cocoa" ~> 8.7.0 diff --git a/ios/Cartfile.resolved b/ios/Cartfile.resolved index ff30a8871dd..1366ecd9ff6 100644 --- a/ios/Cartfile.resolved +++ b/ios/Cartfile.resolved @@ -1,5 +1,4 @@ github "ashleymills/Reachability.swift" "v5.1.0" -github "devicekit/DeviceKit" "5.0.0" -github "getsentry/sentry-cocoa" "8.7.0" -github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113" -github "marmelroy/Zip" "2.1.2" +github "devicekit/DeviceKit" "5.1.0" +github "getsentry/sentry-cocoa" "6.2.1" +github "weichsel/ZIPFoundation" "0.9.17" diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index f0205767d54..374f08bdcc2 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -25,6 +25,7 @@ 1645D5952036C6FF0076C51B /* KeymanPackage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5942036C6FF0076C51B /* KeymanPackage.swift */; }; 1645D5972036C9F80076C51B /* KMPKeyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1645D5962036C9F80076C51B /* KMPKeyboard.swift */; }; 165EB3A12098993900040A69 /* KeyboardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3A02098993900040A69 /* KeyboardError.swift */; }; + 296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */; }; 377D10DE26846B8900467431 /* SpacebarTextViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 377D10DD26846B8900467431 /* SpacebarTextViewController.swift */; }; 6CD5DFAA150F6DC8007A5DDE /* icon.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA8150F6DC8007A5DDE /* icon.png */; }; 6CD5DFAB150F6DC8007A5DDE /* icon@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 6CD5DFA9150F6DC8007A5DDE /* icon@2x.png */; }; @@ -121,7 +122,6 @@ CE5C8BE324B5B3BA00FAFB7F /* Queries+LexicalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5C8BE224B5B3BA00FAFB7F /* Queries+LexicalModel.swift */; }; CE5C8BE524B5BD1C00FAFB7F /* QueryModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE5C8BE424B5BD1C00FAFB7F /* QueryModelTests.swift */; }; CE5EDDC526522EAA001733AC /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */; }; - CE5EDDC626522EAA001733AC /* Zip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC126522EA9001733AC /* Zip.xcframework */; }; CE5EDDC726522EAA001733AC /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC226522EAA001733AC /* Sentry.xcframework */; }; CE5EDDC826522EAA001733AC /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC326522EAA001733AC /* Reachability.xcframework */; }; CE5EDDC926522EAA001733AC /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */; }; @@ -308,6 +308,7 @@ 2949146F2738DA6700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/ResourceInfoView.strings"; sourceTree = ""; }; 294914702738DD7400400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/Localizable.strings"; sourceTree = ""; }; 294914712738DD9700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "ff-NG"; path = "ff-NG.lproj/Localizable.stringsdict"; sourceTree = ""; }; + 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = ../../Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 297810FE297FAEDF007C886D /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kn; path = kn.lproj/ResourceInfoView.strings; sourceTree = ""; }; 297810FF297FAEF8007C886D /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kn; path = kn.lproj/Localizable.strings; sourceTree = ""; }; 29781100297FAF06007C886D /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = kn; path = kn.lproj/Localizable.stringsdict; sourceTree = ""; }; @@ -436,7 +437,6 @@ CE5C8BE424B5BD1C00FAFB7F /* QueryModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QueryModelTests.swift; sourceTree = ""; }; CE5EDDBB26522EA3001733AC /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = ""; }; CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = ""; }; - CE5EDDC126522EA9001733AC /* Zip.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Zip.xcframework; path = ../../Carthage/Build/Zip.xcframework; sourceTree = ""; }; CE5EDDC226522EAA001733AC /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = ""; }; CE5EDDC326522EAA001733AC /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = ""; }; CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = ""; }; @@ -560,8 +560,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 296EF2C72AFA26C700E3E384 /* ZIPFoundation.xcframework in Frameworks */, CE5EDDC526522EAA001733AC /* XCGLogger.xcframework in Frameworks */, - CE5EDDC626522EAA001733AC /* Zip.xcframework in Frameworks */, CE5EDDD52652372A001733AC /* DeviceKit.xcframework in Frameworks */, CE5EDDC726522EAA001733AC /* Sentry.xcframework in Frameworks */, CE5EDDC826522EAA001733AC /* Reachability.xcframework in Frameworks */, @@ -949,11 +949,11 @@ F243888114BBD43000A3E055 /* Frameworks */ = { isa = PBXGroup; children = ( + 296EF2C62AFA26C700E3E384 /* ZIPFoundation.xcframework */, CE5EDDC426522EAA001733AC /* ObjcExceptionBridging.xcframework */, CE5EDDC326522EAA001733AC /* Reachability.xcframework */, CE5EDDC226522EAA001733AC /* Sentry.xcframework */, CE5EDDC026522EA9001733AC /* XCGLogger.xcframework */, - CE5EDDC126522EA9001733AC /* Zip.xcframework */, CE5EDDBB26522EA3001733AC /* DeviceKit.xcframework */, 9A079DE52231A69D00581263 /* Foundation.framework */, ); diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift index 28d89b7ee17..348417e5063 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift @@ -7,7 +7,7 @@ // import Foundation -import Zip +import ZIPFoundation // KMPErrors may be passed to UIAlertControllers, so they need localization. public enum KMPError : String, Error { @@ -443,14 +443,14 @@ public class KeymanPackage { @available(*, deprecated, message: "Use of the completion block is unnecessary; this method now returns synchronously.") static public func extract(fileUrl: URL, destination: URL, complete: @escaping (KeymanPackage?) -> Void) throws { - try unzipFile(fileUrl: fileUrl, destination: destination) { - do { - let package = try KeymanPackage.parse(destination) - complete(package) - } catch { - SentryManager.captureAndLog(error, sentryLevel: .info) - complete(nil) - } + let fileManager = FileManager() + do { + try fileManager.unzipItem(at: fileUrl, to: destination) + let package = try KeymanPackage.parse(destination) + complete(package) + } catch { + SentryManager.captureAndLog(error, sentryLevel: .info) + complete(nil) } } @@ -460,7 +460,8 @@ public class KeymanPackage { } static public func unzipFile(fileUrl: URL, destination: URL, complete: @escaping () -> Void = {}) throws { - try Zip.unzipFile(fileUrl, destination: destination, overwrite: true, password: nil) + let fileManager = FileManager() + try fileManager.unzipItem(at: fileUrl, to: destination) complete() } diff --git a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj index 5f2fad4ced7..ddaa7a831cd 100644 --- a/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj +++ b/ios/keyman/Keyman/Keyman.xcodeproj/project.pbxproj @@ -14,6 +14,8 @@ 162E2C9F2092D36800F40769 /* UIDevice+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 162E2C9E2092D36800F40769 /* UIDevice+Extensions.swift */; }; 165EB39A2097165B00040A69 /* UIColor+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3992097165B00040A69 /* UIColor+Extensions.swift */; }; 165EB39C2097181D00040A69 /* KMView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB39B2097181D00040A69 /* KMView.swift */; }; + 296EF2C42AFA267500E3E384 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */; }; + 296EF2C52AFA267500E3E384 /* ZIPFoundation.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 981AFA8F19EF44DE006706BF /* 724-info@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 9815725518E4F0930014DF0C /* 724-info@2x.png */; }; 981AFA9619EF44DE006706BF /* MainViewController_iPhone.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98ABADCE176935E500B62590 /* MainViewController_iPhone.xib */; }; 981AFA9819EF44DE006706BF /* 724-info.png in Resources */ = {isa = PBXBuildFile; fileRef = 9815725418E4F0930014DF0C /* 724-info.png */; }; @@ -155,8 +157,6 @@ CEBD34422654FEB400EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBD34432654FEB400EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */; }; CEBD34442654FEB400EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CEBD34452654FEB400EB2EA8 /* Zip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD343A2654FEB400EB2EA8 /* Zip.xcframework */; }; - CEBD34462654FEB400EB2EA8 /* Zip.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD343A2654FEB400EB2EA8 /* Zip.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEF4E55623E95B7B0065B9C7 /* ImageBanner.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEF4E55523E95B7B0065B9C7 /* ImageBanner.xib */; }; CEF4E55723E95B7B0065B9C7 /* ImageBanner.xib in Resources */ = {isa = PBXBuildFile; fileRef = CEF4E55523E95B7B0065B9C7 /* ImageBanner.xib */; }; CEF4E55923E967140065B9C7 /* ImageBannerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CEF4E55823E967140065B9C7 /* ImageBannerViewController.swift */; }; @@ -183,11 +183,11 @@ files = ( CEBD34402654FEB400EB2EA8 /* Reachability.xcframework in Embed Frameworks */, CEBD343E2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */, + 296EF2C52AFA267500E3E384 /* ZIPFoundation.xcframework in Embed Frameworks */, CEBD343C2654FEB400EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */, CEBD34422654FEB400EB2EA8 /* Sentry.xcframework in Embed Frameworks */, CEBD34442654FEB400EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */, CE80AD34257F2B4B008D2150 /* KeymanEngine.framework in Embed Frameworks */, - CEBD34462654FEB400EB2EA8 /* Zip.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -205,6 +205,7 @@ 16A9229D20325253003CC98E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = Keyman/Info.plist; sourceTree = SOURCE_ROOT; }; 293EA3E027059C6900545EED /* ha */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ha; path = ha.lproj/Localizable.strings; sourceTree = ""; }; 294914742738DF7700400732 /* ff-NG */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "ff-NG"; path = "ff-NG.lproj/Localizable.strings"; sourceTree = ""; }; + 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = ../../Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 297810FD297FA818007C886D /* kn */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = kn; path = kn.lproj/Localizable.strings; sourceTree = ""; }; 298566B829802828004ACA95 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Localizable.strings; sourceTree = ""; }; 298566C42980C493004ACA95 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Localizable.strings; sourceTree = ""; }; @@ -344,7 +345,6 @@ CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = ""; }; CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = ""; }; CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = ""; }; - CEBD343A2654FEB400EB2EA8 /* Zip.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Zip.xcframework; path = ../../Carthage/Build/Zip.xcframework; sourceTree = ""; }; CEBD3458265511B700EB2EA8 /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/Localizable.strings; sourceTree = ""; }; CEEC468226F2F7BA009A5B7D /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = ""; }; CEEF81B92673019600EE6A07 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "es-419"; path = "es-419.lproj/Localizable.strings"; sourceTree = ""; }; @@ -359,11 +359,11 @@ files = ( CEBD343F2654FEB400EB2EA8 /* Reachability.xcframework in Frameworks */, CEBD343D2654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */, + 296EF2C42AFA267500E3E384 /* ZIPFoundation.xcframework in Frameworks */, CEBD343B2654FEB400EB2EA8 /* DeviceKit.xcframework in Frameworks */, CEBD34412654FEB400EB2EA8 /* Sentry.xcframework in Frameworks */, CEBD34432654FEB400EB2EA8 /* XCGLogger.xcframework in Frameworks */, CE80AD33257F2B4A008D2150 /* KeymanEngine.framework in Frameworks */, - CEBD34452654FEB400EB2EA8 /* Zip.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -544,12 +544,12 @@ 98ABADB2176935E400B62590 /* Frameworks */ = { isa = PBXGroup; children = ( + 296EF2C32AFA267500E3E384 /* ZIPFoundation.xcframework */, CEBD34352654FEB400EB2EA8 /* DeviceKit.xcframework */, CEBD34362654FEB400EB2EA8 /* ObjcExceptionBridging.xcframework */, CEBD34372654FEB400EB2EA8 /* Reachability.xcframework */, CEBD34382654FEB400EB2EA8 /* Sentry.xcframework */, CEBD34392654FEB400EB2EA8 /* XCGLogger.xcframework */, - CEBD343A2654FEB400EB2EA8 /* Zip.xcframework */, CE80AD32257F2B4A008D2150 /* KeymanEngine.framework */, ); name = Frameworks; diff --git a/oem/firstvoices/ios/Cartfile b/oem/firstvoices/ios/Cartfile index 10efbc3d5a0..8dec45e2010 100644 --- a/oem/firstvoices/ios/Cartfile +++ b/oem/firstvoices/ios/Cartfile @@ -1,4 +1,5 @@ -github "keymanapp/dependency-XCGLogger" "master" +github "weichsel/ZIPFoundation" ~> 0.9 +github "DaveWoodCom/XCGLogger" ~> 6.1.0 github "devicekit/DeviceKit" ~> 5.0 github "ashleymills/Reachability.swift" github "getsentry/sentry-cocoa" ~> 8.7.0 diff --git a/oem/firstvoices/ios/Cartfile.resolved b/oem/firstvoices/ios/Cartfile.resolved index 5ac3a553189..1366ecd9ff6 100644 --- a/oem/firstvoices/ios/Cartfile.resolved +++ b/oem/firstvoices/ios/Cartfile.resolved @@ -1,4 +1,4 @@ github "ashleymills/Reachability.swift" "v5.1.0" -github "devicekit/DeviceKit" "5.0.0" -github "getsentry/sentry-cocoa" "8.7.0" -github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113" +github "devicekit/DeviceKit" "5.1.0" +github "getsentry/sentry-cocoa" "6.2.1" +github "weichsel/ZIPFoundation" "0.9.17" From 5a9f91d7966ee0c77cf4eb9c0c7526bbdaaf0dc1 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 8 Nov 2023 09:29:16 +0700 Subject: [PATCH 35/57] add ZIPFoundation to sample projects --- .../KMSample1.xcodeproj/project.pbxproj | 12 ++++++------ .../KMSample2.xcodeproj/project.pbxproj | 16 ++++++++-------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/ios/samples/KMSample1/KMSample1.xcodeproj/project.pbxproj b/ios/samples/KMSample1/KMSample1.xcodeproj/project.pbxproj index 202426dc861..86e6bc7d2db 100644 --- a/ios/samples/KMSample1/KMSample1.xcodeproj/project.pbxproj +++ b/ios/samples/KMSample1/KMSample1.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 290FB45B2AFB234900249D58 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB45A2AFB234800249D58 /* ZIPFoundation.xcframework */; }; + 290FB45C2AFB234900249D58 /* ZIPFoundation.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB45A2AFB234800249D58 /* ZIPFoundation.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 98ABFCEC1AD39165005534F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFCEA1AD39165005534F0 /* Main.storyboard */; }; 98ABFCEE1AD39165005534F0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFCED1AD39165005534F0 /* Images.xcassets */; }; 98ABFCF11AD39165005534F0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFCEF1AD39165005534F0 /* LaunchScreen.xib */; }; @@ -24,8 +26,6 @@ CEBD33CD2654CB1500EB2EA8 /* Sentry.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBD33CE2654CB1600EB2EA8 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */; }; CEBD33CF2654CB1700EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CEBD33D02654CB1900EB2EA8 /* Zip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BB2654CA5900EB2EA8 /* Zip.xcframework */; }; - CEBD33D12654CB1900EB2EA8 /* Zip.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33BB2654CA5900EB2EA8 /* Zip.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CED2AC85260AFADC00A408D2 /* ekwtamil99uni.kmp in Resources */ = {isa = PBXBuildFile; fileRef = CED2AC84260AFADC00A408D2 /* ekwtamil99uni.kmp */; }; /* End PBXBuildFile section */ @@ -38,11 +38,11 @@ files = ( CEBD33CB2654CB1300EB2EA8 /* Reachability.xcframework in Embed Frameworks */, CEBD33C92654CB1100EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */, + 290FB45C2AFB234900249D58 /* ZIPFoundation.xcframework in Embed Frameworks */, CEBD33C72654CB0F00EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */, CEBD33CD2654CB1500EB2EA8 /* Sentry.xcframework in Embed Frameworks */, CEBD33CF2654CB1700EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */, CEBD337E26549EC100EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */, - CEBD33D12654CB1900EB2EA8 /* Zip.xcframework in Embed Frameworks */, ); name = "Embed Frameworks"; runOnlyForDeploymentPostprocessing = 0; @@ -50,6 +50,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 290FB45A2AFB234800249D58 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = ../../Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 98ABFCDD1AD39165005534F0 /* KMSample1.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KMSample1.app; sourceTree = BUILT_PRODUCTS_DIR; }; 98ABFCE11AD39165005534F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98ABFCEB1AD39165005534F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -59,7 +60,6 @@ C0324B831F87172900AF3785 /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; CEBD337C26549EC100EB2EA8 /* KeymanEngine.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = KeymanEngine.xcframework; sourceTree = ""; }; CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = ""; }; - CEBD33BB2654CA5900EB2EA8 /* Zip.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Zip.xcframework; path = ../../Carthage/Build/Zip.xcframework; sourceTree = ""; }; CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = ""; }; CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = XCGLogger.xcframework; path = ../../Carthage/Build/XCGLogger.xcframework; sourceTree = ""; }; CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = ""; }; @@ -74,11 +74,11 @@ files = ( CEBD33C82654CB1000EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */, CEBD33CA2654CB1300EB2EA8 /* Reachability.xcframework in Frameworks */, + 290FB45B2AFB234900249D58 /* ZIPFoundation.xcframework in Frameworks */, CEBD33CC2654CB1400EB2EA8 /* Sentry.xcframework in Frameworks */, CEBD33C62654CB0E00EB2EA8 /* DeviceKit.xcframework in Frameworks */, CEBD337D26549EC100EB2EA8 /* KeymanEngine.xcframework in Frameworks */, CEBD33CE2654CB1600EB2EA8 /* XCGLogger.xcframework in Frameworks */, - CEBD33D02654CB1900EB2EA8 /* Zip.xcframework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -127,11 +127,11 @@ 98ABFD171AD391B0005534F0 /* Frameworks */ = { isa = PBXGroup; children = ( + 290FB45A2AFB234800249D58 /* ZIPFoundation.xcframework */, CEBD33BC2654CA5900EB2EA8 /* ObjcExceptionBridging.xcframework */, CEBD33BF2654CA5900EB2EA8 /* Reachability.xcframework */, CEBD33BE2654CA5900EB2EA8 /* Sentry.xcframework */, CEBD33BD2654CA5900EB2EA8 /* XCGLogger.xcframework */, - CEBD33BB2654CA5900EB2EA8 /* Zip.xcframework */, CEBD33B92654CA4B00EB2EA8 /* DeviceKit.xcframework */, CEBD337C26549EC100EB2EA8 /* KeymanEngine.xcframework */, ); diff --git a/ios/samples/KMSample2/KMSample2.xcodeproj/project.pbxproj b/ios/samples/KMSample2/KMSample2.xcodeproj/project.pbxproj index 782e5b834ab..9b1620b2ad0 100644 --- a/ios/samples/KMSample2/KMSample2.xcodeproj/project.pbxproj +++ b/ios/samples/KMSample2/KMSample2.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 290FB45E2AFB270D00249D58 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */; }; + 290FB45F2AFB270D00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 290FB4602AFB271C00249D58 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */; }; 98ABFD461AD3A047005534F0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFD441AD3A047005534F0 /* Main.storyboard */; }; 98ABFD481AD3A047005534F0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFD471AD3A047005534F0 /* Images.xcassets */; }; 98ABFD4B1AD3A047005534F0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 98ABFD491AD3A047005534F0 /* LaunchScreen.xib */; }; @@ -18,8 +21,6 @@ CEBD33D42654CB6A00EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBD33D82654CB7800EB2EA8 /* DeviceKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */; }; CEBD33D92654CB7800EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; - CEBD33E02654CB8700EB2EA8 /* Zip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DB2654CB8700EB2EA8 /* Zip.xcframework */; }; - CEBD33E12654CB8700EB2EA8 /* Zip.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DB2654CB8700EB2EA8 /* Zip.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBD33E22654CB8700EB2EA8 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; }; CEBD33E32654CB8700EB2EA8 /* Reachability.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; CEBD33E42654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */; }; @@ -38,7 +39,6 @@ CEDB327B265C9B33000A2009 /* Reachability.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */; }; CEDB327C265C9B33000A2009 /* Sentry.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */; }; CEDB327D265C9B33000A2009 /* XCGLogger.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */; }; - CEDB327E265C9B33000A2009 /* Zip.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = CEBD33DB2654CB8700EB2EA8 /* Zip.xcframework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -71,9 +71,9 @@ files = ( CEBD33E32654CB8700EB2EA8 /* Reachability.xcframework in Embed Frameworks */, CEBD33E52654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */, + 290FB45F2AFB270D00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */, CEBD33E72654CB8800EB2EA8 /* Sentry.xcframework in Embed Frameworks */, CEBD33D92654CB7800EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */, - CEBD33E12654CB8700EB2EA8 /* Zip.xcframework in Embed Frameworks */, CEBD33D42654CB6A00EB2EA8 /* KeymanEngine.xcframework in Embed Frameworks */, CEBD33E92654CB8800EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */, ); @@ -94,6 +94,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = ../../Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 98ABFD371AD3A047005534F0 /* KMSample2.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = KMSample2.app; sourceTree = BUILT_PRODUCTS_DIR; }; 98ABFD3B1AD3A047005534F0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98ABFD451AD3A047005534F0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -108,7 +109,6 @@ C045148C1F85E33300D88416 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = ""; }; CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = KeymanEngine.xcframework; sourceTree = ""; }; CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = DeviceKit.xcframework; path = ../../Carthage/Build/DeviceKit.xcframework; sourceTree = ""; }; - CEBD33DB2654CB8700EB2EA8 /* Zip.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Zip.xcframework; path = ../../Carthage/Build/Zip.xcframework; sourceTree = ""; }; CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Reachability.xcframework; path = ../../Carthage/Build/Reachability.xcframework; sourceTree = ""; }; CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ObjcExceptionBridging.xcframework; path = ../../Carthage/Build/ObjcExceptionBridging.xcframework; sourceTree = ""; }; CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = Sentry.xcframework; path = ../../Carthage/Build/Sentry.xcframework; sourceTree = ""; }; @@ -125,9 +125,9 @@ files = ( CEBD33E22654CB8700EB2EA8 /* Reachability.xcframework in Frameworks */, CEBD33E42654CB8800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */, + 290FB45E2AFB270D00249D58 /* ZIPFoundation.xcframework in Frameworks */, CEBD33E62654CB8800EB2EA8 /* Sentry.xcframework in Frameworks */, CEBD33D82654CB7800EB2EA8 /* DeviceKit.xcframework in Frameworks */, - CEBD33E02654CB8700EB2EA8 /* Zip.xcframework in Frameworks */, CEBD33E82654CB8800EB2EA8 /* XCGLogger.xcframework in Frameworks */, CEBD33D32654CB6A00EB2EA8 /* KeymanEngine.xcframework in Frameworks */, ); @@ -139,9 +139,9 @@ files = ( CEDB327A265C9B33000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */, CEDB327B265C9B33000A2009 /* Reachability.xcframework in Frameworks */, + 290FB4602AFB271C00249D58 /* ZIPFoundation.xcframework in Frameworks */, CEDB327C265C9B33000A2009 /* Sentry.xcframework in Frameworks */, CEDB327D265C9B33000A2009 /* XCGLogger.xcframework in Frameworks */, - CEDB327E265C9B33000A2009 /* Zip.xcframework in Frameworks */, CEDB3279265C9B15000A2009 /* DeviceKit.xcframework in Frameworks */, CEDB3276265C953A000A2009 /* KeymanEngine.xcframework in Frameworks */, ); @@ -195,11 +195,11 @@ 98ABFD601AD3A087005534F0 /* Frameworks */ = { isa = PBXGroup; children = ( + 290FB45D2AFB270D00249D58 /* ZIPFoundation.xcframework */, CEBD33DD2654CB8700EB2EA8 /* ObjcExceptionBridging.xcframework */, CEBD33DC2654CB8700EB2EA8 /* Reachability.xcframework */, CEBD33DE2654CB8700EB2EA8 /* Sentry.xcframework */, CEBD33DF2654CB8700EB2EA8 /* XCGLogger.xcframework */, - CEBD33DB2654CB8700EB2EA8 /* Zip.xcframework */, CEBD33D72654CB7800EB2EA8 /* DeviceKit.xcframework */, CEBD33D22654CB6A00EB2EA8 /* KeymanEngine.xcframework */, ); From b1c93534daa1f694e58b615e26dd5ef19978c160 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Wed, 8 Nov 2023 10:25:39 +0700 Subject: [PATCH 36/57] add ZIPFoundation to FirstVoices project --- oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj index 83e34c42a61..dcb81b684f2 100644 --- a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj +++ b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 290FB4632AFB358F00249D58 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */; }; + 290FB4642AFB358F00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 290FB4652AFB359A00249D58 /* ZIPFoundation.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */; }; 294D40E9279FE62600DB37F6 /* KeyboardRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294D40E8279FE62600DB37F6 /* KeyboardRepository.swift */; }; 294D40EC27A110BC00DB37F6 /* KeyboardSettingsRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 294D40EB27A110BC00DB37F6 /* KeyboardSettingsRepository.swift */; }; 29694DC227A27EC300EA6C18 /* LexicalModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29694DC127A27EC300EA6C18 /* LexicalModelRepository.swift */; }; @@ -74,6 +77,7 @@ files = ( CEBD34202654D76900EB2EA8 /* Reachability.xcframework in Embed Frameworks */, CEBD341E2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Embed Frameworks */, + 290FB4642AFB358F00249D58 /* ZIPFoundation.xcframework in Embed Frameworks */, CEBD341B2654D76600EB2EA8 /* DeviceKit.xcframework in Embed Frameworks */, CEBD34232654D76B00EB2EA8 /* Sentry.xcframework in Embed Frameworks */, CEBD34262654D76C00EB2EA8 /* XCGLogger.xcframework in Embed Frameworks */, @@ -96,6 +100,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; name = ZIPFoundation.xcframework; path = Carthage/Build/ZIPFoundation.xcframework; sourceTree = ""; }; 294D40E8279FE62600DB37F6 /* KeyboardRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardRepository.swift; sourceTree = ""; }; 294D40EB27A110BC00DB37F6 /* KeyboardSettingsRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardSettingsRepository.swift; sourceTree = ""; }; 29694DC127A27EC300EA6C18 /* LexicalModelRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelRepository.swift; sourceTree = ""; }; @@ -153,6 +158,7 @@ files = ( CEBD341D2654D76800EB2EA8 /* ObjcExceptionBridging.xcframework in Frameworks */, CEBD341F2654D76900EB2EA8 /* Reachability.xcframework in Frameworks */, + 290FB4632AFB358F00249D58 /* ZIPFoundation.xcframework in Frameworks */, CEBD34222654D76B00EB2EA8 /* Sentry.xcframework in Frameworks */, CEBD341A2654D76600EB2EA8 /* DeviceKit.xcframework in Frameworks */, CEBD34022654D41200EB2EA8 /* KeymanEngine.xcframework in Frameworks */, @@ -166,6 +172,7 @@ files = ( CEDB327F265C9C58000A2009 /* DeviceKit.xcframework in Frameworks */, CEDB3280265C9C58000A2009 /* ObjcExceptionBridging.xcframework in Frameworks */, + 290FB4652AFB359A00249D58 /* ZIPFoundation.xcframework in Frameworks */, CEDB3281265C9C58000A2009 /* Reachability.xcframework in Frameworks */, CEDB3282265C9C58000A2009 /* Sentry.xcframework in Frameworks */, CEDB3283265C9C58000A2009 /* XCGLogger.xcframework in Frameworks */, @@ -284,6 +291,7 @@ 98C9A9FB1BFC19ED009E9A4F /* Frameworks */ = { isa = PBXGroup; children = ( + 290FB4622AFB358E00249D58 /* ZIPFoundation.xcframework */, CEBD34082654D5AA00EB2EA8 /* ObjcExceptionBridging.xcframework */, CEBD340C2654D5AB00EB2EA8 /* Reachability.xcframework */, CEBD34092654D5AA00EB2EA8 /* Sentry.xcframework */, From 1a8f401a1d6e18b2543d2adf6b5cdbaeaa97e9b9 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Mon, 13 Nov 2023 10:39:31 +0700 Subject: [PATCH 37/57] clear cache directory before extracting zip --- .../KeymanEngine/Classes/KeymanPackage.swift | 8 ++ .../ResourceFileManager.swift | 3 + .../KeymanPackageTests.swift | 80 +++++++++++++++++++ .../ResourceUpdateTests.swift | 2 + .../TestUtils/EngineStateBundler.swift | 24 ++++-- .../xcshareddata/xcschemes/Keyman.xcscheme | 3 +- 6 files changed, 112 insertions(+), 8 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift index 348417e5063..fc7f4ffb3d5 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift @@ -453,6 +453,14 @@ public class KeymanPackage { complete(nil) } } + + static public func clearDirectory(destination: URL) throws { + let fileArray = try FileManager.default.contentsOfDirectory(atPath: destination.path) + try fileArray.forEach { file in + let fileUrl = destination.appendingPathComponent(file) + try FileManager.default.removeItem(atPath: fileUrl.path) + } + } static public func extract(fileUrl: URL, destination: URL) throws -> KeymanPackage? { try unzipFile(fileUrl: fileUrl, destination: destination) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift index 11740ce42b1..d3831413cbc 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Resource Management/ResourceFileManager.swift @@ -183,6 +183,9 @@ public class ResourceFileManager { var extractionFolder = cacheDirectory extractionFolder.appendPathComponent("temp/\(archiveUrl.lastPathComponent)") + // first clear extraction folder to avoid creating duplicates + try KeymanPackage.clearDirectory(destination: extractionFolder) + do { if let package = try KeymanPackage.extract(fileUrl: archiveUrl, destination: extractionFolder) { return package diff --git a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift index ee91cf96b98..67f89fde10b 100644 --- a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift @@ -58,6 +58,86 @@ class KeymanPackageTests: XCTestCase { } } + func testKeyboardPackage_clearEmptyDirectory_throwsNoError() throws { + let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let destinationDirectory = cacheDirectory.appendingPathComponent("destination") + + do { + // create directory + try FileManager.default.createDirectory( + atPath: destinationDirectory.path, + withIntermediateDirectories: false, + attributes: nil + ) + + // clear directory + try KeymanPackage.clearDirectory(destination: destinationDirectory) + } catch { + XCTFail("error clearing the empty directory \(error)") + } + + let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path) + XCTAssert(fileArray.count == 0, "directory still contains /(fileArray.count) items") + } + + func testKeyboardPackage_clearNonEmptyDirectory_directoryIsEmpty() throws { + let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let destinationDirectory = cacheDirectory.appendingPathComponent("destination") + + do { + // create directory + try FileManager.default.createDirectory( + atPath: destinationDirectory.path, + withIntermediateDirectories: false, + attributes: nil + ) + + // add some files + FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("fileone").path, contents: nil) + FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("filetwo").path, contents: nil) + FileManager.default.createFile(atPath: destinationDirectory.appendingPathComponent("filethree").path, contents: nil) + + // clear directory + try KeymanPackage.clearDirectory(destination: destinationDirectory) + } catch { + XCTFail("error clearing the empty directory \(error)") + } + + let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path) + XCTAssert(fileArray.count == 0, "directory still contains /(fileArray.count) items") + } + + func testKeyboardPackage_extractTwice_noDuplicateFileError() throws { + let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp.zip") + try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip) + + let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor") + + // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. + do { + if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { + log.info("*** first unzip of \(kmp.id)") + do { + // clear directory before second extract + try KeymanPackage.clearDirectory(destination: destinationFolderURL) + + if let secondKmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { + log.info("*** second unzip of \(secondKmp.id)") + } else { + XCTAssert(false, "*** second unzip failed") + } + } catch { + XCTFail("unzip 2 failure with error \(error)") + } + } else { + XCTAssert(false, "*** first unzip failed") + } + } catch { + XCTFail("unzip 1 failure with error \(error)") + } + } + func testLexicalModelPackageExtraction() throws { let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] let mtntZip = cacheDirectory.appendingPathComponent("nrc.en.mtnt.kmp.zip") diff --git a/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift b/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift index 3945f9212ee..7a6d4b45a1a 100644 --- a/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift @@ -57,10 +57,12 @@ class ResourceUpdateTests: XCTestCase { wait(for: [queryExpectation], timeout: 5) + /* // Uses the XCTest 'attachment' system to retrieve the desired file. self.add(try TestUtils.EngineStateBundler.createBundle(withName: "khmer_angkor update base")) log.info("Bundle archived and attached to test's report.") + */ } func testCacheCurrent() { diff --git a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift index 1f3bd4e27ca..e15f266fc49 100644 --- a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift +++ b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift @@ -8,7 +8,8 @@ import Foundation import XCTest -import Zip +import ZIPFoundation + @testable import KeymanEngine extension TestUtils { @@ -28,6 +29,7 @@ extension TestUtils { return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].appendingPathComponent("Preferences") } + /* static func createBundle(withName name: String) throws -> XCTAttachment { let tempDirectory = FileManager.default.temporaryDirectory let bundleConstructionURL = tempDirectory.appendingPathComponent("\(name).bundle") @@ -64,12 +66,20 @@ extension TestUtils { try FileManager.default.moveItem(at: pListPath.appendingPathComponent(testEngineFilename), to: pListPath.appendingPathComponent(appGroupFilename)) } - let attachmentFile = try Zip.quickZipFiles([bundleConstructionURL], fileName: "bundleArchive") - log.info("Archive source: \(bundleConstructionURL)") - let attachment = XCTAttachment(contentsOfFile: attachmentFile) - attachment.lifetime = .keepAlways - - return attachment + let archiveURL = bundleConstructionURL.appendingPathComponent("bundleArchive.zip") + do { + let attachmentFile = try Archive(url: archiveURL, accessMode: .create) + log.info("archiveURL: \(archiveURL)") + let attachment = XCTAttachment(contentsOfFile: archiveURL) + attachment.lifetime = .keepAlways + return attachment + } + catch let error { + print (error.localizedDescription) + return XCTAttachment(string: error.localizedDescription) + } } + + */ } } diff --git a/ios/keymanios.xcworkspace/xcshareddata/xcschemes/Keyman.xcscheme b/ios/keymanios.xcworkspace/xcshareddata/xcschemes/Keyman.xcscheme index abc85ae1420..8f86bea5338 100644 --- a/ios/keymanios.xcworkspace/xcshareddata/xcschemes/Keyman.xcscheme +++ b/ios/keymanios.xcworkspace/xcshareddata/xcschemes/Keyman.xcscheme @@ -40,7 +40,8 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - shouldUseLaunchSchemeArgsEnv = "NO"> + shouldUseLaunchSchemeArgsEnv = "NO" + codeCoverageEnabled = "YES"> Date: Mon, 13 Nov 2023 13:25:37 +0700 Subject: [PATCH 38/57] clearDirectory does nothing when directory does not exist --- .../KMEI/KeymanEngine/Classes/KeymanPackage.swift | 15 +++++++++++---- .../KeymanEngineTests/KeymanPackageTests.swift | 12 ++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift index fc7f4ffb3d5..5c2aa97ecf2 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift @@ -455,10 +455,17 @@ public class KeymanPackage { } static public func clearDirectory(destination: URL) throws { - let fileArray = try FileManager.default.contentsOfDirectory(atPath: destination.path) - try fileArray.forEach { file in - let fileUrl = destination.appendingPathComponent(file) - try FileManager.default.removeItem(atPath: fileUrl.path) + // First check to see if directory exists. If not, then do nothing. + var isDirectory: ObjCBool = false + if(FileManager.default.fileExists(atPath: destination.path, isDirectory: &isDirectory)){ + if (isDirectory.boolValue) { + // it exists and is actually a directory, so remove every file it contains + let fileArray = try FileManager.default.contentsOfDirectory(atPath: destination.path) + try fileArray.forEach { file in + let fileUrl = destination.appendingPathComponent(file) + try FileManager.default.removeItem(atPath: fileUrl.path) + } + } } } diff --git a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift index 67f89fde10b..e291ea26bf8 100644 --- a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift @@ -58,6 +58,18 @@ class KeymanPackageTests: XCTestCase { } } + func testKeyboardPackage_clearNonexistentDirectory_doesNothing() throws { + let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] + let destinationDirectory = cacheDirectory.appendingPathComponent("doesnotexist") + + do { + // clear directory + try KeymanPackage.clearDirectory(destination: destinationDirectory) + } catch { + XCTFail("error clearing the nonexistent directory \(error)") + } + } + func testKeyboardPackage_clearEmptyDirectory_throwsNoError() throws { let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] let destinationDirectory = cacheDirectory.appendingPathComponent("destination") From 35a37bc033c305f19f8b2fa2a885a317b06780fa Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 03:21:04 +0700 Subject: [PATCH 39/57] chore(android): Additional cleanup * Remove old assets * Add API header * Cleanup comments --- .../KMEA/app/src/main/assets/android-host.js | 4 +- .../java/com/keyman/engine/KMKeyboard.java | 1 - .../java/com/keyman/engine/KMManager.java | 5 +++ .../com/keyman/kmsample1/MainActivity.java | 3 -- .../app/src/main/assets/svg/teal-logo.svg | 39 ------------------- 5 files changed, 7 insertions(+), 45 deletions(-) delete mode 100644 oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index d3a7293b11e..7ebeb6679d2 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -59,8 +59,8 @@ function init() { } function showBanner(flag) { - window.console.log("Setting banner display for dictionaryless keyboards to " + flag); - window.console.log("bannerHTMLContents: " + bannerHTMLContents); + console_debug("Setting banner display for dictionaryless keyboards to " + flag); + console_debug("bannerHTMLContents: " + bannerHTMLContents); var bc = keyman.osk.bannerController; if (bc) { if (bannerHTMLContents != '') { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 1136cc3a4c6..f7504e8ad0b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -649,7 +649,6 @@ public String getHTMLBanner() { public void setHTMLBanner(String contents) { this.htmlBannerString = contents; - KMLog.LogInfo(TAG, KMString.format("HTML Banner string: (%s).", contents)); String jsString = KMString.format("setBannerHTML('%s')", contents); loadJavascript(jsString); } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index bebc6a6ad8d..6512d7b40f2 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -1468,6 +1468,11 @@ public static boolean setHTMLBanner(KeyboardType keyboard, String htmlContent) { return true; } + /** + * Get the HTML content associated with the HTML banner + * @param {KeyboardType} keyboard + * @return {String} + */ public static String getHTMLBanner(KeyboardType keyboard) { if (keyboard == KeyboardType.KEYBOARD_TYPE_INAPP && InAppKeyboard != null) { return InAppKeyboard.getHTMLBanner(); diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 70a08b18632..f56064b4594 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -24,9 +24,6 @@ public class MainActivity extends AppCompatActivity implements OnKeyboardEventLi public static Context context; private KMTextView textView; - // Paths relative to assets folder for banner themes. Change this for custom banner - private static String BANNER_THEME_KMSAMPLE1 = "svg/green_banner.svg"; - @Override protected void onCreate(Bundle savedInstanceState) { setTheme(R.style.AppTheme); diff --git a/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg b/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg deleted file mode 100644 index 606201acf61..00000000000 --- a/oem/firstvoices/android/app/src/main/assets/svg/teal-logo.svg +++ /dev/null @@ -1,39 +0,0 @@ - - - - - - - - - - From 76eab6e12a5f2473aae2f1442c6be356559f1731 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 05:52:43 +0700 Subject: [PATCH 40/57] fix(android/engine): Don't set banner on test mode --- .../java/com/keyman/engine/KMManager.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 6512d7b40f2..8c280687c0b 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -661,15 +661,16 @@ private static void initKeyboard(Context appContext, KeyboardType keyboardType) keyboard.addJavascriptInterface(new KMKeyboardJSHandler(appContext, keyboard), "jsInterface"); keyboard.loadKeyboard(); - // For apps that don't specify an HTML banner, specify a default phone/tablet HTML banner - if (getFormFactor() == FormFactor.PHONE) { - keyboard.setHTMLBanner(KMBLACK_BANNER); - } else { - keyboard.setHTMLBanner(KMGRAY_BANNER); + if (!isTestMode()) { + // For apps that don't specify an HTML banner, specify a default phone/tablet HTML banner + if (getFormFactor() == FormFactor.PHONE) { + keyboard.setHTMLBanner(KMBLACK_BANNER); + } else { + keyboard.setHTMLBanner(KMGRAY_BANNER); + } + keyboard.setBanner(KMManager.BannerType.HTML); + keyboard.showBanner(true); } - keyboard.setBanner(KMManager.BannerType.HTML); - keyboard.showBanner(true); - setEngineWebViewVersionStatus(appContext, keyboard); } @@ -1421,7 +1422,7 @@ public static void deleteLexicalModel(Context context, int position, boolean sil * @return boolean - Success */ public static boolean setBannerOptions(boolean mayPredict) { - String url = KMString.format("setBannerOptions(%b)", mayPredict); + String url = KMString.format("setBannerOptions(%s)", mayPredict); if (InAppKeyboard != null) { InAppKeyboard.loadJavascript(url); } From 887e6b2628f1e61135185316b4c1e1e23bfacab3 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 08:42:06 +0700 Subject: [PATCH 41/57] chore(web): Revert 2 files to master --- web/src/engine/osk/src/banner/banner.ts | 479 +++++++++++++++++++- web/src/engine/osk/src/banner/bannerView.ts | 204 +++++++-- 2 files changed, 654 insertions(+), 29 deletions(-) diff --git a/web/src/engine/osk/src/banner/banner.ts b/web/src/engine/osk/src/banner/banner.ts index 7e39dcfe980..999f7d63486 100644 --- a/web/src/engine/osk/src/banner/banner.ts +++ b/web/src/engine/osk/src/banner/banner.ts @@ -1,4 +1,13 @@ +import EventEmitter from 'eventemitter3'; + +import { DeviceSpec } from '@keymanapp/web-utils'; import { Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor'; +import { type PredictionContext } from '@keymanapp/input-processor'; +import InputEventEngine, { InputEventEngineConfig } from '../input/event-interpreter/inputEventEngine.js'; +import MouseEventEngine from '../input/event-interpreter/mouseEventEngine.js'; +import TouchEventEngine from '../input/event-interpreter/touchEventEngine.js'; +import UITouchHandlerBase from '../input/event-interpreter/uiTouchHandlerBase.js'; + import { createUnselectableElement } from 'keyman/engine/dom-utils'; // Base class for a banner above the keyboard in the OSK @@ -89,5 +98,473 @@ export abstract class Banner { * @param keyboardProperties */ public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) { } - abstract get type(); +} + +/** + * Function BlankBanner + * Description A banner of height 0 that should not be shown + */ +export class BlankBanner extends Banner { + + constructor() { + super(0); + } +} + +/** + * Function ImageBanner + * @param {string} imagePath Path of image to display in the banner + * @param {number} height If provided, the height of the banner in pixels + * Description Display an image in the banner + */ +export class ImageBanner extends Banner { + private img: HTMLElement; + + constructor(imagePath: string, height?: number) { + if (imagePath.length > 0) { + super(); + if (height) { + this.height = height; + } + } else { + super(0); + } + + if(imagePath.indexOf('base64') >=0) { + console.log("Loading img from base64 data"); + } else { + console.log("Loading img with src '" + imagePath + "'"); + } + this.img = document.createElement('img'); + this.img.setAttribute('src', imagePath); + let ds = this.img.style; + ds.width = '100%'; + ds.height = '100%'; + this.getDiv().appendChild(this.img); + console.log("Image loaded."); + } + + /** + * Function setImagePath + * Scope Public + * @param {string} imagePath Path of image to display in the banner + * Description Update the image in the banner + */ + public setImagePath(imagePath: string) { + if (this.img) { + this.img.setAttribute('src', imagePath); + } + } +} + +export class BannerSuggestion { + div: HTMLDivElement; + private display: HTMLSpanElement; + private fontFamily?: string; + private rtl: boolean = false; + + private _suggestion: Suggestion; + + private index: number; + + static readonly BASE_ID = 'kmw-suggestion-'; + + constructor(index: number, isRTL: boolean) { + this.index = index; + this.rtl = isRTL; + + this.constructRoot(); + + // Provides an empty, base SPAN for text display. We'll swap these out regularly; + // `Suggestion`s will have varying length and may need different styling. + let display = this.display = createUnselectableElement('span'); + this.div.appendChild(display); + } + + private constructRoot() { + // Add OSK suggestion labels + let div = this.div = createUnselectableElement('div'), ds=div.style; + div.className = "kmw-suggest-option"; + div.id = BannerSuggestion.BASE_ID + this.index; + + // Ensures that a reasonable width % is set. + let usableWidth = 100 - SuggestionBanner.MARGIN * (SuggestionBanner.SUGGESTION_LIMIT - 1); + let widthpc = usableWidth / SuggestionBanner.SUGGESTION_LIMIT; + + ds.width = widthpc + '%'; + + this.div['suggestion'] = this; + } + + public matchKeyboardProperties(keyboardProperties: KeyboardProperties) { + const div = this.div; + + if(keyboardProperties) { + if (keyboardProperties['KLC']) { + div.lang = keyboardProperties['KLC']; + } + + // Establish base font settings + let font = keyboardProperties['KFont']; + if(font && font.family && font.family != '') { + div.style.fontFamily = this.fontFamily = font.family; + } + } + } + + get suggestion(): Suggestion { + return this._suggestion; + } + + /** + * Function update + * @param {string} id Element ID for the suggestion span + * @param {Suggestion} suggestion Suggestion from the lexical model + * Description Update the ID and text of the BannerSuggestionSpec + */ + public update(suggestion: Suggestion) { + this._suggestion = suggestion; + this.updateText(); + } + + private updateText() { + let display = this.generateSuggestionText(this.rtl); + this.div.replaceChild(display, this.display); + this.display = display; + } + + public isEmpty(): boolean { + return !this._suggestion; + } + + /** + * Function generateSuggestionText + * @return {HTMLSpanElement} Span element of the suggestion + * Description Produces a HTMLSpanElement with the key's actual text. + */ + // + public generateSuggestionText(rtl: boolean): HTMLSpanElement { + let suggestion = this._suggestion; + var suggestionText: string; + + var s=createUnselectableElement('span'); + s.className = 'kmw-suggestion-text'; + + if(suggestion == null) { + return s; + } + + if(suggestion.displayAs == null || suggestion.displayAs == '') { + suggestionText = '\xa0'; // default: nbsp. + } else { + // Default the LTR ordering to match that of the active keyboard. + let orderCode = rtl ? 0x202e /* RTL */ : 0x202d /* LTR */; + suggestionText = String.fromCharCode(orderCode) + suggestion.displayAs; + } + + // TODO: Dynamic suggestion text resizing. (Refer to OSKKey.getTextWidth in visualKeyboard.ts.) + + // Finalize the suggestion text + s.innerHTML = suggestionText; + return s; + } +} + +/** + * Function SuggestionBanner + * Scope Public + * @param {number} height - If provided, the height of the banner in pixels + * Description Display lexical model suggestions in the banner + */ +export class SuggestionBanner extends Banner { + public static readonly SUGGESTION_LIMIT: number = 3; + public static readonly MARGIN = 1; + + public readonly events: EventEmitter; + + private currentSuggestions: Suggestion[] = []; + + private options : BannerSuggestion[] = []; + private hostDevice: DeviceSpec; + + private manager: SuggestionInputManager; + + private _predictionContext: PredictionContext; + + static readonly TOUCHED_CLASS: string = 'kmw-suggest-touched'; + static readonly BANNER_CLASS: string = 'kmw-suggest-banner'; + + constructor(hostDevice: DeviceSpec, height?: number) { + super(height || Banner.DEFAULT_HEIGHT); + this.hostDevice = hostDevice; + + this.getDiv().className = this.getDiv().className + ' ' + SuggestionBanner.BANNER_CLASS; + + this.buildInternals(false); + + this.manager = new SuggestionInputManager(this.getDiv()); + this.events = this.manager.events; + + this.setupInputHandling(); + } + + buildInternals(rtl: boolean) { + if(this.options.length > 0) { + this.options.splice(0, this.options.length); // Clear the array. + } + for (var i=0; i { + const elem = suggestion.div; + let classes = elem.className; + let cs = ' ' + SuggestionBanner.TOUCHED_CLASS; + + if(on && classes.indexOf(cs) < 0) { + elem.className=classes+cs; + } else { + elem.className=classes.replace(cs,''); + } + }); + + this.manager.events.on('apply', (option) => { + if(this.predictionContext) { + this.predictionContext.accept(option.suggestion); + } + }); + } + + public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) { + const rtl = keyboard.isRTL; + + // Removes all previous children. (.replaceChildren requires Chrome for Android 86.) + // Instantly replaces all children with an empty text node, bypassing the need to actually + // parse incoming HTML. + // + // Just in case, alternative approaches: https://stackoverflow.com/a/3955238 + this.getDiv().textContent = ''; + + // Builds new children to match needed RTL properties. + this.buildInternals(rtl); + + this.options.forEach((option) => option.matchKeyboardProperties(keyboardProperties)); + this.onSuggestionUpdate(this.currentSuggestions); // restore suggestions + } + + private get mouseEventConfig() { + const config: InputEventEngineConfig = { + targetRoot: this.getDiv(), + // document.body is the event root b/c we need to track the mouse if it leaves + // the VisualKeyboard's hierarchy. + eventRoot: document.body, + inputStartHandler: this.manager.touchStart.bind(this.manager), + inputMoveHandler: this.manager.touchMove.bind(this.manager), + inputEndHandler: this.manager.touchEnd.bind(this.manager), + coordConstrainedWithinInteractiveBounds: function() { return true; } + }; + + return new MouseEventEngine(config); + } + + private get touchEventConfig() { + const config: InputEventEngineConfig = { + targetRoot: this.getDiv(), + // document.body is the event root b/c we need to track the mouse if it leaves + // the VisualKeyboard's hierarchy. + eventRoot: this.getDiv(), + inputStartHandler: this.manager.touchStart.bind(this.manager), + inputMoveHandler: this.manager.touchMove.bind(this.manager), + inputEndHandler: this.manager.touchEnd.bind(this.manager), + coordConstrainedWithinInteractiveBounds: function() { return true; } + }; + + return new TouchEventEngine(config); + } + + public get predictionContext(): PredictionContext { + return this._predictionContext; + } + + public set predictionContext(context: PredictionContext) { + if(this._predictionContext) { + // disconnect the old one! + this._predictionContext.off('update', this.onSuggestionUpdate); + } + + // connect the new one! + this._predictionContext = context; + if(context) { + context.on('update', this.onSuggestionUpdate); + this.onSuggestionUpdate(context.currentSuggestions); + } + } + + public onSuggestionUpdate = (suggestions: Suggestion[]): void => { + this.currentSuggestions = suggestions; + + this.options.forEach((option: BannerSuggestion, i: number) => { + if(i < suggestions.length) { + option.update(suggestions[i]); + } else { + option.update(null); + } + }); + } +} + +interface SuggestionInputEventMap { + highlight: (bannerSuggestion: BannerSuggestion, state: boolean) => void, + apply: (bannerSuggestion: BannerSuggestion) => void; + hold: (bannerSuggestion: BannerSuggestion) => void; +} + +class SuggestionInputManager extends UITouchHandlerBase { + public readonly events = new EventEmitter(); + + private eventDisablePromise: Promise; + + platformHold: (suggestion: BannerSuggestion, isCustom: boolean) => void; + + //#region Touch handling implementation + findTargetFrom(e: HTMLElement): HTMLDivElement { + try { + if(e) { + if(e.classList.contains('kmw-suggest-option')) { + return e as HTMLDivElement; + } + if(e.parentElement && e.parentElement.classList.contains('kmw-suggest-option')) { + return e.parentElement as HTMLDivElement; + } + // if(e.firstChild && util.hasClass( e.firstChild,'kmw-suggest-option')) { + // return e.firstChild as HTMLDivElement; + // } + } + } catch(ex) {} + return null; + } + + protected highlight(t: HTMLDivElement, on: boolean): void { + let suggestion = t['suggestion'] as BannerSuggestion; + + // Never highlight an empty suggestion button. + if(suggestion.isEmpty()) { + on = false; + } + + this.events.emit('highlight', suggestion, on); + } + + protected select(t: HTMLDivElement): void { + this.events.emit('apply', t['suggestion'] as BannerSuggestion); + } + + //#region Long-press support + protected hold(t: HTMLDivElement): void { + // let suggestionObj = t['suggestion'] as BannerSuggestion; + // + // // Is this the suggestion? It's never in this.currentSuggestions, so check against that. + // let isCustom = this.currentSuggestions.indexOf(suggestionObj.suggestion) == -1; + + this.events.emit('hold', t['suggestion'] as BannerSuggestion); + } + protected clearHolds(): void { + // Temp, pending implementation of suggestion longpress submenus + // - nothing to clear without them - + + // only really used in native-KMW + } + + protected hasModalPopup(): boolean { + return this.eventsBlocked; + } + + protected dealiasSubTarget(target: HTMLDivElement): HTMLDivElement { + return target; + } + + protected hasSubmenu(t: HTMLDivElement): boolean { + // Temp, pending implementation of suggestion longpress submenus + + // Only really used by native-KMW - see kmwnative's highlightSubKeys func. + return false; + } + + protected isSubmenuActive(): boolean { + // Temp, pending implementation of suggestion longpress submenus + + // Utilized only by native-KMW - it parallels hasModalPopup() in purpose. + return false; + } + + protected displaySubmenuFor(target: HTMLDivElement) { + // Utilized only by native-KMW to show submenus. + throw new Error("Method not implemented."); + } + //#endregion + //#endregion + + public get eventsBlocked(): boolean { + return !!this.eventDisablePromise; + } + + /** + * Intended for use by the mobile apps, which sometimes 'takes over' touch handling. + * For such cases, input should be blocked within KMW when the apps are managing an + * ongoing touch-hold for any other interaction. + * + * Formerly: + ``` + let keyman = com.keyman.singleton; + return keyman['osk'].vkbd.subkeyGesture && keyman.isEmbedded; + ``` + */ + public temporarilyBlockEvents(promise: Promise) { // TODO: ensure connection for embedded mode! + this.eventDisablePromise = promise; // Will require routing; this class is not exported! + promise.finally(() => { + this.eventDisablePromise = null; + }) + } + + constructor(div: HTMLElement) { + // TODO: Determine appropriate CSS styling names, etc. + super(div, Banner.BANNER_CLASS, SuggestionBanner.TOUCHED_CLASS); + } } diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 9cd6b598a26..556ec61762d 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -1,11 +1,12 @@ import EventEmitter from 'eventemitter3'; -import { createUnselectableElement } from 'keyman/engine/dom-utils'; - -import { Banner } from './banner.js'; +import { Banner, BlankBanner, ImageBanner, SuggestionBanner } from './banner.js'; import OSKViewComponent from '../components/oskViewComponent.interface.js'; import { ParsedLengthStyle } from '../lengthStyle.js'; -import { BlankBanner } from './blankBanner.js'; + +import { DeviceSpec } from '@keymanapp/web-utils'; +import type { PredictionContext, StateChangeEnum } from '@keymanapp/input-processor'; +import { createUnselectableElement } from 'keyman/engine/dom-utils'; /** * This object is used to specify options by both `BannerManager.getOptions` @@ -17,7 +18,7 @@ export interface BannerOptions { imagePath?: string; } -export type BannerType = "blank" | "image" | "suggestion" | "html"; +export type BannerType = "blank" | "image" | "suggestion"; interface BannerViewEventMap { 'bannerchange': () => void; @@ -44,12 +45,12 @@ interface BannerViewEventMap { * * This should help to avoid wasting computational resources. * * It will listen to ModelManager events and automatically swap Banner * instances as appropriate: - * * The option `alwaysShow == true` is designed to replicate current + * * The option `persistentBanner == true` is designed to replicate current * iOS system keyboard behavior. * * When true, an `ImageBanner` will be displayed. * * If false, it will be replaced with a `BlankBanner` of zero height, * corresponding to our current default lack of banner. - * * It will not automatically set `alwaysShow == true`; + * * It will not automatically set `persistentBanner == true`; * this must be set by the iOS app, and only under the following conditions: * * `keyman.isEmbedded == true` * * `device.OS == 'ios'` @@ -57,13 +58,9 @@ interface BannerViewEventMap { * needs to reserve this space (i.e: Keyman for iOS), * rather than as its standalone app. */ -export class BannerView implements OSKViewComponent { +export default class BannerView implements OSKViewComponent { private bannerContainer: HTMLDivElement; - - /** - * The currently active banner. - */ - private currentBanner: Banner; + private activeBanner: Banner; private _activeBannerHeight: number = Banner.DEFAULT_HEIGHT; public readonly events = new EventEmitter(); @@ -94,31 +91,32 @@ export class BannerView implements OSKViewComponent { * Applies any stylesheets needed by specific `Banner` instances. */ public appendStyles() { - if(this.currentBanner) { - this.currentBanner.appendStyleSheet(); + if(this.activeBanner) { + this.activeBanner.appendStyleSheet(); } } public get banner(): Banner { - return this.currentBanner; + return this.activeBanner; } /** - * The `Banner` actively being displayed to the user in the OSK's current state, - * whether a `SuggestionBanner` (with predictive-text active) or a different - * type for use when the predictive-text engine is inactive. + * Sets the active `Banner` to the specified type, regardless of + * existing management logic settings. + * + * @param banner The `Banner` instance to set as active. */ public set banner(banner: Banner) { - if(this.currentBanner) { - if(banner == this.currentBanner) { + if(this.activeBanner) { + if(banner == this.activeBanner) { return; } else { - let prevBanner = this.currentBanner; - this.currentBanner = banner; + let prevBanner = this.activeBanner; + this.activeBanner = banner; this.bannerContainer.replaceChild(banner.getDiv(), prevBanner.getDiv()); } } else { - this.currentBanner = banner; + this.activeBanner = banner; if(banner) { this.bannerContainer.appendChild(banner.getDiv()); } @@ -135,8 +133,8 @@ export class BannerView implements OSKViewComponent { * Gets the height (in pixels) of the active `Banner` instance. */ public get height(): number { - if(this.currentBanner) { - return this.currentBanner.height; + if(this.activeBanner) { + return this.activeBanner.height; } else { return 0; } @@ -152,8 +150,8 @@ export class BannerView implements OSKViewComponent { public set activeBannerHeight(h: number) { this._activeBannerHeight = h; - if (this.currentBanner && !(this.currentBanner instanceof BlankBanner)) { - this.currentBanner.height = h; + if (this.activeBanner && !(this.activeBanner instanceof BlankBanner)) { + this.activeBanner.height = h; } } @@ -162,4 +160,154 @@ export class BannerView implements OSKViewComponent { } public refreshLayout() {}; +} + +export class BannerController { + private _activeType: BannerType; + private _options: BannerOptions = {}; + private container: BannerView; + private alwaysShow: boolean; + private imagePath?: string = ""; + + private predictionContext?: PredictionContext; + + private readonly hostDevice: DeviceSpec; + + public static readonly DEFAULT_OPTIONS: BannerOptions = { + alwaysShow: false, + imagePath: "" + } + + constructor(bannerView: BannerView, hostDevice: DeviceSpec, predictionContext?: PredictionContext) { + // Step 1 - establish the container element. Must come before this.setOptions. + this.hostDevice = hostDevice; + this.container = bannerView; + this.predictionContext = predictionContext; + + // Initialize with the default options - any 'manually set' options come post-construction. + // This will also automatically set the default banner in place. + this.setOptions(BannerController.DEFAULT_OPTIONS); + } + + /** + * This function corresponds to `keyman.osk.banner.getOptions`. + * + * Gets the current control settings in use by `BannerManager`. + */ + public getOptions(): BannerOptions { + let retObj = {}; + + for(let key in this._options) { + retObj[key] = this._options[key]; + } + + return retObj; + } + + /** + * This function corresponds to `keyman.osk.banner.setOptions`. + * + * Sets options used to tweak the automatic `Banner` + * control logic used by `BannerManager`. + * @param optionSpec An object specifying one or more of the following options: + * * `persistentBanner` (boolean) When `true`, ensures that a `Banner` + * is always displayed, even when no predictive model exists + * for the active language. + * + * Default: `false` + * * `imagePath` (URL string) Specifies the file path to use for an + * `ImageBanner` when `persistentBanner` is `true` and no predictive model exists. + * + * Default: `''`. + * * `enablePredictions` (boolean) Turns KMW predictions + * on (when `true`) and off (when `false`). + * + * Default: `true`. + */ + public setOptions(optionSpec: BannerOptions) { + for(let key in optionSpec) { + switch(key) { + // Each defined option may require specialized handling. + case 'alwaysShow': + // Determines the banner type to activate. + this.alwaysShow = optionSpec[key]; + break; + case 'imagePath': + // Determines the image file to use for ImageBanners. + this.imagePath = optionSpec[key]; + break; + default: + // Invalid option specified! + } + this._options[key] = optionSpec[key]; + + // If no banner instance exists yet, go with a safe, blank initialization. + if(!this.container.banner) { + this.selectBanner('inactive'); + } + } + } + + /** + * Sets the active `Banner` to the specified type, regardless of + * existing management logic settings. + * + * @param type `'blank' | 'image' | 'suggestion'` - A plain-text string + * representing the type of `Banner` to set active. + * @param height - Optional banner height in pixels. + */ + public setBanner(type: BannerType) { + var banner: Banner; + + let oldBanner = this.container.banner; + if(oldBanner instanceof SuggestionBanner) { + this.predictionContext.off('update', oldBanner.onSuggestionUpdate); + } + + switch(type) { + case 'blank': + banner = new BlankBanner(); + break; + case 'image': + banner = new ImageBanner(this.imagePath, this.container.activeBannerHeight); + break; + case 'suggestion': + let suggestBanner = banner = new SuggestionBanner(this.hostDevice, this.container.activeBannerHeight); + suggestBanner.predictionContext = this.predictionContext; + suggestBanner.events.on('apply', (selection) => this.predictionContext.accept(selection.suggestion)); + + this.predictionContext.on('update', suggestBanner.onSuggestionUpdate); + break; + default: + throw new Error("Invalid type specified for the banner!"); + } + + this._activeType = type; + + if(banner) { + this.container.banner = banner; + } + } + + /** + * Handles `LanguageProcessor`'s `'statechange'` events, + * allowing logic to automatically hot-swap `Banner`s as needed. + * @param state + */ + selectBanner(state: StateChangeEnum) { + // Only display a SuggestionBanner when LanguageProcessor states it is active. + if(state == 'active' || state == 'configured') { + this.setBanner('suggestion'); + } else if(state == 'inactive') { + if(this.alwaysShow) { + this.setBanner('image'); + } else { + this.setBanner('blank'); + } + } + } + + public get activeType(): BannerType { + return this._activeType; + } } \ No newline at end of file From 68f2661e1f118cfc105c2ef131d7f4c1477e90f9 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 08:45:26 +0700 Subject: [PATCH 42/57] chore(web): Revert take 2 --- web/src/engine/osk/src/banner/banner.ts | 480 +------------------- web/src/engine/osk/src/banner/bannerView.ts | 200 ++------ 2 files changed, 28 insertions(+), 652 deletions(-) diff --git a/web/src/engine/osk/src/banner/banner.ts b/web/src/engine/osk/src/banner/banner.ts index 999f7d63486..3394efc4382 100644 --- a/web/src/engine/osk/src/banner/banner.ts +++ b/web/src/engine/osk/src/banner/banner.ts @@ -1,13 +1,4 @@ -import EventEmitter from 'eventemitter3'; - -import { DeviceSpec } from '@keymanapp/web-utils'; import { Keyboard, KeyboardProperties } from '@keymanapp/keyboard-processor'; -import { type PredictionContext } from '@keymanapp/input-processor'; -import InputEventEngine, { InputEventEngineConfig } from '../input/event-interpreter/inputEventEngine.js'; -import MouseEventEngine from '../input/event-interpreter/mouseEventEngine.js'; -import TouchEventEngine from '../input/event-interpreter/touchEventEngine.js'; -import UITouchHandlerBase from '../input/event-interpreter/uiTouchHandlerBase.js'; - import { createUnselectableElement } from 'keyman/engine/dom-utils'; // Base class for a banner above the keyboard in the OSK @@ -98,473 +89,6 @@ export abstract class Banner { * @param keyboardProperties */ public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) { } -} - -/** - * Function BlankBanner - * Description A banner of height 0 that should not be shown - */ -export class BlankBanner extends Banner { - - constructor() { - super(0); - } -} - -/** - * Function ImageBanner - * @param {string} imagePath Path of image to display in the banner - * @param {number} height If provided, the height of the banner in pixels - * Description Display an image in the banner - */ -export class ImageBanner extends Banner { - private img: HTMLElement; - - constructor(imagePath: string, height?: number) { - if (imagePath.length > 0) { - super(); - if (height) { - this.height = height; - } - } else { - super(0); - } - - if(imagePath.indexOf('base64') >=0) { - console.log("Loading img from base64 data"); - } else { - console.log("Loading img with src '" + imagePath + "'"); - } - this.img = document.createElement('img'); - this.img.setAttribute('src', imagePath); - let ds = this.img.style; - ds.width = '100%'; - ds.height = '100%'; - this.getDiv().appendChild(this.img); - console.log("Image loaded."); - } - - /** - * Function setImagePath - * Scope Public - * @param {string} imagePath Path of image to display in the banner - * Description Update the image in the banner - */ - public setImagePath(imagePath: string) { - if (this.img) { - this.img.setAttribute('src', imagePath); - } - } -} - -export class BannerSuggestion { - div: HTMLDivElement; - private display: HTMLSpanElement; - private fontFamily?: string; - private rtl: boolean = false; - - private _suggestion: Suggestion; - - private index: number; - - static readonly BASE_ID = 'kmw-suggestion-'; - - constructor(index: number, isRTL: boolean) { - this.index = index; - this.rtl = isRTL; - - this.constructRoot(); - - // Provides an empty, base SPAN for text display. We'll swap these out regularly; - // `Suggestion`s will have varying length and may need different styling. - let display = this.display = createUnselectableElement('span'); - this.div.appendChild(display); - } - - private constructRoot() { - // Add OSK suggestion labels - let div = this.div = createUnselectableElement('div'), ds=div.style; - div.className = "kmw-suggest-option"; - div.id = BannerSuggestion.BASE_ID + this.index; - - // Ensures that a reasonable width % is set. - let usableWidth = 100 - SuggestionBanner.MARGIN * (SuggestionBanner.SUGGESTION_LIMIT - 1); - let widthpc = usableWidth / SuggestionBanner.SUGGESTION_LIMIT; - - ds.width = widthpc + '%'; - - this.div['suggestion'] = this; - } - - public matchKeyboardProperties(keyboardProperties: KeyboardProperties) { - const div = this.div; - - if(keyboardProperties) { - if (keyboardProperties['KLC']) { - div.lang = keyboardProperties['KLC']; - } - - // Establish base font settings - let font = keyboardProperties['KFont']; - if(font && font.family && font.family != '') { - div.style.fontFamily = this.fontFamily = font.family; - } - } - } - get suggestion(): Suggestion { - return this._suggestion; - } - - /** - * Function update - * @param {string} id Element ID for the suggestion span - * @param {Suggestion} suggestion Suggestion from the lexical model - * Description Update the ID and text of the BannerSuggestionSpec - */ - public update(suggestion: Suggestion) { - this._suggestion = suggestion; - this.updateText(); - } - - private updateText() { - let display = this.generateSuggestionText(this.rtl); - this.div.replaceChild(display, this.display); - this.display = display; - } - - public isEmpty(): boolean { - return !this._suggestion; - } - - /** - * Function generateSuggestionText - * @return {HTMLSpanElement} Span element of the suggestion - * Description Produces a HTMLSpanElement with the key's actual text. - */ - // - public generateSuggestionText(rtl: boolean): HTMLSpanElement { - let suggestion = this._suggestion; - var suggestionText: string; - - var s=createUnselectableElement('span'); - s.className = 'kmw-suggestion-text'; - - if(suggestion == null) { - return s; - } - - if(suggestion.displayAs == null || suggestion.displayAs == '') { - suggestionText = '\xa0'; // default: nbsp. - } else { - // Default the LTR ordering to match that of the active keyboard. - let orderCode = rtl ? 0x202e /* RTL */ : 0x202d /* LTR */; - suggestionText = String.fromCharCode(orderCode) + suggestion.displayAs; - } - - // TODO: Dynamic suggestion text resizing. (Refer to OSKKey.getTextWidth in visualKeyboard.ts.) - - // Finalize the suggestion text - s.innerHTML = suggestionText; - return s; - } -} - -/** - * Function SuggestionBanner - * Scope Public - * @param {number} height - If provided, the height of the banner in pixels - * Description Display lexical model suggestions in the banner - */ -export class SuggestionBanner extends Banner { - public static readonly SUGGESTION_LIMIT: number = 3; - public static readonly MARGIN = 1; - - public readonly events: EventEmitter; - - private currentSuggestions: Suggestion[] = []; - - private options : BannerSuggestion[] = []; - private hostDevice: DeviceSpec; - - private manager: SuggestionInputManager; - - private _predictionContext: PredictionContext; - - static readonly TOUCHED_CLASS: string = 'kmw-suggest-touched'; - static readonly BANNER_CLASS: string = 'kmw-suggest-banner'; - - constructor(hostDevice: DeviceSpec, height?: number) { - super(height || Banner.DEFAULT_HEIGHT); - this.hostDevice = hostDevice; - - this.getDiv().className = this.getDiv().className + ' ' + SuggestionBanner.BANNER_CLASS; - - this.buildInternals(false); - - this.manager = new SuggestionInputManager(this.getDiv()); - this.events = this.manager.events; - - this.setupInputHandling(); - } - - buildInternals(rtl: boolean) { - if(this.options.length > 0) { - this.options.splice(0, this.options.length); // Clear the array. - } - for (var i=0; i { - const elem = suggestion.div; - let classes = elem.className; - let cs = ' ' + SuggestionBanner.TOUCHED_CLASS; - - if(on && classes.indexOf(cs) < 0) { - elem.className=classes+cs; - } else { - elem.className=classes.replace(cs,''); - } - }); - - this.manager.events.on('apply', (option) => { - if(this.predictionContext) { - this.predictionContext.accept(option.suggestion); - } - }); - } - - public configureForKeyboard(keyboard: Keyboard, keyboardProperties: KeyboardProperties) { - const rtl = keyboard.isRTL; - - // Removes all previous children. (.replaceChildren requires Chrome for Android 86.) - // Instantly replaces all children with an empty text node, bypassing the need to actually - // parse incoming HTML. - // - // Just in case, alternative approaches: https://stackoverflow.com/a/3955238 - this.getDiv().textContent = ''; - - // Builds new children to match needed RTL properties. - this.buildInternals(rtl); - - this.options.forEach((option) => option.matchKeyboardProperties(keyboardProperties)); - this.onSuggestionUpdate(this.currentSuggestions); // restore suggestions - } - - private get mouseEventConfig() { - const config: InputEventEngineConfig = { - targetRoot: this.getDiv(), - // document.body is the event root b/c we need to track the mouse if it leaves - // the VisualKeyboard's hierarchy. - eventRoot: document.body, - inputStartHandler: this.manager.touchStart.bind(this.manager), - inputMoveHandler: this.manager.touchMove.bind(this.manager), - inputEndHandler: this.manager.touchEnd.bind(this.manager), - coordConstrainedWithinInteractiveBounds: function() { return true; } - }; - - return new MouseEventEngine(config); - } - - private get touchEventConfig() { - const config: InputEventEngineConfig = { - targetRoot: this.getDiv(), - // document.body is the event root b/c we need to track the mouse if it leaves - // the VisualKeyboard's hierarchy. - eventRoot: this.getDiv(), - inputStartHandler: this.manager.touchStart.bind(this.manager), - inputMoveHandler: this.manager.touchMove.bind(this.manager), - inputEndHandler: this.manager.touchEnd.bind(this.manager), - coordConstrainedWithinInteractiveBounds: function() { return true; } - }; - - return new TouchEventEngine(config); - } - - public get predictionContext(): PredictionContext { - return this._predictionContext; - } - - public set predictionContext(context: PredictionContext) { - if(this._predictionContext) { - // disconnect the old one! - this._predictionContext.off('update', this.onSuggestionUpdate); - } - - // connect the new one! - this._predictionContext = context; - if(context) { - context.on('update', this.onSuggestionUpdate); - this.onSuggestionUpdate(context.currentSuggestions); - } - } - - public onSuggestionUpdate = (suggestions: Suggestion[]): void => { - this.currentSuggestions = suggestions; - - this.options.forEach((option: BannerSuggestion, i: number) => { - if(i < suggestions.length) { - option.update(suggestions[i]); - } else { - option.update(null); - } - }); - } -} - -interface SuggestionInputEventMap { - highlight: (bannerSuggestion: BannerSuggestion, state: boolean) => void, - apply: (bannerSuggestion: BannerSuggestion) => void; - hold: (bannerSuggestion: BannerSuggestion) => void; -} - -class SuggestionInputManager extends UITouchHandlerBase { - public readonly events = new EventEmitter(); - - private eventDisablePromise: Promise; - - platformHold: (suggestion: BannerSuggestion, isCustom: boolean) => void; - - //#region Touch handling implementation - findTargetFrom(e: HTMLElement): HTMLDivElement { - try { - if(e) { - if(e.classList.contains('kmw-suggest-option')) { - return e as HTMLDivElement; - } - if(e.parentElement && e.parentElement.classList.contains('kmw-suggest-option')) { - return e.parentElement as HTMLDivElement; - } - // if(e.firstChild && util.hasClass( e.firstChild,'kmw-suggest-option')) { - // return e.firstChild as HTMLDivElement; - // } - } - } catch(ex) {} - return null; - } - - protected highlight(t: HTMLDivElement, on: boolean): void { - let suggestion = t['suggestion'] as BannerSuggestion; - - // Never highlight an empty suggestion button. - if(suggestion.isEmpty()) { - on = false; - } - - this.events.emit('highlight', suggestion, on); - } - - protected select(t: HTMLDivElement): void { - this.events.emit('apply', t['suggestion'] as BannerSuggestion); - } - - //#region Long-press support - protected hold(t: HTMLDivElement): void { - // let suggestionObj = t['suggestion'] as BannerSuggestion; - // - // // Is this the suggestion? It's never in this.currentSuggestions, so check against that. - // let isCustom = this.currentSuggestions.indexOf(suggestionObj.suggestion) == -1; - - this.events.emit('hold', t['suggestion'] as BannerSuggestion); - } - protected clearHolds(): void { - // Temp, pending implementation of suggestion longpress submenus - // - nothing to clear without them - - - // only really used in native-KMW - } - - protected hasModalPopup(): boolean { - return this.eventsBlocked; - } - - protected dealiasSubTarget(target: HTMLDivElement): HTMLDivElement { - return target; - } - - protected hasSubmenu(t: HTMLDivElement): boolean { - // Temp, pending implementation of suggestion longpress submenus - - // Only really used by native-KMW - see kmwnative's highlightSubKeys func. - return false; - } - - protected isSubmenuActive(): boolean { - // Temp, pending implementation of suggestion longpress submenus - - // Utilized only by native-KMW - it parallels hasModalPopup() in purpose. - return false; - } - - protected displaySubmenuFor(target: HTMLDivElement) { - // Utilized only by native-KMW to show submenus. - throw new Error("Method not implemented."); - } - //#endregion - //#endregion - - public get eventsBlocked(): boolean { - return !!this.eventDisablePromise; - } - - /** - * Intended for use by the mobile apps, which sometimes 'takes over' touch handling. - * For such cases, input should be blocked within KMW when the apps are managing an - * ongoing touch-hold for any other interaction. - * - * Formerly: - ``` - let keyman = com.keyman.singleton; - return keyman['osk'].vkbd.subkeyGesture && keyman.isEmbedded; - ``` - */ - public temporarilyBlockEvents(promise: Promise) { // TODO: ensure connection for embedded mode! - this.eventDisablePromise = promise; // Will require routing; this class is not exported! - promise.finally(() => { - this.eventDisablePromise = null; - }) - } - - constructor(div: HTMLElement) { - // TODO: Determine appropriate CSS styling names, etc. - super(div, Banner.BANNER_CLASS, SuggestionBanner.TOUCHED_CLASS); - } -} + abstract get type(); +} \ No newline at end of file diff --git a/web/src/engine/osk/src/banner/bannerView.ts b/web/src/engine/osk/src/banner/bannerView.ts index 556ec61762d..b291f7454e0 100644 --- a/web/src/engine/osk/src/banner/bannerView.ts +++ b/web/src/engine/osk/src/banner/bannerView.ts @@ -1,12 +1,11 @@ import EventEmitter from 'eventemitter3'; -import { Banner, BlankBanner, ImageBanner, SuggestionBanner } from './banner.js'; +import { createUnselectableElement } from 'keyman/engine/dom-utils'; + +import { Banner } from './banner.js'; import OSKViewComponent from '../components/oskViewComponent.interface.js'; import { ParsedLengthStyle } from '../lengthStyle.js'; - -import { DeviceSpec } from '@keymanapp/web-utils'; -import type { PredictionContext, StateChangeEnum } from '@keymanapp/input-processor'; -import { createUnselectableElement } from 'keyman/engine/dom-utils'; +import { BlankBanner } from './blankBanner.js'; /** * This object is used to specify options by both `BannerManager.getOptions` @@ -18,7 +17,7 @@ export interface BannerOptions { imagePath?: string; } -export type BannerType = "blank" | "image" | "suggestion"; +export type BannerType = "blank" | "image" | "suggestion" | "html"; interface BannerViewEventMap { 'bannerchange': () => void; @@ -58,9 +57,13 @@ interface BannerViewEventMap { * needs to reserve this space (i.e: Keyman for iOS), * rather than as its standalone app. */ -export default class BannerView implements OSKViewComponent { +export class BannerView implements OSKViewComponent { private bannerContainer: HTMLDivElement; - private activeBanner: Banner; + + /** + * The currently active banner. + */ + private currentBanner: Banner; private _activeBannerHeight: number = Banner.DEFAULT_HEIGHT; public readonly events = new EventEmitter(); @@ -91,32 +94,31 @@ export default class BannerView implements OSKViewComponent { * Applies any stylesheets needed by specific `Banner` instances. */ public appendStyles() { - if(this.activeBanner) { - this.activeBanner.appendStyleSheet(); + if(this.currentBanner) { + this.currentBanner.appendStyleSheet(); } } public get banner(): Banner { - return this.activeBanner; + return this.currentBanner; } /** - * Sets the active `Banner` to the specified type, regardless of - * existing management logic settings. - * - * @param banner The `Banner` instance to set as active. + * The `Banner` actively being displayed to the user in the OSK's current state, + * whether a `SuggestionBanner` (with predictive-text active) or a different + * type for use when the predictive-text engine is inactive. */ public set banner(banner: Banner) { - if(this.activeBanner) { - if(banner == this.activeBanner) { + if(this.currentBanner) { + if(banner == this.currentBanner) { return; } else { - let prevBanner = this.activeBanner; - this.activeBanner = banner; + let prevBanner = this.currentBanner; + this.currentBanner = banner; this.bannerContainer.replaceChild(banner.getDiv(), prevBanner.getDiv()); } } else { - this.activeBanner = banner; + this.currentBanner = banner; if(banner) { this.bannerContainer.appendChild(banner.getDiv()); } @@ -133,8 +135,8 @@ export default class BannerView implements OSKViewComponent { * Gets the height (in pixels) of the active `Banner` instance. */ public get height(): number { - if(this.activeBanner) { - return this.activeBanner.height; + if(this.currentBanner) { + return this.currentBanner.height; } else { return 0; } @@ -150,8 +152,8 @@ export default class BannerView implements OSKViewComponent { public set activeBannerHeight(h: number) { this._activeBannerHeight = h; - if (this.activeBanner && !(this.activeBanner instanceof BlankBanner)) { - this.activeBanner.height = h; + if (this.currentBanner && !(this.currentBanner instanceof BlankBanner)) { + this.currentBanner.height = h; } } @@ -160,154 +162,4 @@ export default class BannerView implements OSKViewComponent { } public refreshLayout() {}; -} - -export class BannerController { - private _activeType: BannerType; - private _options: BannerOptions = {}; - private container: BannerView; - private alwaysShow: boolean; - private imagePath?: string = ""; - - private predictionContext?: PredictionContext; - - private readonly hostDevice: DeviceSpec; - - public static readonly DEFAULT_OPTIONS: BannerOptions = { - alwaysShow: false, - imagePath: "" - } - - constructor(bannerView: BannerView, hostDevice: DeviceSpec, predictionContext?: PredictionContext) { - // Step 1 - establish the container element. Must come before this.setOptions. - this.hostDevice = hostDevice; - this.container = bannerView; - this.predictionContext = predictionContext; - - // Initialize with the default options - any 'manually set' options come post-construction. - // This will also automatically set the default banner in place. - this.setOptions(BannerController.DEFAULT_OPTIONS); - } - - /** - * This function corresponds to `keyman.osk.banner.getOptions`. - * - * Gets the current control settings in use by `BannerManager`. - */ - public getOptions(): BannerOptions { - let retObj = {}; - - for(let key in this._options) { - retObj[key] = this._options[key]; - } - - return retObj; - } - - /** - * This function corresponds to `keyman.osk.banner.setOptions`. - * - * Sets options used to tweak the automatic `Banner` - * control logic used by `BannerManager`. - * @param optionSpec An object specifying one or more of the following options: - * * `persistentBanner` (boolean) When `true`, ensures that a `Banner` - * is always displayed, even when no predictive model exists - * for the active language. - * - * Default: `false` - * * `imagePath` (URL string) Specifies the file path to use for an - * `ImageBanner` when `persistentBanner` is `true` and no predictive model exists. - * - * Default: `''`. - * * `enablePredictions` (boolean) Turns KMW predictions - * on (when `true`) and off (when `false`). - * - * Default: `true`. - */ - public setOptions(optionSpec: BannerOptions) { - for(let key in optionSpec) { - switch(key) { - // Each defined option may require specialized handling. - case 'alwaysShow': - // Determines the banner type to activate. - this.alwaysShow = optionSpec[key]; - break; - case 'imagePath': - // Determines the image file to use for ImageBanners. - this.imagePath = optionSpec[key]; - break; - default: - // Invalid option specified! - } - this._options[key] = optionSpec[key]; - - // If no banner instance exists yet, go with a safe, blank initialization. - if(!this.container.banner) { - this.selectBanner('inactive'); - } - } - } - - /** - * Sets the active `Banner` to the specified type, regardless of - * existing management logic settings. - * - * @param type `'blank' | 'image' | 'suggestion'` - A plain-text string - * representing the type of `Banner` to set active. - * @param height - Optional banner height in pixels. - */ - public setBanner(type: BannerType) { - var banner: Banner; - - let oldBanner = this.container.banner; - if(oldBanner instanceof SuggestionBanner) { - this.predictionContext.off('update', oldBanner.onSuggestionUpdate); - } - - switch(type) { - case 'blank': - banner = new BlankBanner(); - break; - case 'image': - banner = new ImageBanner(this.imagePath, this.container.activeBannerHeight); - break; - case 'suggestion': - let suggestBanner = banner = new SuggestionBanner(this.hostDevice, this.container.activeBannerHeight); - suggestBanner.predictionContext = this.predictionContext; - suggestBanner.events.on('apply', (selection) => this.predictionContext.accept(selection.suggestion)); - - this.predictionContext.on('update', suggestBanner.onSuggestionUpdate); - break; - default: - throw new Error("Invalid type specified for the banner!"); - } - - this._activeType = type; - - if(banner) { - this.container.banner = banner; - } - } - - /** - * Handles `LanguageProcessor`'s `'statechange'` events, - * allowing logic to automatically hot-swap `Banner`s as needed. - * @param state - */ - selectBanner(state: StateChangeEnum) { - // Only display a SuggestionBanner when LanguageProcessor states it is active. - if(state == 'active' || state == 'configured') { - this.setBanner('suggestion'); - } else if(state == 'inactive') { - if(this.alwaysShow) { - this.setBanner('image'); - } else { - this.setBanner('blank'); - } - } - } - - public get activeType(): BannerType { - return this._activeType; - } } \ No newline at end of file From 44fa59b463e758bc1ba379dd543efe4daba2b392 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 11:40:25 +0700 Subject: [PATCH 43/57] chore(android/engine): Revert some changes in android-host.js --- android/KMEA/app/src/main/assets/android-host.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 7ebeb6679d2..317d3d14165 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -107,7 +107,6 @@ function setBannerHeight(h) { // Refresh KMW's OSK keyman.refreshOskLayout(); - doResetContext(); } function setOskHeight(h) { @@ -118,7 +117,6 @@ function setOskHeight(h) { keyman.core.activeKeyboard.refreshLayouts(); } keyman.refreshOskLayout(); - doResetContext(); } function setOskWidth(w) { @@ -205,13 +203,18 @@ function enableSuggestions(model, mayPredict, mayCorrect) { keyman.core.languageProcessor.mayPredict = mayPredict; keyman.core.languageProcessor.mayCorrect = mayCorrect; - keyman.addModel(model); + registerModel(model); } function setBannerOptions(mayPredict) { keyman.core.languageProcessor.mayPredict = mayPredict; } +function registerModel(model) { + //window.console.log('registerModel: ' + model); + keyman.addModel(model); +} + function resetContext() { keyman.resetContext(); } From 8249abd8293dbdb9d96563f06819e368b5accc39 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 11:52:21 +0700 Subject: [PATCH 44/57] fix(android): Handle single quotes in HTMl banner --- android/KMAPro/kMAPro/src/main/assets/svg/banner.html | 6 +++--- .../app/src/main/java/com/keyman/engine/KMKeyboard.java | 5 +++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html index 8750ba1e05c..4ca3cf77894 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/svg/banner.html @@ -4,7 +4,7 @@ -
-
-
+
+
+
diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index f7504e8ad0b..215630cd03d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -382,7 +382,7 @@ public void onConfigurationChanged(Configuration newConfig) { int bannerHeight = KMManager.getBannerHeight(context); int oskHeight = KMManager.getKeyboardHeight(context); if (this.htmlBannerString != null && !this.htmlBannerString.isEmpty()) { - loadJavascript(KMString.format("setBannerHTML('%s')", this.htmlBannerString)); + setHTMLBanner(this.htmlBannerString); } loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); loadJavascript(KMString.format("setOskWidth(%d)", newConfig.screenWidthDp)); @@ -649,7 +649,8 @@ public String getHTMLBanner() { public void setHTMLBanner(String contents) { this.htmlBannerString = contents; - String jsString = KMString.format("setBannerHTML('%s')", contents); + String jsString = KMString.format("setBannerHTML(%s)", + JSONObject.quote(this.htmlBannerString)); loadJavascript(jsString); } From 37e9cf5f2d48092d32d54ae56d6d61c697667586 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 13:53:51 +0700 Subject: [PATCH 45/57] fix(android/app): Move banner asset folder --- .../main/assets/{svg => banner}/banner.html | 4 +- .../assets/{svg => banner}/keyman_banner.svg | 0 .../com/keyman/android/BannerController.java | 14 ++----- .../java/com/keyman/engine/KMManager.java | 41 +++++++++++-------- 4 files changed, 32 insertions(+), 27 deletions(-) rename android/KMAPro/kMAPro/src/main/assets/{svg => banner}/banner.html (80%) rename android/KMAPro/kMAPro/src/main/assets/{svg => banner}/keyman_banner.svg (100%) diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html similarity index 80% rename from android/KMAPro/kMAPro/src/main/assets/svg/banner.html rename to android/KMAPro/kMAPro/src/main/assets/banner/banner.html index 4ca3cf77894..1bca0296c9f 100644 --- a/android/KMAPro/kMAPro/src/main/assets/svg/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html @@ -1,7 +1,9 @@
- + + +
diff --git a/android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg b/android/KMAPro/kMAPro/src/main/assets/banner/keyman_banner.svg similarity index 100% rename from android/KMAPro/kMAPro/src/main/assets/svg/keyman_banner.svg rename to android/KMAPro/kMAPro/src/main/assets/banner/keyman_banner.svg diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index d5c48e970b4..dd67e049fe3 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -10,24 +10,18 @@ public class BannerController { // Paths relative to assets folder for banner themes - public static final String KM_BANNER_THEME_KEYMAN = "svg/banner.html"; - public static final String KM_BANNER_THEME_KEYMAN_SVG = "svg/keyman_banner.svg"; - + public static final String KM_BANNER_DIR = "banner"; + public static final String KM_BANNER_THEME_KEYMAN = KM_BANNER_DIR + "/banner.html"; public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { return; } + KMManager.copyHTMLBannerAssets(context, KM_BANNER_DIR); + // Always use Keyman banner theme String contents = FileUtils.readContents(context, KM_BANNER_THEME_KEYMAN); - - // If $BANNER string exists, replace with actual path - File bannerPath = new File(KMManager.getResourceRoot(), KM_BANNER_THEME_KEYMAN_SVG); - if (bannerPath.exists()) { - contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); - } - KMManager.setHTMLBanner(keyboardType, contents); KMManager.setBanner(keyboardType, KMManager.BannerType.HTML); KMManager.showBanner(true); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 8c280687c0b..df90f1d869d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -270,8 +270,6 @@ public String toString() { public static final String KMDefault_AssetPackages = "packages"; public static final String KMDefault_LexicalModelPackages = "models"; - public static final String KMDefault_AssetSVG = "svg"; - // Default Keyboard Info public static final String KMDefault_PackageID = "sil_euro_latin"; public static final String KMDefault_KeyboardID = "sil_euro_latin"; @@ -326,10 +324,6 @@ public static String getCloudDir() { return getResourceRoot() + KMDefault_UndefinedPackageID + File.separator; } - public static String getSVGDir() { - return getResourceRoot() + KMDefault_AssetSVG + File.separator; - } - public static FormFactor getFormFactor() { String device_type = appContext.getResources().getString(R.string.device_type); @@ -828,6 +822,31 @@ public static boolean hasInternetPermission(Context context) { return hasPermission(context, Manifest.permission.INTERNET); } + /** + * Copy HTML banner assets to the app + * @param context - The context + * @param path - Folder relative to assets/ containing the banner file. + * @return boolean - true if assets copied + */ + public static boolean copyHTMLBannerAssets(Context context, String path) { + AssetManager assetManager = context.getAssets(); + try { + File bannerDir = new File(getResourceRoot() + File.separator + path); + if (!bannerDir.exists()) { + bannerDir.mkdir(); + } + + String[] bannerFiles = assetManager.list(path); + for (String bannerFile : bannerFiles) { + copyAsset(context, bannerFile, path, true); + } + return true; + } catch (Exception e) { + KMLog.LogException(TAG, "copyHTMLBannerAssets() failed. Error: ", e); + } + return false; + } + private static void copyAssets(Context context) { AssetManager assetManager = context.getAssets(); try { @@ -848,16 +867,6 @@ private static void copyAssets(Context context) { copyAsset(context, KMDefault_KeyboardFont, "", true); copyAsset(context, KMFilename_JSPolyfill, "", true); - // SVG directory for html banner themes - File svgDir = new File(getSVGDir()); - if (!svgDir.exists()) { - svgDir.mkdir(); - } - String[] svgFiles = assetManager.list(KMDefault_AssetSVG); - for (String svgFile : svgFiles) { - copyAsset(context, svgFile, KMDefault_AssetSVG, true); - } - // Keyboard packages directory File packagesDir = new File(getPackagesDir()); if (!packagesDir.exists()) { From 37e9bbd881776152ee3f0368c2a6cec47d03c44f Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 13:58:41 +0700 Subject: [PATCH 46/57] fix(oem/fv/android): Move banner asset folder --- .../src/main/assets/{svg => banner}/banner.html | 4 +++- .../src/main/assets/{svg => banner}/red-logo.svg | 0 .../com/firstvoices/android/BannerController.java | 15 +++++---------- 3 files changed, 8 insertions(+), 11 deletions(-) rename oem/firstvoices/android/app/src/main/assets/{svg => banner}/banner.html (74%) rename oem/firstvoices/android/app/src/main/assets/{svg => banner}/red-logo.svg (100%) diff --git a/oem/firstvoices/android/app/src/main/assets/svg/banner.html b/oem/firstvoices/android/app/src/main/assets/banner/banner.html similarity index 74% rename from oem/firstvoices/android/app/src/main/assets/svg/banner.html rename to oem/firstvoices/android/app/src/main/assets/banner/banner.html index a320a45cbcc..56913fd545f 100644 --- a/oem/firstvoices/android/app/src/main/assets/svg/banner.html +++ b/oem/firstvoices/android/app/src/main/assets/banner/banner.html @@ -1,7 +1,9 @@
- + + +
diff --git a/oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg b/oem/firstvoices/android/app/src/main/assets/banner/red-logo.svg similarity index 100% rename from oem/firstvoices/android/app/src/main/assets/svg/red-logo.svg rename to oem/firstvoices/android/app/src/main/assets/banner/red-logo.svg diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java index a8703e7e7d9..034b4ac9c53 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java @@ -10,23 +10,18 @@ public class BannerController { // Paths relative to assets folder for banner themes - public static final String BANNER_THEME_FV = "svg/banner.html"; - public static final String BANNER_THEME_FV_SVG = "svg/red-logo.svg"; + public static final String FV_BANNER_DIR = "banner"; + public static final String FV_BANNER_THEME = FV_BANNER_DIR + "/banner.html"; public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { return; } - // Always use FirstVoices banner theme - String contents = FileUtils.readContents(context, BANNER_THEME_FV); - - // If $BANNER string exists, replace with actual path - File bannerPath = new File(KMManager.getResourceRoot(), BANNER_THEME_FV_SVG); - if (bannerPath.exists()) { - contents = contents.replace("$BANNER", bannerPath.getAbsolutePath()); - } + KMManager.copyHTMLBannerAssets(context, FV_BANNER_DIR); + // Always use FirstVoices banner theme + String contents = FileUtils.readContents(context, FV_BANNER_THEME); KMManager.setHTMLBanner(keyboardType, contents); KMManager.setBanner(keyboardType, KMManager.BannerType.HTML); KMManager.showBanner(true); From b41f04903652ad9b9672b5cd4a62c8e41a1c1122 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 14:47:57 +0700 Subject: [PATCH 47/57] chore(android): Tweak banner sizing --- android/KMAPro/kMAPro/src/main/assets/banner/banner.html | 4 ++-- .../android/app/src/main/assets/banner/banner.html | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/assets/banner/banner.html b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html index 1bca0296c9f..a132c6ff878 100644 --- a/android/KMAPro/kMAPro/src/main/assets/banner/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html @@ -1,9 +1,9 @@ -
+
- +
diff --git a/oem/firstvoices/android/app/src/main/assets/banner/banner.html b/oem/firstvoices/android/app/src/main/assets/banner/banner.html index 56913fd545f..3cff6254529 100644 --- a/oem/firstvoices/android/app/src/main/assets/banner/banner.html +++ b/oem/firstvoices/android/app/src/main/assets/banner/banner.html @@ -1,9 +1,9 @@ -
+
- +
From 2b34c641f33dca4300bb14cbcaba15994fc1a69f Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 15:10:18 +0700 Subject: [PATCH 48/57] Apply suggestions from code review Co-authored-by: Marc Durdin --- android/KMAPro/kMAPro/src/main/assets/banner/banner.html | 1 - .../src/main/java/com/keyman/android/BannerController.java | 2 +- android/KMEA/app/src/main/assets/android-host.js | 4 ---- .../android/app/src/main/assets/banner/banner.html | 1 - .../main/java/com/firstvoices/android/BannerController.java | 2 +- 5 files changed, 2 insertions(+), 8 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/assets/banner/banner.html b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html index a132c6ff878..41107f11424 100644 --- a/android/KMAPro/kMAPro/src/main/assets/banner/banner.html +++ b/android/KMAPro/kMAPro/src/main/assets/banner/banner.html @@ -1,6 +1,5 @@
- diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java index dd67e049fe3..f716faa9aab 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/BannerController.java @@ -11,7 +11,7 @@ public class BannerController { // Paths relative to assets folder for banner themes public static final String KM_BANNER_DIR = "banner"; - public static final String KM_BANNER_THEME_KEYMAN = KM_BANNER_DIR + "/banner.html"; + public static final String KM_BANNER_THEME_KEYMAN = "banner.html"; public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 317d3d14165..b513c96f1c9 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -304,10 +304,6 @@ function hideKeyboard() { window.location.hash = 'hideKeyboard' + fragmentToggle; } -function doResetContext() { - keyman.resetContext(); -} - function showKeyboard() { // Refresh KMW OSK keyman.refreshOskLayout(); diff --git a/oem/firstvoices/android/app/src/main/assets/banner/banner.html b/oem/firstvoices/android/app/src/main/assets/banner/banner.html index 3cff6254529..b0c30219cbe 100644 --- a/oem/firstvoices/android/app/src/main/assets/banner/banner.html +++ b/oem/firstvoices/android/app/src/main/assets/banner/banner.html @@ -1,6 +1,5 @@
- diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java index 034b4ac9c53..a0df15b2f1e 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/android/BannerController.java @@ -11,7 +11,7 @@ public class BannerController { // Paths relative to assets folder for banner themes public static final String FV_BANNER_DIR = "banner"; - public static final String FV_BANNER_THEME = FV_BANNER_DIR + "/banner.html"; + public static final String FV_BANNER_THEME = "banner.html"; public static void setHTMLBanner(Context context, KMManager.KeyboardType keyboardType) { if (keyboardType == KMManager.KeyboardType.KEYBOARD_TYPE_UNDEFINED) { From 4c453cc395fa5d5682f7a858b4e14a82aefd8d2a Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 15:24:13 +0700 Subject: [PATCH 49/57] chore(android): Move banner.html up to assets/ folder * They don't need to be copied into the resources folder on app load. --- android/KMAPro/kMAPro/src/main/assets/{banner => }/banner.html | 0 .../android/app/src/main/assets/{banner => }/banner.html | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename android/KMAPro/kMAPro/src/main/assets/{banner => }/banner.html (100%) rename oem/firstvoices/android/app/src/main/assets/{banner => }/banner.html (100%) diff --git a/android/KMAPro/kMAPro/src/main/assets/banner/banner.html b/android/KMAPro/kMAPro/src/main/assets/banner.html similarity index 100% rename from android/KMAPro/kMAPro/src/main/assets/banner/banner.html rename to android/KMAPro/kMAPro/src/main/assets/banner.html diff --git a/oem/firstvoices/android/app/src/main/assets/banner/banner.html b/oem/firstvoices/android/app/src/main/assets/banner.html similarity index 100% rename from oem/firstvoices/android/app/src/main/assets/banner/banner.html rename to oem/firstvoices/android/app/src/main/assets/banner.html From 97a8c68b8f749803f97aaf1c2c5de202fd4491a8 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Thu, 16 Nov 2023 16:27:01 +0700 Subject: [PATCH 50/57] cartfile updates from master due to merge error --- ios/Cartfile | 2 +- ios/Cartfile.resolved | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/ios/Cartfile b/ios/Cartfile index 8dec45e2010..57ec6de1e2b 100644 --- a/ios/Cartfile +++ b/ios/Cartfile @@ -1,5 +1,5 @@ github "weichsel/ZIPFoundation" ~> 0.9 -github "DaveWoodCom/XCGLogger" ~> 6.1.0 +github "keymanapp/dependency-XCGLogger" "master" github "devicekit/DeviceKit" ~> 5.0 github "ashleymills/Reachability.swift" github "getsentry/sentry-cocoa" ~> 8.7.0 diff --git a/ios/Cartfile.resolved b/ios/Cartfile.resolved index 1366ecd9ff6..5245b0dfa73 100644 --- a/ios/Cartfile.resolved +++ b/ios/Cartfile.resolved @@ -1,4 +1,5 @@ github "ashleymills/Reachability.swift" "v5.1.0" github "devicekit/DeviceKit" "5.1.0" -github "getsentry/sentry-cocoa" "6.2.1" +github "getsentry/sentry-cocoa" "8.15.2" +github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113" github "weichsel/ZIPFoundation" "0.9.17" From cb15b2ee6bd239592ec1337508142a7f9b6610bf Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 17 Nov 2023 08:24:16 +0700 Subject: [PATCH 51/57] cartfile update --- oem/firstvoices/ios/Cartfile | 2 +- oem/firstvoices/ios/Cartfile.resolved | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/oem/firstvoices/ios/Cartfile b/oem/firstvoices/ios/Cartfile index 8dec45e2010..57ec6de1e2b 100644 --- a/oem/firstvoices/ios/Cartfile +++ b/oem/firstvoices/ios/Cartfile @@ -1,5 +1,5 @@ github "weichsel/ZIPFoundation" ~> 0.9 -github "DaveWoodCom/XCGLogger" ~> 6.1.0 +github "keymanapp/dependency-XCGLogger" "master" github "devicekit/DeviceKit" ~> 5.0 github "ashleymills/Reachability.swift" github "getsentry/sentry-cocoa" ~> 8.7.0 diff --git a/oem/firstvoices/ios/Cartfile.resolved b/oem/firstvoices/ios/Cartfile.resolved index 1366ecd9ff6..5245b0dfa73 100644 --- a/oem/firstvoices/ios/Cartfile.resolved +++ b/oem/firstvoices/ios/Cartfile.resolved @@ -1,4 +1,5 @@ github "ashleymills/Reachability.swift" "v5.1.0" github "devicekit/DeviceKit" "5.1.0" -github "getsentry/sentry-cocoa" "6.2.1" +github "getsentry/sentry-cocoa" "8.15.2" +github "keymanapp/dependency-XCGLogger" "57a7b975dbb6fe4fe90cef3d1bc52b8adbd89113" github "weichsel/ZIPFoundation" "0.9.17" From d77df3942cef08b9e93a86d6c7a171d062bd9085 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 17 Nov 2023 10:19:57 +0700 Subject: [PATCH 52/57] fix(android): Refresh HTML banner when KeymanWeb reloads --- .../src/main/java/com/keyman/android/SystemKeyboard.java | 3 +++ .../main/java/com/firstvoices/keyboards/SystemKeyboard.java | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index 984d0595f73..cf919deddb4 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -97,6 +97,9 @@ public void onDestroy() { @Override public void onInitializeInterface() { super.onInitializeInterface(); + + // KeymanWeb reloaded, so we have to pass the banner again + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); } /** diff --git a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java index 8d8869a2260..0fb770c9b51 100644 --- a/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java +++ b/oem/firstvoices/android/app/src/main/java/com/firstvoices/keyboards/SystemKeyboard.java @@ -82,7 +82,10 @@ public void onDestroy() { * is called after creation and any configuration change. */ @Override public void onInitializeInterface() { - super.onInitializeInterface(); + super.onInitializeInterface(); + + // KeymanWeb reloaded, so we have to pass the banner again + BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); } /** Called by the framework when your view for creating input needs to From e322596b82b0ed7ee43cf53cfeaa014902e6428d Mon Sep 17 00:00:00 2001 From: sgschantz Date: Fri, 17 Nov 2023 15:06:09 +0700 Subject: [PATCH 53/57] fix unit tests --- ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift | 2 -- .../KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift b/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift index 7a6d4b45a1a..3945f9212ee 100644 --- a/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/ResourceUpdateTests.swift @@ -57,12 +57,10 @@ class ResourceUpdateTests: XCTestCase { wait(for: [queryExpectation], timeout: 5) - /* // Uses the XCTest 'attachment' system to retrieve the desired file. self.add(try TestUtils.EngineStateBundler.createBundle(withName: "khmer_angkor update base")) log.info("Bundle archived and attached to test's report.") - */ } func testCacheCurrent() { diff --git a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift index e15f266fc49..f50449d8bd4 100644 --- a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift +++ b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift @@ -29,7 +29,6 @@ extension TestUtils { return FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask)[0].appendingPathComponent("Preferences") } - /* static func createBundle(withName name: String) throws -> XCTAttachment { let tempDirectory = FileManager.default.temporaryDirectory let bundleConstructionURL = tempDirectory.appendingPathComponent("\(name).bundle") @@ -68,7 +67,7 @@ extension TestUtils { let archiveURL = bundleConstructionURL.appendingPathComponent("bundleArchive.zip") do { - let attachmentFile = try Archive(url: archiveURL, accessMode: .create) + _ = try Archive(url: archiveURL, accessMode: .create) log.info("archiveURL: \(archiveURL)") let attachment = XCTAttachment(contentsOfFile: archiveURL) attachment.lifetime = .keepAlways @@ -80,6 +79,5 @@ extension TestUtils { } } - */ } } From 4ebc1e287ac4429b7a016a0fe53362d322c077e8 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 17 Nov 2023 15:21:48 +0700 Subject: [PATCH 54/57] fix(android/engine): Fix typo --- android/KMEA/app/src/main/assets/android-host.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index b513c96f1c9..7930c76f11f 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -66,13 +66,13 @@ function showBanner(flag) { if (bannerHTMLContents != '') { bc.inactiveBanner = flag ? new bc.HTMLBanner(bannerHTMLContents) : null; } else { - bc.inactiveBanner = flag ? new bc.ImageBanner(bannerImgPath) : null; + bc.inactiveBanner = flag ? new bc.ImageBanner(bannerImagePath) : null; } } } function setBannerImage(path) { - bannerImgPath = path; + bannerImagePath = path; } // Set the HTML banner to use when predictive-text is not available From aad62bb4df9d252753ae752efb82c3e1b1317ad5 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 17 Nov 2023 13:02:13 -0500 Subject: [PATCH 55/57] auto: increment master version to 17.0.214 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 1e121ddb861..cb0e0a37eb5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 17.0.213 alpha 2023-11-17 + +* fix(linux): Fix packaging GHA (#10020) +* fix(android): Always display HTML banner when suggestions aren't available (#9696) + ## 17.0.212 alpha 2023-11-16 * chore(web): splits banner.ts into separate files per banner type (#9987) diff --git a/VERSION.md b/VERSION.md index 8bd573dbaff..28e0dea4f4e 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.213 \ No newline at end of file +17.0.214 \ No newline at end of file From 5cd958cff2f58edebdbd2e56a36f81c6b379aae4 Mon Sep 17 00:00:00 2001 From: sgschantz Date: Mon, 20 Nov 2023 09:36:26 +0700 Subject: [PATCH 56/57] fixed formatting and other items caught in review --- .../KeymanEngine/Classes/KeymanPackage.swift | 2 +- .../KeymanPackageTests.swift | 49 +++++++++---------- .../TestUtils/EngineStateBundler.swift | 6 +-- 3 files changed, 26 insertions(+), 31 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift index 5c2aa97ecf2..a13cef142c8 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanPackage.swift @@ -462,7 +462,7 @@ public class KeymanPackage { // it exists and is actually a directory, so remove every file it contains let fileArray = try FileManager.default.contentsOfDirectory(atPath: destination.path) try fileArray.forEach { file in - let fileUrl = destination.appendingPathComponent(file) + let fileUrl = destination.appendingPathComponent(file) try FileManager.default.removeItem(atPath: fileUrl.path) } } diff --git a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift index e291ea26bf8..baf61ee4872 100644 --- a/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift +++ b/ios/engine/KMEI/KeymanEngineTests/KeymanPackageTests.swift @@ -29,14 +29,13 @@ class KeymanPackageTests: XCTestCase { } } - func testKeyboardPackageExtraction() throws { + func testKeyboardPackage_extractWithoutKmpExtension_succeeds() throws { let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] - let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp.zip") + let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp") try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip) let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor") - // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. do { if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { // Run assertions on the package's kmp.info. @@ -65,9 +64,9 @@ class KeymanPackageTests: XCTestCase { do { // clear directory try KeymanPackage.clearDirectory(destination: destinationDirectory) - } catch { - XCTFail("error clearing the nonexistent directory \(error)") - } + } catch { + XCTFail("error clearing the nonexistent directory \(error)") + } } func testKeyboardPackage_clearEmptyDirectory_throwsNoError() throws { @@ -84,12 +83,12 @@ class KeymanPackageTests: XCTestCase { // clear directory try KeymanPackage.clearDirectory(destination: destinationDirectory) - } catch { - XCTFail("error clearing the empty directory \(error)") - } + } catch { + XCTFail("error clearing the empty directory \(error)") + } let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path) - XCTAssert(fileArray.count == 0, "directory still contains /(fileArray.count) items") + XCTAssert(fileArray.count == 0, "directory still contains \(fileArray.count) items") } func testKeyboardPackage_clearNonEmptyDirectory_directoryIsEmpty() throws { @@ -111,22 +110,21 @@ class KeymanPackageTests: XCTestCase { // clear directory try KeymanPackage.clearDirectory(destination: destinationDirectory) - } catch { - XCTFail("error clearing the empty directory \(error)") - } + } catch { + XCTFail("error clearing the empty directory \(error)") + } let fileArray = try FileManager.default.contentsOfDirectory(atPath: destinationDirectory.path) - XCTAssert(fileArray.count == 0, "directory still contains /(fileArray.count) items") + XCTAssert(fileArray.count == 0, "directory still contains \(fileArray.count) items") } func testKeyboardPackage_extractTwice_noDuplicateFileError() throws { let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] - let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp.zip") + let khmerPackageZip = cacheDirectory.appendingPathComponent("khmer_angkor.kmp") try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.Keyboards.khmerAngkorKMP, to: khmerPackageZip) let destinationFolderURL = cacheDirectory.appendingPathComponent("khmer_angkor") - // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. do { if let kmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { log.info("*** first unzip of \(kmp.id)") @@ -135,15 +133,15 @@ class KeymanPackageTests: XCTestCase { try KeymanPackage.clearDirectory(destination: destinationFolderURL) if let secondKmp = try KeymanPackage.extract(fileUrl: khmerPackageZip, destination: destinationFolderURL) { - log.info("*** second unzip of \(secondKmp.id)") - } else { - XCTAssert(false, "*** second unzip failed") - } - } catch { - XCTFail("unzip 2 failure with error \(error)") + log.info("*** second unzip of \(secondKmp.id)") + } else { + XCTAssert(false, "*** second unzip failed") } - } else { - XCTAssert(false, "*** first unzip failed") + } catch { + XCTFail("unzip 2 failure with error \(error)") + } + } else { + XCTAssert(false, "*** first unzip failed") } } catch { XCTFail("unzip 1 failure with error \(error)") @@ -152,12 +150,11 @@ class KeymanPackageTests: XCTestCase { func testLexicalModelPackageExtraction() throws { let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0] - let mtntZip = cacheDirectory.appendingPathComponent("nrc.en.mtnt.kmp.zip") + let mtntZip = cacheDirectory.appendingPathComponent("nrc.en.mtnt.kmp") try ResourceFileManager.shared.copyWithOverwrite(from: TestUtils.LexicalModels.mtntKMP, to: mtntZip) let destinationFolderURL = cacheDirectory.appendingPathComponent("nrc.en.mtnt.model") - // Requires that the source file is already .zip, not .kmp. It's a ZipUtils limitation. do { if let kmp = try KeymanPackage.extract(fileUrl: mtntZip, destination: destinationFolderURL) { // Run assertions on the package's kmp.info. diff --git a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift index f50449d8bd4..b8950dc9c2b 100644 --- a/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift +++ b/ios/engine/KMEI/KeymanEngineTests/TestUtils/EngineStateBundler.swift @@ -72,12 +72,10 @@ extension TestUtils { let attachment = XCTAttachment(contentsOfFile: archiveURL) attachment.lifetime = .keepAlways return attachment - } - catch let error { + } catch let error { print (error.localizedDescription) return XCTAttachment(string: error.localizedDescription) } - } - + } } } From 18ee82be414ef582c266ed9be988b992cbb2c27c Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Mon, 20 Nov 2023 13:03:06 -0500 Subject: [PATCH 57/57] auto: increment master version to 17.0.215 --- HISTORY.md | 4 ++++ VERSION.md | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index cb0e0a37eb5..8f7978032ef 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,9 @@ # Keyman Version History +## 17.0.214 alpha 2023-11-20 + +* fix(ios): fv: replace Zip framework to prevent crash on startup (#10018) + ## 17.0.213 alpha 2023-11-17 * fix(linux): Fix packaging GHA (#10020) diff --git a/VERSION.md b/VERSION.md index 28e0dea4f4e..87185aaedf7 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.214 \ No newline at end of file +17.0.215 \ No newline at end of file