From 4273140149904ec500c7d7bbe9c72ec679815839 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Tue, 19 Sep 2023 14:42:42 +0700 Subject: [PATCH 01/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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/46] 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 35a37bc033c305f19f8b2fa2a885a317b06780fa Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Thu, 16 Nov 2023 03:21:04 +0700 Subject: [PATCH 34/46] 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 35/46] 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 36/46] 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 37/46] 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 38/46] 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 39/46] 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 40/46] 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 41/46] 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 42/46] 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 43/46] 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 44/46] 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 d77df3942cef08b9e93a86d6c7a171d062bd9085 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 17 Nov 2023 10:19:57 +0700 Subject: [PATCH 45/46] 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 4ebc1e287ac4429b7a016a0fe53362d322c077e8 Mon Sep 17 00:00:00 2001 From: Darcy Wong Date: Fri, 17 Nov 2023 15:21:48 +0700 Subject: [PATCH 46/46] 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