diff --git a/.github/workflows/README.md b/.github/workflows/README.md new file mode 100644 index 0000000000..0c0420c443 --- /dev/null +++ b/.github/workflows/README.md @@ -0,0 +1,123 @@ +# api-verification.yml +TODO + +# auto-merge-keyman-server-pr.yml +TODO + +# build-test-publish-docker.yml +TODO + +# cleanup-ghcr.yml +TODO + +# close-linked-issues-for-merged-prs.yml +TODO + +# core-arm64-windows-test.yml: Test Keyman Core on Windows/ARM64 + +This action allows us to test Keyman Core for Windows on ARM64 architecture with +a GitHub agent, as we do not have any TeamCity Windows build agents with ARM64 +arch. + +It will normally be triggered as part of a test build from the +resources/teamcity/triggers/trigger-test-builds.sh script. + +You can manually trigger a core-arm64-windows-test action on GitHub on your fork +of keymanapp/keyman. This is normally only needed to test changes to the action +itself: + +- push the changes to your fork of keymanapp/keyman: `git push myfork HEAD:master` +- make sure you have GHA enabled in the settings of your fork +- seet `GITHUB_TOKEN=` +- trigger a build with: + + ```bash + curl --write-out '\n' --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: token $GITHUB_TOKEN" \ + --data "{ \"event_type\": \"core-arm64-windows-test: master\", \ + \"client_payload\": { \ + \"buildSha\": \"$(git rev-parse refs/heads/master)\", \ + \"branch\": \"master\", \ + \"user\": \"${USER}\", \ + \"isTestBuild\": \"true\", \ + \"force\": \"true\"}" \ + https://api.github.com/repos//keyman/dispatches + ``` + + To trigger a build for PR #1234 in a branch `my-pr-branch` this would look similar + to this: + + ```bash + curl --write-out '\n' --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: token $GITHUB_TOKEN" \ + --data "{ \"event_type\": \"core-arm64-windows-test: PR #1234\", \ + \"client_payload\": { \ + \"buildSha\": \"$(git rev-parse refs/heads/my-pr-branch)\", \ + \"branch\": \"my-pr-branch\", \ + \"user\": \"${USER}\", \ + \"isTestBuild\": \"true\", \ + \"force\": \"true\"}" \ + https://api.github.com/repos//keyman/dispatches + ``` + +.... + +# crowdin.yml +TODO + +# deb-packaging.yml: Ubuntu Packaging GitHub Action + +You can manually trigger a deb-packaging action on GitHub on your fork +of keymanapp/keyman. This is normally only needed to test changes to the action +itself: + +- push the changes to your fork of keymanapp/keyman: `git push myfork HEAD:master` +- make sure you have GHA enabled in the settings of your fork +- seet `GITHUB_TOKEN=` +- trigger a build with: + + ```bash + curl --write-out '\n' --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: token $GITHUB_TOKEN" \ + --data "{ \"event_type\": \"deb-pr-packaging: master\", \ + \"client_payload\": { \ + \"buildSha\": \"$(git rev-parse refs/heads/master)\", \ + \"branch\": \"master\", \ + \"baseBranch\": \"master\", \ + \"baseRef\": \"$(git rev-parse refs/heads/master^)\", \ + \"user\": \"${USER}\", \ + \"isTestBuild\": \"true\", \ + \"force\": \"true\"}" \ + https://api.github.com/repos//keyman/dispatches + ``` + + To trigger a build for PR #1234 in a branch `pr-1234` this would look similar + to this: + + ```bash + curl --write-out '\n' --request POST \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: token $GITHUB_TOKEN" \ + --data "{ \"event_type\": \"deb-pr-packaging: PR #1234\", \ + \"client_payload\": { \ + \"buildSha\": \"$(git rev-parse refs/heads/pr-1234)\", \ + \"branch\": \"pr-1234\", \ + \"baseBranch\": \"master\", \ + \"baseRef\": \"$(git rev-parse refs/heads/master)\", \ + \"user\": \"${USER}\", \ + \"isTestBuild\": \"true\", \ + \"force\": \"true\"}" \ + https://api.github.com/repos//keyman/dispatches + ``` + +# labeler.yml +TODO + +# npm-publish.yml +TODO + +# pr-build-status.yml +TODO diff --git a/.github/workflows/core-arm64-windows-test.yml b/.github/workflows/core-arm64-windows-test.yml new file mode 100644 index 0000000000..237e60d4ef --- /dev/null +++ b/.github/workflows/core-arm64-windows-test.yml @@ -0,0 +1,167 @@ +# +# This workflow tests Keyman Core on ARM64/Windows; we cannot use our TeamCity +# agents because we have no ARM64 Windows agents. +# +# It is triggered by a repository_dispatch event with the type +# `core-arm64-windows-test:*`. +# + +name: "Keyman Core - ARM64/Windows test" +run-name: "Keyman Core - ARM64/Windows test - ${{ github.event.client_payload.branch }} by @${{ github.event.client_payload.user }}" +on: + repository_dispatch: + types: ['core-arm64-windows-test:*'] + +# Inputs: +# +# buildSha: The SHA of the commit to build, e.g. of the branch or +# refs/pull/1234/head for PR +# isTestBuild: true for PR and test builds, false for release builds +# force: true to run this action on a fork of keymanapp/keyman +# +# Descriptive inputs: +# +# branch: Name of the branch to build, generally, `master`, `beta`, +# `stable-x.y`, or for a PR `#1234`. Used only in the action +# title +# user: The user that triggered the build or created the PR. Used +# only in the action title + +env: + GH_TOKEN: ${{ github.token }} + STATUS_CONTEXT: 'Keyman Core - ARM64 test' + _builder_timings: false + +jobs: + core-arm64-windows-test: + name: Keyman Core ARM64/Windows test + if: github.repository == 'keymanapp/keyman' || github.event.client_payload.force + runs-on: windows-11-arm + + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2 + with: + ref: '${{ github.event.client_payload.buildSha }}' + + - name: Set pending status on PR builds + id: set_status + if: github.event.client_payload.isTestBuild == 'true' + shell: bash + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + repos/$GITHUB_REPOSITORY/statuses/${{ github.event.client_payload.buildSha }} \ + -f state='pending' \ + -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + -f description='Keyman Core - ARM64 test started' \ + -f context="$STATUS_CONTEXT" + + - name: Report initial npm/node versions + run: | + echo "Initial npm/node versions" + npm -v + node -v + + - uses: actions/setup-python@v6 + with: + python-version: '3.13' + + - uses: actions/setup-node@v6 + with: + # This needs to be kept in sync with minimum-versions.sh + node-version: '20.19' + # As of Dec 2025, ngrok not yet supported on ARM Windows, + # google-closure-compiler (and others?) does not support arm64 yet, so + # use the x64 version of node. This is needed to avoid `npm ci` failure, + # even though we don't use ngrok directly; google-closure-compiler is + # used by emscripten + architecture: x64 + + - name: Report on current environment + shell: bash + run: | + echo "-- Environment --" + echo "pythonLocation=$pythonLocation" + echo "-- Current npm/node versions --" + npm -v + node -v + + - name: Setup environment + shell: bash + run: | + # As of Dec 2025, Emscripten does not support Win11 ARM natively + echo "EMSDK_ARCH=x86_64" >> $GITHUB_ENV + echo "EMSDK_KEEP_DOWNLOADS=1" >> $GITHUB_ENV + echo "KEYMAN_USE_EMSDK=1" >> $GITHUB_ENV + + echo "PATH=$PATH:$pythonLocation/Scripts" >> $GITHUB_ENV + + echo "KEYMAN_CI_SKIP_NVM=1" >> $GITHUB_ENV + + + - name: Install emscripten + shell: bash + run: | + source resources/build/minimum-versions.inc.sh + mkdir ../emsdk + pushd ../emsdk + git clone https://github.com/emscripten-core/emsdk.git + cd emsdk + ./emsdk install "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" + ./emsdk activate "${KEYMAN_MIN_VERSION_EMSCRIPTEN}" + cd upstream/emscripten + npm install + echo "EMSCRIPTEN_BASE=$(pwd)" >> $GITHUB_ENV + popd + + - name: Install meson + shell: bash + run: | + pip3 install --user meson + + - name: Run test + shell: bash + run: | + # Run the test + ./core/build.sh configure,build,test:arm64 + + set_status: + name: Set result status on PR builds + needs: [core-arm64-windows-test] + runs-on: windows-11-arm + if: ${{ always() && github.event.client_payload.isTestBuild == 'true' }} + steps: + - name: Set success + if: needs.core-arm64-windows-test.result == 'success' + shell: bash + run: | + echo "RESULT=success" >> $GITHUB_ENV + echo "MSG=Keyman Core ARM64 test succeeded" >> $GITHUB_ENV + + - name: Set cancelled + if: needs.core-arm64-windows-test.result == 'cancelled' + shell: bash + run: | + echo "RESULT=error" >> $GITHUB_ENV + echo "MSG=Keyman Core ARM64 test cancelled" >> $GITHUB_ENV + + - name: Set failure + if: needs.core-arm64-windows-test.result == 'failure' + shell: bash + run: | + echo "RESULT=failure" >> $GITHUB_ENV + echo "MSG=Keyman Core ARM64 test failed" >> $GITHUB_ENV + + - name: Set final status + shell: bash + run: | + gh api \ + --method POST \ + -H "Accept: application/vnd.github+json" \ + repos/$GITHUB_REPOSITORY/statuses/${{ github.event.client_payload.buildSha }} \ + -f state="$RESULT" \ + -f target_url="$GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID" \ + -f description="$MSG" \ + -f context="$STATUS_CONTEXT" diff --git a/.github/workflows/crowdin.yml b/.github/workflows/crowdin.yml index d5a25aacb6..7503d51d21 100644 --- a/.github/workflows/crowdin.yml +++ b/.github/workflows/crowdin.yml @@ -16,7 +16,7 @@ jobs: uses: actions/checkout@v4 - name: crowdin action - uses: crowdin/github-action@2.7.0 + uses: crowdin/github-action@v2.7.0 with: upload_sources: true diff --git a/.github/workflows/deb-packaging.README.md b/.github/workflows/deb-packaging.README.md deleted file mode 100644 index 662526a875..0000000000 --- a/.github/workflows/deb-packaging.README.md +++ /dev/null @@ -1,43 +0,0 @@ -# Ubuntu Packaging GitHub Action - -You can manually trigger a deb-packaging action on GitHub, e.g. to test changes: - -- push the changes to your fork of keymanapp/keyman: git push myfork HEAD:master -- make sure you have GHA enabled in the settings of your fork and created an - access token on GitHub -- trigger a build with: - - ```bash - curl --write-out '\n' --request POST \ - --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ - --data "{ \"event_type\": \"deb-pr-packaging: master\", \ - \"client_payload\": { \ - \"buildSha\": \"$(git rev-parse refs/heads/master)\", \ - \"branch\": \"master\", \ - \"baseBranch\": \"master\", - \"baseRef\": \"$(git rev-parse refs/heads/master^)\", - \"user\": \"${USER}\", - \"isTestBuild\": \"true\", - \"force\": \"true\"}" \ - https://api.github.com/repos//keyman/dispatches - ``` - - To trigger a build for PR #1234 in a branch `pr-1234` this would look similar - to this: - - ```bash - curl --write-out '\n' --request POST \ - --header "Accept: application/vnd.github+json" \ - --header "Authorization: token $GITHUB_TOKEN" \ - --data "{ \"event_type\": \"deb-pr-packaging: PR #1234\", \ - \"client_payload\": { \ - \"buildSha\": \"$(git rev-parse refs/heads/pr-1234)\", \ - \"branch\": \"pr-1234\", \ - \"baseBranch\": \"master\", - \"baseRef\": \"$(git rev-parse refs/heads/master)\", - \"user\": \"${USER}\", - \"isTestBuild\": \"true\", - \"force\": \"true\"}" \ - https://api.github.com/repos//keyman/dispatches - ``` diff --git a/.gitignore b/.gitignore index 97c28ac83b..7b87219588 100644 --- a/.gitignore +++ b/.gitignore @@ -103,8 +103,8 @@ /windows/src/engine/keyman32/*.cod /windows/src/engine/keyman32/*.pdb -# /engine/keymanx64/ -/windows/src/engine/keymanx64/x64 +# /engine/keymanhp/ +/windows/src/engine/keymanhp/x64 # /engine/kmcomapi/ /windows/src/engine/kmcomapi/kmcomapi.tlb diff --git a/HISTORY.md b/HISTORY.md index 328e69d84c..b20b54709b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,101 @@ # Keyman Version History +## 19.0.183 alpha 2025-12-22 + +* maint(core): remove leading slash on gh api call parameter (#15381) +* maint(core): fix test runner runs-on (#15382) + +## 19.0.182 alpha 2025-12-22 + +* maint(common): Fix crowdin sync version to v2.7.0 (#15373) +* chore(linux): Update keyman-config.pot strings (#15366) +* chore(windows): show architecture in support texteditor (#15378) + +## 19.0.181 alpha 2025-12-20 + +* fix(developer): clear message when selecting custom layer name in touch layout editor (#15371) +* fix(developer): prevent duplicate layer name in Layer Properties dialog (#15372) + +## 19.0.180 alpha 2025-12-19 + +* fix(mac): ensure retry for mac disk detach actually repeats (#15330) +* fix(developer): handle Enter key in dialogs in Touch Layout Editor (#15338) +* chore(windows): fix typo in error message (#15362) +* chore(common): Update crowdin strings for PT (#15368) + +## 19.0.179 alpha 2025-12-18 + +* fix(linux): fix persisting options (#15364) + +## 19.0.178 alpha 2025-12-17 + +* chore(windows): correct kbda1 to kbdth0 in comment in unit test (#15351) + +## 19.0.177 alpha 2025-12-16 + +* feat(windows): report a sentry event when low level hook is reinstalled (#15335) +* chore(linux): add manual test for localization (#15356) +* feat(developer): show keyboard version in web debugger (#15339) + +## 19.0.176 alpha 2025-12-13 + +* chore(windows): remove unused variable (#15334) + +## 19.0.175 alpha 2025-12-12 + +* fix(windows): use Thai Kedmanee instead of Arabic 101 in RightAltEmulationCheck.tests.cpp (#15346) + +## 19.0.174 alpha 2025-12-10 + +* chore(windows): simple bisect utility for checking setup-redist.exe file size (#15322) + +## 19.0.173 alpha 2025-12-09 + +* docs(developer): Clarify keystrokes in Step-1 of tutorial (#15326) +* feat(android): Add Grabbable Bar and settings status for keyboard resizing (#15267) +* chore(linux): Update debian changelog (#15282) +* docs(web): fix typo in osk documentation (#15313) + +## 19.0.172 alpha 2025-12-08 + +* docs(web): add links to next/previous page to web guide (#15311) +* fix(developer): verify project file loaded successfully in analyze (#15318) + +## 19.0.171 alpha 2025-12-08 + +* maint(resources): verify order of builder_describe_internal_dependency (#15257) +* maint(common): use `builder_launch` for child builds (#15258) +* change(windows): Revert "change(windows): include map2pdb generation for relevant Delphi projects" (#15276) +* change(windows): add shortcuts to 'Update Available' dialog (#15280) +* maint(resources): roll back builder_launch for trigger-release-builds.sh (#15316) + +## 19.0.170 alpha 2025-12-03 + +* chore(deps-dev): bump js-yaml from 4.1.0 to 4.1.1 in /resources/build/pr-build-status (#15173) +* chore(deps-dev): bump glob from 10.4.5 to 10.5.0 in /resources/build/pr-build-status (#15184) +* chore(deps): bump js-yaml and @microsoft/api-documenter (#15185) +* chore(deps): bump glob from 10.4.5 to 10.5.0 in /developer/src/server/src/win32/trayicon/addon-src (#15186) +* docs(developer): add/update API docs (#15275) + +## 19.0.169 alpha 2025-12-02 + +* maint(core): verify that keyman_core_ldml.h is in sync (#15256) +* chore(common): Update FirstVoices keyboard versions in keyboards.csv (#15261) + +## 19.0.168 alpha 2025-12-01 + +* chore(android): add intent to broadcast font name (#15193) +* docs(android): Document Keyman API for keyboard font change (#15247) +* docs(ios): Use /cdn/dev assets for Engine guides (#15253) +* fix(developer): handle errors in .keyman-touch-layout file when loading (#15243) +* fix(android): address keyboard height vs touch height race after keyboard height preference change (also min max heights + docs) (#13663) + +## 19.0.167 alpha 2025-11-28 + +* change(windows): add unit testing to keyman32 build (#15244) +* refactor(windows): merge syskbdnt.cpp and syskbdnt64.cpp into RightAltEmulationCheck.cpp (#15245) +* chore(windows): add wow64kbd support app for researching WOW64 reqs (#15246) + ## 19.0.166 alpha 2025-11-26 * chore(android): Clarify PR title for #15136 (#15224) @@ -1012,6 +1108,16 @@ * refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168) * chore: increment to alpha 19.0 (#13187) +## 18.0.245 stable 2025-12-03 + +* chore(windows): VS2022 patch for stable-18.0 (#15081) +* maint(resources): use VERSION_ENVIRONMENT var for v18 (#15068) +* chore(linux): Update debian changelog (#14964) +* fix(windows): add check a fix for registry datatypes (#15175) +* chore(linux): Update debian changelog (#15132) +* feat(windows): update wix to 3.14.1 (#15195) +* fix(windows): reinstall low level keyboard hook if it gets removed (#15219) + ## 18.0.244 stable 2025-10-31 * fix(linux): use `ibus` command to restart ibus (#14975) diff --git a/VERSION.md b/VERSION.md index 492d1a0d57..8c493bc4d8 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -19.0.167 \ No newline at end of file +19.0.184 \ 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 180da002c9..97f8ab7d38 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 @@ -35,6 +35,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.widget.FrameLayout; +import android.content.Intent; import io.sentry.android.core.SentryAndroid; import io.sentry.Sentry; @@ -140,6 +141,22 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); } + private void sendCurrentFontName() { + Keyboard keyboardInfo = KMManager.getCurrentKeyboardInfo(this); + if (keyboardInfo != null) { + String fontName = keyboardInfo.getFont(); + + Intent intent; + if (BuildConfig.DEBUG) { + intent = new Intent("com.tavultesoft.kmapro.debug.keyboard_changed"); + } else { + intent = new Intent("com.tavultesoft.kmapro.keyboard_changed"); + } + intent.putExtra("fontName", fontName); + sendBroadcast(intent); + } + } + /** * This is the main point where we do our initialization of the input method * to begin operating on an application. At this point we have been @@ -198,6 +215,10 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven if (KMManager.isNumericField(inputType)) { KMManager.setNumericLayer(KeyboardType.KEYBOARD_TYPE_SYSTEM); } + + if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)) { + sendCurrentFontName(); + } } @Override @@ -268,6 +289,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven // Refresh banner theme BannerController.setHTMLBanner(this, KeyboardType.KEYBOARD_TYPE_SYSTEM); KMManager.showSystemKeyboard(); + sendCurrentFontName(); } @Override 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 cc23b2d2ed..4bc419ced6 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 @@ -22,32 +22,28 @@ import com.keyman.engine.BaseActivity; import com.keyman.engine.KMManager; /** - * Settings menu for adjusting the keyboard height. The value for the current device orientation - * is saved in shared preferences. + * Settings menu for adjusting the keyboard height. + * The value for the current device orientation is saved via KMManager. */ public class AdjustKeyboardHeightActivity extends BaseActivity { - private static final String TAG = "AdjustKbdHeight"; public static final String adjustKeyboardHeightKey = "AdjustKeyboardHeight"; - private static Button resetButton = null; - private static ImageView sampleKeyboard = null; - - // Keeps track of the adjusted keyboard height for saving - private static int currentHeight = 0; - private static ViewGroup.LayoutParams layoutParams; + private Button resetButton; + private ImageView sampleKeyboard; + private TextView percentageDisplay; + private int currentHeight; + private ViewGroup.LayoutParams layoutParams; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final Context context = this; - setContentView(R.layout.activity_adjust_keyboard_height); setupEdgeToEdge(R.id.adjust_keyboard_layout); setupStatusBarColors(R.color.keyman_blue, android.R.color.white); - Toolbar toolbar = (Toolbar) findViewById(R.id.titlebar); + Toolbar toolbar = findViewById(R.id.titlebar); setSupportActionBar(toolbar); ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { @@ -60,75 +56,162 @@ public class AdjustKeyboardHeightActivity extends BaseActivity { actionBar.setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.keyman_blue))); } - TextView adjustKeyboardHeightActivityTitle = (TextView) findViewById(R.id.bar_title); - - String titleStr = getString(R.string.adjust_keyboard_height); + TextView adjustKeyboardHeightActivityTitle = findViewById(R.id.bar_title); adjustKeyboardHeightActivityTitle.setTextColor(ContextCompat.getColor(this, R.color.ms_white)); - adjustKeyboardHeightActivityTitle.setText(titleStr); + adjustKeyboardHeightActivityTitle.setText(getString(R.string.adjust_keyboard_height)); + + sampleKeyboard = findViewById(R.id.sample_keyboard); + percentageDisplay = findViewById(R.id.keyboard_height_percentage); + View resizeHandle = findViewById(R.id.resize_handle); - sampleKeyboard = (ImageView) findViewById(R.id.sample_keyboard); layoutParams = sampleKeyboard.getLayoutParams(); - currentHeight = KMManager.getKeyboardHeight(context); - refreshSampleKeyboard(context); + currentHeight = KMManager.getKeyboardHeight(this); + refreshSampleKeyboard(); - resetButton = (Button) findViewById(R.id.reset_to_defaults); - resetButton.setOnClickListener(new View.OnClickListener() { - public void onClick(View v) { - // Clear the keyboard height preferences to reset - KMManager.applyKeyboardHeight(context, KMManager.KeyboardHeight_Reset); - - // Restore default height - currentHeight = KMManager.getKeyboardHeight(context); - refreshSampleKeyboard(context); - } + resetButton = findViewById(R.id.reset_to_defaults); + resetButton.setOnClickListener(v -> { + KMManager.applyKeyboardHeight(this, KMManager.KeyboardHeight_Reset); + currentHeight = KMManager.getKeyboardHeight(this); + refreshSampleKeyboard(); }); - sampleKeyboard.setOnTouchListener(new View.OnTouchListener() { - @Override - public boolean onTouch(View view, MotionEvent event) { - int y = (int)event.getY(); - - switch(event.getAction()) { - case MotionEvent.ACTION_MOVE: - // Update currentHeight as the user drags (moves) - // Increasing the keyboard height is a negative y - currentHeight -= y; - - // Apply lower and upper bounds on currentHeight - int defaultHeight = (int) context.getResources().getDimension(R.dimen.keyboard_height); - currentHeight = Math.max(defaultHeight/2, currentHeight); - currentHeight = Math.min(defaultHeight*2, currentHeight); - - refreshSampleKeyboard(context); - break; - case MotionEvent.ACTION_UP: - // Save the currentHeight when the user releases - KMManager.applyKeyboardHeight(context, currentHeight); - break; - } - return true; + View.OnTouchListener touchListener = (view, event) -> { + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + view.setPressed(true); + break; + case MotionEvent.ACTION_MOVE: + int[] location = new int[2]; + sampleKeyboard.getLocationOnScreen(location); + int viewBottom = location[1] + sampleKeyboard.getHeight(); + int touchY = (int) event.getRawY(); + currentHeight = calculateKeyboardHeightFromTouch(touchY, viewBottom); + refreshSampleKeyboard(); + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_CANCEL: + view.setPressed(false); + KMManager.applyKeyboardHeight(this, currentHeight); + break; } - }); + return true; + }; + + resizeHandle.setOnTouchListener(touchListener); + sampleKeyboard.setOnTouchListener(touchListener); } /** - * Refresh the layout for the sample keyboard - * @param context + * Refresh the layout for the sample keyboard and update percentage display. */ - private void refreshSampleKeyboard(Context context) { - layoutParams.height = currentHeight; - sampleKeyboard.setLayoutParams(layoutParams); + private void refreshSampleKeyboard() { + if (layoutParams != null && sampleKeyboard != null) { + layoutParams.height = currentHeight; + sampleKeyboard.requestLayout(); + + if (percentageDisplay != null) { + percentageDisplay.setText(createLiveKeyboardHeightString()); + } + } + } + + /** + * Calculate percentage from height and default height. + * @param height Current height + * @param defaultHeight Default height + * @return Percentage rounded to nearest integer + */ + private static int calculatePercentage(int height, int defaultHeight) { + if (defaultHeight == 0 || height == 0) { + return 100; + } + return (int) Math.round((double) height / defaultHeight * 100); + } + + /** + * Create a string showing the keyboard height as percentages for both orientations. + * @param context Context + * @return String in format "100% Portrait | 100% Landscape" + */ + public static String createKeyboardHeightString(Context context) { + int portraitHeight = KMManager.getKeyboardHeight(context, Configuration.ORIENTATION_PORTRAIT); + int landscapeHeight = KMManager.getKeyboardHeight(context, Configuration.ORIENTATION_LANDSCAPE); + + int portraitDefault = KMManager.getDefaultKeyboardHeight(Configuration.ORIENTATION_PORTRAIT); + int landscapeDefault = KMManager.getDefaultKeyboardHeight(Configuration.ORIENTATION_LANDSCAPE); + + int portraitPercent = calculatePercentage(portraitHeight, portraitDefault); + int landscapePercent = calculatePercentage(landscapeHeight, landscapeDefault); + + return context.getString(R.string.keyboard_height_format, portraitPercent, landscapePercent); + } + + /** + * Calculate keyboard height from touch Y coordinate and clamp + * within min-max bounds. + * @param touchY Touch Y coordinate in screen coordinates + * @param viewBottom Bottom Y coordinate of keyboard view + * @return Keyboard height in pixels, clamped to min/max bounds + */ + private int calculateKeyboardHeightFromTouch(int touchY, int viewBottom) { + int height = viewBottom - touchY; + int minKeyboardHeight = KMManager.getKeyboardHeightMin(this); + int maxKeyboardHeight = KMManager.getKeyboardHeightMax(this); + return Math.max(minKeyboardHeight, Math.min(maxKeyboardHeight, height)); + } + + /** + * Create a string showing keyboard height percentages for both orientations, + * using the current (unsaved) height for the active orientation. + * @return String in format "100% Portrait | 100% Landscape" + */ + private String createLiveKeyboardHeightString() { + int currentOrientation = KMManager.getOrientation(this); + + int portraitDefault = KMManager.getDefaultKeyboardHeight(Configuration.ORIENTATION_PORTRAIT); + int landscapeDefault = KMManager.getDefaultKeyboardHeight(Configuration.ORIENTATION_LANDSCAPE); + + int portraitHeight; + int landscapeHeight; + if (currentOrientation == Configuration.ORIENTATION_PORTRAIT) { + portraitHeight = currentHeight; + landscapeHeight = KMManager.getKeyboardHeight(this, Configuration.ORIENTATION_LANDSCAPE); + } else { + landscapeHeight = currentHeight; + portraitHeight = KMManager.getKeyboardHeight(this, Configuration.ORIENTATION_PORTRAIT); + } + + int portraitPercent = calculatePercentage(portraitHeight, portraitDefault); + int landscapePercent = calculatePercentage(landscapeHeight, landscapeDefault); + + return getString(R.string.keyboard_height_format, portraitPercent, landscapePercent); + } + + @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); + refreshSampleKeyboard(); } @Override diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java index 4629336635..3aa149f477 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/KeymanSettingsFragment.java @@ -106,6 +106,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { adjustKeyboardHeight.setKey(AdjustKeyboardHeightActivity.adjustKeyboardHeightKey); adjustKeyboardHeight.setTitle(getString(R.string.adjust_keyboard_height)); adjustKeyboardHeight.setWidgetLayoutResource(R.layout.preference_height_icon_layout); + adjustKeyboardHeight.setSummary(AdjustKeyboardHeightActivity.createKeyboardHeightString(context)); Intent adjustKeyboardHeightIntent = new Intent(context, AdjustKeyboardHeightActivity.class); adjustKeyboardHeight.setIntent(adjustKeyboardHeightIntent); @@ -268,5 +269,7 @@ public class KeymanSettingsFragment extends PreferenceFragmentCompat { // Update the language / keyboard count when we return to this menu from deeper levels. languagesPreference.setTitle(getInstalledLanguagesText()); + + adjustKeyboardHeight.setSummary(AdjustKeyboardHeightActivity.createKeyboardHeightString(context)); } } diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_down.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000000..f8a99a03b9 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,10 @@ + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_up.xml b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 0000000000..646c7b0a70 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,10 @@ + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_background.xml b/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_background.xml new file mode 100644 index 0000000000..3f54ca25ca --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_background.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_bar.xml b/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_bar.xml new file mode 100644 index 0000000000..f6882731a1 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/drawable/resize_handle_bar.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + 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 0cb7a262d2..8541398a57 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 @@ -10,32 +10,22 @@ - - + app:layout_constraintTop_toBottomOf="@id/adjust_instructions">