diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java index 5bbf448433..5e3d41a89c 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/AdjustKeyboardHeightActivity.java @@ -97,20 +97,13 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { break; case MotionEvent.ACTION_MOVE: // Get the touch position relative to the screen bottom - // The keyboard top should align with the touch point int[] location = new int[2]; sampleKeyboard.getLocationOnScreen(location); int viewBottom = location[1] + sampleKeyboard.getHeight(); int touchY = (int) event.getRawY(); - // Calculate height: distance from touch point to bottom of screen - currentHeight = viewBottom - touchY; - - // Apply lower and upper bounds on currentHeight - int minKeyboardHeight = KMManager.getKeyboardHeightMin(context); - int maxKeyboardHeight = KMManager.getKeyboardHeightMax(context); - currentHeight = Math.max(minKeyboardHeight, currentHeight); - currentHeight = Math.min(maxKeyboardHeight, currentHeight); + // Calculate height with bounds checking + currentHeight = KMManager.calculateKeyboardHeightFromTouch(context, touchY, viewBottom); refreshSampleKeyboard(context); break; @@ -136,25 +129,58 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { * @param context */ private void refreshSampleKeyboard(Context context) { - layoutParams.height = currentHeight; - sampleKeyboard.setLayoutParams(layoutParams); + if (layoutParams != null && sampleKeyboard != null) { + layoutParams.height = currentHeight; + sampleKeyboard.requestLayout(); - // Update percentage display - String percentageText = KMManager.createKeyboardHeightString( + // Update percentage display based on current (unsaved) height + if (percentageDisplay != null) { + String percentageText = createLiveKeyboardHeightString(context); + percentageDisplay.setText(percentageText); + } + } + } + + /** + * Create a string showing keyboard height percentages for both orientations. + * Uses the current (unsaved) height for the active orientation and saved height for the other. + * @param context Context + * @return String in format "100% Portrait | 100% Landscape" + */ + private String createLiveKeyboardHeightString(Context context) { + int currentOrientation = KMManager.getOrientation(context); + return KMManager.createKeyboardHeightString( context, + currentHeight, + currentOrientation, getString(R.string.portrait), getString(R.string.landscape) ); - percentageDisplay.setText(percentageText); + } + + @Override + protected void onPause() { + super.onPause(); + // Ensure height is saved even if user switches tasks or navigates away abnormally + // This prevents the touch zone from being out of sync with the keyboard visual + if (currentHeight > 0) { + KMManager.applyKeyboardHeight(this, currentHeight); + } } @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); + // IMPORTANT: Apply current height BEFORE switching orientations + // This ensures the old orientation's height is saved before loading the new one + if (currentHeight > 0) { + KMManager.applyKeyboardHeight(this, currentHeight); + } + layoutParams = sampleKeyboard.getLayoutParams(); - // When the user rotates the device, restore currentHeight + // When the user rotates the device, restore currentHeight for the new orientation currentHeight = KMManager.getKeyboardHeight(this); refreshSampleKeyboard(this); } diff --git a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_keyboard_height.xml b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_keyboard_height.xml index eec110e46b..262f0ac30f 100644 --- a/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_keyboard_height.xml +++ b/android/KMAPro/kMAPro/src/main/res/layout/activity_adjust_keyboard_height.xml @@ -64,7 +64,7 @@ android:layout_height="wrap_content" android:gravity="center" android:textSize="14sp" - android:textColor="@color/keyman_blue" + android:textColor="?android:attr/textColorSecondary" android:layout_marginTop="8dp" android:layout_marginBottom="8dp" android:text="100% Portrait | 100% Landscape" @@ -83,7 +83,7 @@ android:clickable="true" android:focusable="true" android:contentDescription="@string/drag_the_keyboard" - app:layout_constraintTop_toBottomOf="@id/keyboard_height_percentage" + app:layout_constraintBottom_toTopOf="@id/sample_keyboard" app:layout_constraintStart_toStartOf="parent"> @@ -129,15 +129,7 @@ android:contentDescription="@string/adjust_keyboard_height" android:scaleType="fitXY" android:src="@drawable/blank_osk" - android:background="@drawable/dash_border" - android:layout_alignParentBottom="true" - android:paddingStart="@dimen/blank_osk_padding" - android:paddingEnd="@dimen/blank_osk_padding" - android:paddingTop="@dimen/blank_osk_padding" - android:paddingBottom="@dimen/blank_osk_padding" - app:layout_constraintTop_toBottomOf="@id/resize_handle" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintVertical_bias="1.0" /> + app:layout_constraintBottom_toBottomOf="parent" /> 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 ea9047f926..a891c19b39 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 @@ -2619,16 +2619,34 @@ public final class KMManager { } /** - * Get keyboard height as percentage of default for specified orientation + * Get keyboard height as percentage of default for specified orientation. + * + *

The percentage is calculated using Math.ceil() to round up to the nearest integer. + * This ensures consistency with {@link #createKeyboardHeightString(Context, String, String)} + * and prevents percentages from being displayed as 0%. + * + *

Example: If current height is 288px and default is 200px: + *

+   * percentage = ceil(288 * 100.0 / 200) = ceil(144.0) = 144%
+   * 
+ * * @param context Context - * @param orientation Configuration.ORIENTATION_PORTRAIT or ORIENTATION_LANDSCAPE - * @return Percentage (e.g., 120 for 120%) + * @param orientation Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE + * @return Percentage (e.g., 120 for 120%), minimum value is 100 + * @see #getKeyboardHeight(Context, int) + * @see #getDefaultKeyboardHeight(int) + * @see #createKeyboardHeightString(Context, String, String) */ public static int getKeyboardHeightPercentage(Context context, int orientation) { int currentHeight = getKeyboardHeight(context, orientation); int defaultHeight = getDefaultKeyboardHeight(orientation); if (defaultHeight == 0) return 100; - return (int) Math.round((currentHeight * 100.0) / defaultHeight); + // Use Math.ceil to match createKeyboardHeightString() calculation + int percent = (int) Math.ceil((currentHeight * 100.0) / defaultHeight); + if (percent == 0) { + percent = 100; + } + return percent; } /** @@ -2682,6 +2700,114 @@ public final class KMManager { return percentages; } + /** + * Create a string showing keyboard height percentages for both orientations. + * Uses a live (unsaved) height for the specified orientation and reads saved height for the other. + * This is useful for displaying real-time percentages during keyboard height adjustment. + * + *

The percentage is calculated using Math.ceil() to ensure consistency with + * {@link #createKeyboardHeightString(Context, String, String)} and + * {@link #getKeyboardHeightPercentage(Context, int)}. + * + *

Example usage during drag operation: + *

{@code
+   * // User is dragging to adjust portrait height
+   * int currentOrientation = KMManager.getOrientation(context);
+   * String display = KMManager.createKeyboardHeightString(
+   *     context,
+   *     currentHeight,  // Live height being adjusted
+   *     currentOrientation,
+   *     "Portrait",
+   *     "Landscape"
+   * );
+   * // Result: "145% Portrait | 100% Landscape"
+   * }
+ * + * @param context Context + * @param liveHeight The current keyboard height in pixels (not yet saved to SharedPreferences) + * @param liveOrientation The orientation for which liveHeight applies + * (Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE) + * @param portraitLabel Label for portrait orientation (e.g., "Portrait") + * @param landscapeLabel Label for landscape orientation (e.g., "Landscape") + * @return String in format "100% Portrait | 100% Landscape" + * @see #createKeyboardHeightString(Context, String, String) + * @see #getKeyboardHeightPercentage(Context, int) + * @see #calculateKeyboardHeightFromTouch(Context, int, int) + */ + public static String createKeyboardHeightString(Context context, int liveHeight, int liveOrientation, + String portraitLabel, String landscapeLabel) { + int portraitPercent = 100; + int landscapePercent = 100; + + // Calculate percentage for the live orientation + int liveDefaultHeight = getDefaultKeyboardHeight(liveOrientation); + if (liveDefaultHeight > 0 && liveHeight > 0) { + int livePercent = (int) Math.ceil((liveHeight * 100.0) / liveDefaultHeight); + if (livePercent == 0) { + livePercent = 100; + } + + if (liveOrientation == Configuration.ORIENTATION_PORTRAIT) { + portraitPercent = livePercent; + landscapePercent = getKeyboardHeightPercentage(context, Configuration.ORIENTATION_LANDSCAPE); + } else { + landscapePercent = livePercent; + portraitPercent = getKeyboardHeightPercentage(context, Configuration.ORIENTATION_PORTRAIT); + } + } else { + // Fallback: read both from saved preferences + portraitPercent = getKeyboardHeightPercentage(context, Configuration.ORIENTATION_PORTRAIT); + landscapePercent = getKeyboardHeightPercentage(context, Configuration.ORIENTATION_LANDSCAPE); + } + + return portraitPercent + "% " + portraitLabel + " | " + + landscapePercent + "% " + landscapeLabel; + } + + /** + * Calculate keyboard height from a touch Y coordinate, applying min/max bounds. + * This is used during interactive keyboard height adjustment (e.g., dragging a resize handle). + * + *

The height is calculated as the distance from the touch point to the bottom of the screen, + * then clamped to the valid range defined by {@link #getKeyboardHeightMin(Context)} and + * {@link #getKeyboardHeightMax(Context)}. + * + *

Example usage in touch listener: + *

{@code
+   * case MotionEvent.ACTION_MOVE:
+   *     int[] location = new int[2];
+   *     keyboardView.getLocationOnScreen(location);
+   *     int viewBottom = location[1] + keyboardView.getHeight();
+   *     int touchY = (int) event.getRawY();
+   *
+   *     int newHeight = KMManager.calculateKeyboardHeightFromTouch(
+   *         context, touchY, viewBottom);
+   *     // newHeight is guaranteed to be within min/max bounds
+   *     break;
+   * }
+ * + * @param context Context + * @param touchY The Y coordinate of the touch event in screen coordinates (from event.getRawY()) + * @param viewBottom The bottom Y coordinate of the keyboard view in screen coordinates + * (typically: view.getLocationOnScreen()[1] + view.getHeight()) + * @return The calculated keyboard height in pixels, clamped to valid range + * @see #getKeyboardHeightMin(Context) + * @see #getKeyboardHeightMax(Context) + * @see #applyKeyboardHeight(Context, int) + */ + public static int calculateKeyboardHeightFromTouch(Context context, int touchY, int viewBottom) { + // Calculate height: distance from touch point to bottom of screen + int height = viewBottom - touchY; + + // Apply lower and upper bounds + int minKeyboardHeight = getKeyboardHeightMin(context); + int maxKeyboardHeight = getKeyboardHeightMax(context); + height = Math.max(minKeyboardHeight, height); + height = Math.min(maxKeyboardHeight, height); + + return height; + } + /** * Returns the preference key for tracking pending keyboard height updates. * This allows separate tracking for each keyboard type (in-app/system) and orientation. diff --git a/android/docs/engine/KMManager/calculateKeyboardHeightFromTouch.md b/android/docs/engine/KMManager/calculateKeyboardHeightFromTouch.md new file mode 100644 index 0000000000..edc0c0b3e0 --- /dev/null +++ b/android/docs/engine/KMManager/calculateKeyboardHeightFromTouch.md @@ -0,0 +1,74 @@ +--- +title: KMManager.calculateKeyboardHeightFromTouch() +--- + +## Summary +The **calculateKeyboardHeightFromTouch()** method calculates the keyboard height from a touch Y coordinate, automatically applying minimum and maximum bounds. + +## Syntax +```java +KMManager.calculateKeyboardHeightFromTouch(Context context, int touchY, int viewBottom) +``` + +### Parameters + +`context` +: The context + +`touchY` +: The Y coordinate of the touch event in screen coordinates (from event.getRawY()) + +`viewBottom` +: The bottom Y coordinate of the keyboard view in screen coordinates (typically: view.getLocationOnScreen()[1] + view.getHeight()) + +### Returns +Returns an `int` representing the calculated keyboard height in pixels, clamped to the valid range defined by `getKeyboardHeightMin()` and `getKeyboardHeightMax()`. + +## Description +Use this method during interactive keyboard height adjustment (e.g., when implementing a draggable resize handle). The height is calculated as the distance from the touch point to the bottom of the screen, then automatically clamped to ensure it stays within valid bounds. + +This method encapsulates the calculation logic and bounds checking, ensuring consistency across different implementations of keyboard height adjustment UI. + +## Examples + +### Example: Using `calculateKeyboardHeightFromTouch()` in a touch listener +The following script illustrates implementing a draggable keyboard resize handle: + +```java + View.OnTouchListener touchListener = new View.OnTouchListener() { + @Override + public boolean onTouch(View view, MotionEvent event) { + switch (event.getAction()) { + case MotionEvent.ACTION_MOVE: + // Get the keyboard view's bottom position on screen + int[] location = new int[2]; + keyboardView.getLocationOnScreen(location); + int viewBottom = location[1] + keyboardView.getHeight(); + int touchY = (int) event.getRawY(); + + // Calculate new height with automatic bounds checking + int newHeight = KMManager.calculateKeyboardHeightFromTouch( + context, touchY, viewBottom); + // newHeight is guaranteed to be within min/max bounds + + // Update the keyboard preview + updateKeyboardPreview(newHeight); + break; + case MotionEvent.ACTION_UP: + // Save the final height + KMManager.applyKeyboardHeight(context, newHeight); + break; + } + return true; + } + }; + + // Attach listener to resize handle or keyboard view + resizeHandle.setOnTouchListener(touchListener); +``` + +## See also +* [applyKeyboardHeight()](applyKeyboardHeight) +* [getKeyboardHeightMin()](getKeyboardHeightMin) +* [getKeyboardHeightMax()](getKeyboardHeightMax) +* [createKeyboardHeightString()](createKeyboardHeightString) diff --git a/android/docs/engine/KMManager/createKeyboardHeightString.md b/android/docs/engine/KMManager/createKeyboardHeightString.md new file mode 100644 index 0000000000..0521450035 --- /dev/null +++ b/android/docs/engine/KMManager/createKeyboardHeightString.md @@ -0,0 +1,79 @@ +--- +title: KMManager.createKeyboardHeightString() +--- + +## Summary +The **createKeyboardHeightString()** method creates a formatted string showing keyboard height percentages for both portrait and landscape orientations. + +## Syntax +```java +KMManager.createKeyboardHeightString(Context context, String portraitLabel, String landscapeLabel) +``` +or + +```java +KMManager.createKeyboardHeightString(Context context, int liveHeight, int liveOrientation, String portraitLabel, String landscapeLabel) +``` + +### Parameters + +`context` +: The context + +`portraitLabel` +: Label for portrait orientation (e.g., "Portrait") + +`landscapeLabel` +: Label for landscape orientation (e.g., "Landscape") + +`liveHeight` _(Optional)_ +: The current keyboard height in pixels (not yet saved to SharedPreferences). Used for displaying real-time percentages during keyboard height adjustment. + +`liveOrientation` _(Optional)_ +: The orientation for which `liveHeight` applies (Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE) + +### Returns +Returns a `String` in the format "100% Portrait | 100% Landscape" + +## Description +Use this method to display the keyboard height percentages for both orientations. The first variant reads both values from saved preferences, while the second variant allows you to provide a live (unsaved) height for one orientation, useful during interactive height adjustment. + +The percentage is calculated using Math.ceil() to ensure consistency with `getKeyboardHeightPercentage()`. + +## Examples + +### Example 1: Display saved percentages +The following script illustrates displaying saved keyboard heights: + +```java + // Display the saved keyboard height percentages + String heightText = KMManager.createKeyboardHeightString( + this, + "Portrait", + "Landscape" + ); + // Result: "100% Portrait | 100% Landscape" + textView.setText(heightText); +``` + +### Example 2: Display live percentage during drag +The following script shows how to display real-time percentages while the user adjusts keyboard height: + +```java + // User is dragging to adjust portrait height + int currentOrientation = KMManager.getOrientation(this); + String display = KMManager.createKeyboardHeightString( + this, + currentHeight, // Live height being adjusted + currentOrientation, + "Portrait", + "Landscape" + ); + // Result: "145% Portrait | 100% Landscape" + percentageDisplay.setText(display); +``` + +## See also +* [getKeyboardHeightPercentage()](getKeyboardHeightPercentage) +* [applyKeyboardHeight()](applyKeyboardHeight) +* [calculateKeyboardHeightFromTouch()](calculateKeyboardHeightFromTouch) diff --git a/android/docs/engine/KMManager/getKeyboardHeightPercentage.md b/android/docs/engine/KMManager/getKeyboardHeightPercentage.md new file mode 100644 index 0000000000..08a131288f --- /dev/null +++ b/android/docs/engine/KMManager/getKeyboardHeightPercentage.md @@ -0,0 +1,64 @@ +--- +title: KMManager.getKeyboardHeightPercentage() +--- + +## Summary +The **getKeyboardHeightPercentage()** method returns the keyboard height as a percentage of the default height for the current or specified orientation. + +## Syntax +```java +KMManager.getKeyboardHeightPercentage(Context context) +``` +or + +```java +KMManager.getKeyboardHeightPercentage(Context context, int orientation) +``` + +### Parameters + +`context` +: The context + +`orientation` _(Optional)_ +: Accepts a [screen orientation](https://developer.android.com/training/multiscreen/screensizes#TaskUseOriQuali) value (Configuration.ORIENTATION_PORTRAIT or Configuration.ORIENTATION_LANDSCAPE). If not specified, uses the current device orientation. + +### Returns +Returns an `int` representing the keyboard height as a percentage (e.g., 120 for 120%). The minimum return value is 100. + +## Description +Use this method to determine how much the user has adjusted the keyboard height from its default size. The percentage is calculated using Math.ceil() to round up to the nearest integer, ensuring consistency with `createKeyboardHeightString()`. + +For example, if the current height is 288px and the default height is 200px: +``` +percentage = ceil(288 * 100.0 / 200) = ceil(144.0) = 144% +``` + +## Examples + +### Example: Using `getKeyboardHeightPercentage()` +The following script illustrates the use of `getKeyboardHeightPercentage()`: + +```java + // Get the current keyboard height percentage + int percentage = KMManager.getKeyboardHeightPercentage(this); + Log.d("Keyboard", "Current height is " + percentage + "% of default"); +``` + +or + +```java + import android.content.res.Configuration; + ... + // Get the keyboard height percentage for landscape orientation + int landscapePercentage = KMManager.getKeyboardHeightPercentage( + this, + Configuration.ORIENTATION_LANDSCAPE + ); +``` + +## See also +* [getKeyboardHeight()](getKeyboardHeight) +* [getDefaultKeyboardHeight()](getDefaultKeyboardHeight) +* [applyKeyboardHeight()](applyKeyboardHeight) +* [createKeyboardHeightString()](createKeyboardHeightString) diff --git a/android/docs/engine/KMManager/index.md b/android/docs/engine/KMManager/index.md index 0f9d9c0fba..e2ad88be58 100644 --- a/android/docs/engine/KMManager/index.md +++ b/android/docs/engine/KMManager/index.md @@ -44,6 +44,9 @@ The KMManager is the core class which provides most of the methods and constants [`applyKeyboardHeight()`](applyKeyboardHeight) : sets the height of keyboard frame +[`calculateKeyboardHeightFromTouch()`](calculateKeyboardHeightFromTouch) +: calculates the keyboard height from a touch Y coordinate, applying min/max bounds + [`canAddNewKeyboard()`](canAddNewKeyboard) : returns whether adding a new keyboard is enabled, like in the keyboard picker menu @@ -59,6 +62,9 @@ The KMManager is the core class which provides most of the methods and constants [`createInputView()`](createInputView) : creates the input view to be used in InputMethodService +[`createKeyboardHeightString()`](createKeyboardHeightString) +: creates a formatted string showing keyboard height percentages for both orientations + [`deregisterLexicalModel()`](deregisterLexicalModel) : deregisters the specified lexical model from the LMLayer so it isn't used @@ -107,6 +113,9 @@ The KMManager is the core class which provides most of the methods and constants [`getKeyboardHeightMin()`](getKeyboardHeightMin) : returns the minimum allowed height of the keyboard frame +[`getKeyboardHeightPercentage()`](getKeyboardHeightPercentage) +: returns the keyboard height as a percentage of the default height + [`getKeyboardIndex()`](getKeyboardIndex) : returns index number of the specified keyboard in keyboards list